Гибка система логирования на Go

от автора

Привет. Данная статья это адское изобретение нового велосипеда. Так что на продакшене использовать только на свой страх и риск.

Я долго искал систему для ведения логов на Go которая удовлетворила бы мои запросы (гибкая, возможность уведомления на емейл, очень быстрая и хранение логов в мускуле)

Скажу честно искал я дня три так не чего и не нашел. Потом я начал писать свой велосипед (первая его версия была очень кривая и еле еле работала). Потом я удалил весь тот код и начал думать писать заново.

Я сразу понял что писать в бд каждый раз очень утомительно. По этому я сделал так:

Библиотека для каждого типа логов делает ключ в редисе куда пишет данные в таком формате

Данные в редисе

('Debug','2015-11-05 20:12:37.700052989 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.700506704 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.700663127 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.700803651 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.700987999 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701128513 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701293643 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701433496 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701602372 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701745287 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.701925988 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702093499 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702276867 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702431455 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702581625 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702738953 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.702899007 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.703055622 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.703210768 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.70340691 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.703566623 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.7037252 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.703954549 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.704119435 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.704281902 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.704536707 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.704721061 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.704901908 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705106033 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705284342 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705465074 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705633484 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705802108 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.705962381 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.706129288 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.706314702 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.706463092 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.706674268 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.706848586 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707050005 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707221136 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707379335 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707583978 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707742422 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.707967253 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.708164671 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.708410554 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.708578324 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.708775197 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.708955609 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.709184168 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.709349784 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.709510939 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.709726286 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.709940253 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.710141611 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.71034329 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.710537637 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.710763157 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.710969449 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.711167704 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.711355522 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.711550562 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.711756 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.712048767 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.712273974 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.712517739 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.712828333 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.71306392 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.713335398 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.713570618 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.71389819 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.714182802 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.714448273 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.714754937 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.715018147 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.715291228 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.715596998 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.715910118 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.7162719 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.716552975 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.716807074 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.717153412 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.717434854 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.717704591 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.717991896 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.718283451 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.718590239 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.718849058 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.719152303 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.719424972 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.719734567 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.720070491 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.720386241 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.720651655 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.72094698 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.721207595 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.721514296 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.721776408 +0200 EET','Testing'),('Debug','2015-11-05 20:12:37.722090163 +0200 EET','Testing'),

Я сразу захотел их встроить в sql запрос но наткнулся на ошибку sql синтаксиса. Я долго бился голов об клавиатуру искал багу в коде, а оказалось что из — за способа добавления данных в редис в конце строки всегда будет кома. Когда я её заметил я долго гуглил спросил на тостере как же убрать эту кому. Оказалось проще некуда

strings.TrimRight(data, ",") 

Потом я начал пытаться из библиотеки запустить функцию в отдельном потоке. Со временем я понял что я осёл что это я сделать не могу и поэтому вынес её в демон.

На выходе я имею
Библиотека -> пишет данные в редис. Вся нагрузка на редис
Демон -> одним запросом все логи с редиса перемещаю в mysql

На слабом впс (частота 1.6; 1ядро, 2 гб озу) 1 000 000 записей в мускуль добавилось за 25мл.сек (бд без тюнинга) правда в редис эти данные писались около 6 минут. Вся нагрузка на на редисе.

Вот вроде и всё.

конфиг

{ "MailConf" : [ "логин на smtp сервере", "пароль", "smtp сервер", "порт smtp сервера" ], "MailTo" : [ "на_какие_емейлы_отправлять@gmail.com", "v.grabko99@yandex.ru" ], "Types" : [ "Debug", "Info", "Warn", "Error", "Fatal" ], "EmailSend" : [ "Error", "Fatal" ], "Redis" : [ "localhost:6379", "parsh888", "log_" ], "MysqlConnect" : [ "юзер", "пароль", "база данных" ], "MysqlTable" : "log", "ReplicationTimeSecond" : 320 }
библиотека

