Привет. Данная статья это адское изобретение нового велосипеда. Так что на продакшене использовать только на свой страх и риск.
Я долго искал систему для ведения логов на 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 { } } }
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.
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/
Добавить комментарий