package GeneralsLog  import ( 	"encoding/json" 	"gopkg.in/redis.v3" 	"io/ioutil" 	"log" 	"microService/libs/mail" 	"time" )  type Config struct { 	MailTo, MailConf, Types, EmailSend, Redis, MysqlConnect []string 	MysqlTable                                              string }  var ( 	R           *redis.Client 	MailConf    map[string]string 	MailTo      []string 	Types       []string 	EmailSend   []string 	RedisConfig []string  	//Путь к файлу с конфигами 	config_file string = "/home/v-smerti/localhost/api/src/microService/config/log.json" )  func init() {  	//Спарсим конфиг 	bs, err := ioutil.ReadFile(config_file) 	if err != nil { 		log.Panicln(err) 	}  	b := []byte(bs) 	var conf Config 	err = json.Unmarshal(b, &conf) 	if err != nil { 		log.Panicln(err) 	} 	//Передаём данные с конфиг файла в глобальные переменные 	MailConf = map[string]string{ 		"username": conf.MailConf[0], 		"password": conf.MailConf[1], 		"host":     conf.MailConf[2], 		"port":     conf.MailConf[3], 	}  	MailTo = conf.MailTo 	Types = conf.Types 	EmailSend = conf.EmailSend 	RedisConfig = conf.Redis  	//Конект с редисом 	R = redis.NewClient(&redis.Options{ 		Addr:     RedisConfig[0], 		Password: RedisConfig[1], 		DB:       0, 	}) 	//Инициализация пакета. Здесь проверям есть ли в редисе такие типы логов. Если нет то создаём 	for _, typ := range Types { 		_, err := R.Get(RedisConfig[2] + typ).Result() 		if err == redis.Nil { 			if err := R.Set(RedisConfig[2]+typ, " ", 0).Err(); err != nil { 				mail.Send(MailConf, MailTo, "Fatal error game", "package GeneralsLog func Init____R.Set (Создание пустой записи в редис ключ "+typ+")") 			} 		} else if err != nil { 			mail.Send(MailConf, MailTo, "Fatal error game", "package GeneralsLog func Init____R.Set (Проверка записи "+typ+" в редисе)") 		} 	} }  func New(types string, messages string) { 	for _, typ := range Types { 		if typ == types { 			if data, err := R.Get(RedisConfig[2] + typ).Result(); err == nil {  				//Надо для проверки текущего типа в списке на отправку уведомления на e-mail 				for _, b := range EmailSend { 					//надо отправлять 					if b == typ { 						mail.Send(MailConf, MailTo, typ, messages) 					} 				}  				data = data + "('" + types + "','" + time.Now().String() + "','" + messages + "'),"  				if err := R.Set(RedisConfig[2]+typ, data, 0).Err(); err != nil { 					mail.Send(MailConf, MailTo, "Fatal error game", "package GeneralsLog func "+typ+"____R.Set (Обновление записи в редисе)") 				} 			} else { 				mail.Send(MailConf, MailTo, "Fatal error game", "package GeneralsLog func "+typ+"____R.Get (Получение данных с редиса)") 			} 		} else { 		} 	}  }  

демон

package main

import (
«database/sql»
_ «github.com/go-sql-driver/mysql» //можно подключить любую sql базу данных
«gopkg.in/redis.v3»
«log»
«microService/libs/mail»
«strings»
«time»

«encoding/json»
«io/ioutil»
)

type Config struct {
MailTo, MailConf, Types, EmailSend, Redis, MysqlConnect []string
MysqlTable string
ReplicationTimeSecond time.Duration
}

var (
DB *sql.DB
R *redis.Client
MailConf map[string]string
MailTo []string
Types []string
EmailSend []string
RedisConfig []string
MysqlTable string
ReplicationSecond time.Duration
//Путь к файлу с конфигами
config_file string = "/home/v-smerti/localhost/api/src/microService/config/log.json"
)

func init() {
print(«Starting…»)
//Спарсим конфиг
bs, err := ioutil.ReadFile(config_file)
if err != nil {
log.Panicln(err)
}

b := []byte(bs)
var conf Config
err = json.Unmarshal(b, &conf)
if err != nil {
log.Panicln(err)
}
//Передаём данные с конфиг файла в глобальные переменные
MailConf = map[string]string{
«username»: conf.MailConf[0],
«password»: conf.MailConf[1],
«host»: conf.MailConf[2],
«port»: conf.MailConf[3],
}

MailTo = conf.MailTo
Types = conf.Types
EmailSend = conf.EmailSend
RedisConfig = conf.Redis
MysqlTable = conf.MysqlTable
ReplicationSecond = conf.ReplicationTimeSecond

//Конект с редисом
R = redis.NewClient(&redis.Options{
Addr: RedisConfig[0],
Password: RedisConfig[1],
DB: 0,
})

//конект с бд
db, err := sql.Open(«mysql», conf.MysqlConnect[0]+":"+conf.MysqlConnect[1]+"@/"+conf.MysqlConnect[2])
if err != nil {
log.Fatal(err)
}
DB = db

//Инициализация пакета. Здесь проверям есть ли в редисе такие типы логов. Если нет то создаём
for _, typ := range Types {
_, err := R.Get(RedisConfig[2] + typ).Result()
if err == redis.Nil {
if err := R.Set(RedisConfig[2]+typ, " ", 0).Err(); err != nil {
mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func Init____R.Set (Создание пустой записи в редис ключ „+typ+“)»)
}
} else if err != nil {
mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func Init____R.Set (Проверка записи „+typ+“ в редисе)»)
}
}
print(" Ok!")
}

func main() {
for {
replication_db()
time.Sleep(time.Second * ReplicationSecond)
}
}

func replication_db() {

for _, typ := range Types {
data, err := R.Get(RedisConfig[2] + typ).Result()
if err == redis.Nil {

mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func replication_db____R.Set (Нету в редисе „+typ+“)»)
if err := R.Set(RedisConfig[2]+typ, " ", 0).Err(); err != nil {
mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func replication_db____R.Set (Создание пустой записи в редис ключ „+typ+“)»)
}
} else if err != nil {
log.Fatal(err)
mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func replication_db____R.GET (Фатальная ошибка редиса)»)

} else {
if data != " " {
_, err := DB.Exec(«INSERT INTO » + MysqlTable + " (type,time,messages) VALUES" + strings.TrimRight(data, ","))
if err != nil {
log.Fatal(err)
} else {
log.Println(«replication»)
}
if err := R.Set(RedisConfig[2]+typ, " ", 0).Err(); err != nil {
mail.Send(MailConf, MailTo, «Fatal error game», «package GeneralsLog func replication_db____R.Set (Очистка записии „+typ+“)»)
}
}

}
}
}

В коде ещё есть импорт пакета mail.

Пакет mail

package mail  import ( 	"fmt" 	"net/smtp" )  func Send(conf map[string]string, to []string, subject string, msg string) error { 	auth := smtp.PlainAuth( 		"", 		conf["username"], 		conf["password"], 		conf["host"], 	) 	address := fmt.Sprintf("%v:%v", conf["host"], conf["port"]) 	body := []byte("Subject: " + subject + "\r\n\r\n" + msg) 	err := smtp.SendMail( 		address, 		auth, 		conf["username"], 		to, 		body, 	) 	if err != nil { 		return err 	} 	return nil } 

ссылка на оригинал статьи http://habrahabr.ru/post/270263/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *