From 7be4b7fb196e2afd8d518150acb038b976e15ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=B1=D0=B5=D0=BB=D0=B5=D0=B2=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B4=D1=80=D0=B5=D0=B9=20=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B5?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Fri, 3 Jun 2022 10:32:25 +0300 Subject: [PATCH] init --- .gitignore | 1 + go.mod | 20 +++++++ go.sum | 36 ++++++++++++ logger.go | 79 +++++++++++++++++++++++++++ logger_test.go | 58 ++++++++++++++++++++ service/log.go | 130 ++++++++++++++++++++++++++++++++++++++++++++ service/log_test.go | 1 + service/model.go | 47 ++++++++++++++++ utils/utils.go | 47 ++++++++++++++++ utils/utils_test.go | 52 ++++++++++++++++++ 10 files changed, 471 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 logger.go create mode 100644 logger_test.go create mode 100644 service/log.go create mode 100644 service/log_test.go create mode 100644 service/model.go create mode 100644 utils/utils.go create mode 100644 utils/utils_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd3d225 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logs \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..84c789e --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module git.belvedersky.ru/common/logger + +go 1.18 + +require github.com/valyala/fasthttp v1.37.0 + +require ( + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect +) + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/fatih/color v1.13.0 + github.com/gofiber/fiber/v2 v2.34.0 + github.com/klauspost/compress v1.15.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..41aba6e --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/gofiber/fiber/v2 v2.34.0 h1:96BJMw6uaxQhJsHY54SFGOtGgp9pgombK5Hbi4JSEQA= +github.com/gofiber/fiber/v2 v2.34.0/go.mod h1:ozRQfS+D7EL1+hMH+gutku0kfx1wLX4hAxDCtDzpj4U= +github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE= +github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..a0866ea --- /dev/null +++ b/logger.go @@ -0,0 +1,79 @@ +package logger + +import ( + "io" + "io/ioutil" + "os" + "regexp" + "time" + + "git.belvedersky.ru/common/logger/service" + "git.belvedersky.ru/common/logger/utils" +) + +const ( + ansi = "s,\x1b\\[[0-9;]*[a-zA-Z],,g" +) + +var ( + re = regexp.MustCompile(ansi) +) + +type ( + Logger struct { + cfg service.Config + file os.File + } +) + +// Сервис логирования +func New(cfg service.Config, durationUpdate *time.Duration) *Logger { + logFile := utils.GetLogFile(cfg.Directory, cfg.LogFileName) + l := &Logger{ + cfg: cfg, + file: *logFile, + } + if durationUpdate == nil { + t := time.Hour * 24 + durationUpdate = &t + } + // Смена и очистка файла лога раз в сутки + go l.FileUpdate(false, durationUpdate) + return l +} + +// Смена и очистка лог файла через указанное время +func (lg *Logger) FileUpdate(now bool, durationUpdate *time.Duration) (err error) { + for { + if !now { + time.Sleep(*durationUpdate) + } + b, err := ioutil.ReadAll(&lg.file) + if err != nil { + return err + } + clean := re.ReplaceAllString(string(b), "") + if err = ioutil.WriteFile(lg.file.Name(), []byte(clean), 0644); err != nil { + return err + } + lg.file = *utils.GetLogFile(lg.cfg.Directory, lg.cfg.LogFileName) + if now { + break + } + } + return nil +} + +// Создание нового сервиса логирования +func (lg *Logger) Create(name string) *service.LoggerService { + // Лог файл + l := utils.SetupLog(name) + // Дебаг в консоль + if lg.cfg.Development { + mw := io.MultiWriter(os.Stdout, &lg.file) + l.SetOutput(mw) + } else { + l.SetOutput(&lg.file) + } + return &service.LoggerService{Log: l, ServiceName: lg.cfg.System, Development: lg.cfg.Development, WebhookUrl: lg.cfg.Webhook} +} diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..f44a157 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,58 @@ +package logger + +import ( + "os" + "testing" + + "git.belvedersky.ru/common/logger/service" + + "github.com/fatih/color" +) + +var ( + cfg = service.Config{ + System: "test", + Development: true, + Directory: "test_logs", + LogFileName: "test", + PanicFileName: "test_panic", + Webhook: "https://devtest.galamart.ru/bus/pub?topic=error&channel=error", + } +) + +func TestNew(t *testing.T) { + + s := New(cfg, nil) + if s.cfg != cfg { + t.Errorf("NewLoggerService() = %v, want %v", s.cfg, &cfg) + } + if s.cfg != cfg { + t.Errorf("Log file is nil") + } +} + +func TestCreate(t *testing.T) { + + s := New(cfg, nil) + if s.cfg != cfg { + t.Errorf("NewLoggerService() = %v, want %v", s.cfg, &cfg) + } + testLogger := s.Create("test") + if testLogger.Log == nil { + t.Errorf("Log is nil") + } + testLogger.Print(service.LogStruct{Message: "test", Color: color.FgBlue}) +} + +func TestFileUpdate(t *testing.T) { + s := New(cfg, nil) + if s.cfg != cfg { + t.Errorf("NewLoggerService() = %v, want %v", s.cfg, &cfg) + } + if err := s.FileUpdate(true, nil); err != nil { + t.Error(err) + } + if err := os.RemoveAll("test_logs"); err != nil { + t.Error(err) + } +} \ No newline at end of file diff --git a/service/log.go b/service/log.go new file mode 100644 index 0000000..dceae00 --- /dev/null +++ b/service/log.go @@ -0,0 +1,130 @@ +package service + +import ( + "encoding/json" + "fmt" + "os" + "runtime" + + "git.belvedersky.ru/common/logger/utils" + + "github.com/fatih/color" + "github.com/gofiber/fiber/v2" +) + +var ( + ServiceError = TypeError{Code: 2, Description: "Сервисная ошибка"} +) + +// Трейс ошибки для рекавера +func (s *LoggerService) TraceHandler(c *fiber.Ctx, e interface{}) { + buf := make([]byte, 1024) + buf = buf[:runtime.Stack(buf, false)] + err := fmt.Sprintf("panic: %v\n%s\n", e, buf) + os.Stderr.WriteString(err) + s.Error(LogErrorStruct{ + Error: ServiceError, + Message: err, + }) + c.JSON(err) +} + +func (s *LoggerService) Errorf(f string, v ...interface{}) { + s.Log.Printf("ERROR: "+f, v...) + +} + +func (s *LoggerService) Warningf(f string, v ...interface{}) { + s.Log.Printf("WARNING: "+f, v...) + +} + +func (s *LoggerService) Infof(f string, v ...interface{}) { + s.Log.Printf("INFO: "+f, v...) + +} + +func (s *LoggerService) Debugf(f string, v ...interface{}) { + s.Log.Printf("DEBUG: "+f, v...) + +} + +// Лог дебага +func (s *LoggerService) Debug(ls LogStruct) { + + // Если цвет не указан + if ls.Color == 0 { + ls.Color = color.FgWhite + } + + // Если указан дебаг, то пишем сообщение + if s.Development { + c := color.New(ls.Color) + s.Log.Println(c.Sprint(ls.Message)) + } +} + +func (s *LoggerService) Separator() { + s.Log.Println("------------------------------------------------------------------") +} + +func (s *LoggerService) RouteSeparator(route string) { + s.Log.Println("----------------------" + route + "-------------------------------") +} + +// Простой вывод текста +func (s *LoggerService) Print(ls LogStruct) { + // Если цвет не указан + if ls.Color == 0 { + ls.Color = color.FgWhite + } + + // Пишем в консоль + c := color.New(ls.Color) + s.Log.Println(c.Sprint(ls.Message)) + +} + +// Ошибка +func (s *LoggerService) Error(message LogErrorStruct) { + + // Меняем цвет на красный + c := color.New(color.FgRed) + + // Пишем в консоль + s.Log.Println(c.Sprint(message.Message)) + + // Добавляем название сервиса + message.System = s.ServiceName + + // Маршим в байтики + b, err := json.Marshal(message) + if err != nil { + s.Log.Println(err) + } + + // Отправляем в шину + if err := utils.SendMessage(s.WebhookUrl, b); err != nil { + + // Не получилось отправить в шину + s.Log.Println("Не смогли отправить в nsq:", err) + } + +} + +// Простое оповещение в телеграм без вывода в консоль +func (s *LoggerService) Notify(message string) { + + m := &LogErrorStruct{ + System: s.ServiceName, + Message: message, + Error: TypeError{Code: -111, Description: "Оповещение"}, + } + // Маршим в байтики + b, err := json.Marshal(m) + if err != nil { + s.Log.Println(err) + } + // Отправляем в шину + go utils.SendMessage(s.WebhookUrl, b) +} diff --git a/service/log_test.go b/service/log_test.go new file mode 100644 index 0000000..6d43c33 --- /dev/null +++ b/service/log_test.go @@ -0,0 +1 @@ +package service diff --git a/service/model.go b/service/model.go new file mode 100644 index 0000000..42c8dc5 --- /dev/null +++ b/service/model.go @@ -0,0 +1,47 @@ +package service + +import ( + "log" + + "github.com/fatih/color" +) + +type ( + Config struct { + System string // Название сервиса + Development bool // Режим разработки + Directory string // Папка в которую необходимо сохранять логи + LogFileName string // Файл логов + PanicFileName string // Файл ошибок с паникой + Webhook string // Url для отправки вебхука + } + + LoggerService struct { + Log *log.Logger + ServiceName string + WebhookUrl string + Development bool + } + + LogStruct struct { + Message string + Color color.Attribute + } + + LogErrorStruct struct { + System string `json:"system"` + Message string `json:"message"` + Error TypeError `json:"error"` + } + + TypeError struct { + Code int `json:"code"` + Description string `json:"description"` + } +) + +// var ( +// MarshalError = TypeError{Code: 1, Description: "Ошибка маршала"} +// ServiceError = TypeError{Code: 2, Description: "Сервисная ошибка"} +// CacheError = TypeError{Code: 3, Description: "Ошибка badger"} +// ) diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..5e3d0cf --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "log" + "math/rand" + "os" + "time" + + "github.com/fatih/color" + "github.com/valyala/fasthttp" +) + +// Получения файла лога +func GetLogFile(logDir, fileName string) *os.File { + if _, err := os.Stat(logDir); os.IsNotExist(err) { + err := os.Mkdir(logDir, 0777) + if err != nil { + panic(err) + } + } + logFile, err := os.OpenFile(fmt.Sprintf("./%s/%s-%s.log", logDir, fileName, time.Now().Format("02-01-2006")), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("Ошибка открытия файла логов %v", err) + } + return logFile +} + +// Отправка сообщения +func SendMessage(uri string, message []byte) error { + req := fasthttp.AcquireRequest() + res := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(res) + req.SetRequestURI(uri) + req.Header.SetMethod("POST") + req.Header.SetContentType("application/json") + req.SetBody(message) + return fasthttp.Do(req, res) +} + +// Инициализация лога +func SetupLog(name string) *log.Logger { + s := []color.Attribute{color.FgBlue, color.FgHiMagenta, color.FgHiGreen, color.FgYellow} + prefix := color.New(s[rand.Intn(len(s))]) + return log.New(nil, prefix.Sprintf(" [%s] ", name), log.LstdFlags) +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 0000000..cf059d2 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,52 @@ +package utils + +import ( + "encoding/json" + "os" + "testing" +) + +type ( + LogTestStruct struct { + System string `json:"system"` + Message string `json:"message"` + Error TypeError `json:"error"` + } + TypeError struct { + Code int `json:"code"` + Description string `json:"description"` + } +) + +func TestGetLogFile(t *testing.T) { + s := GetLogFile("test_logs", "test") + if s == nil { + t.Error("log file is nil") + } + if err := os.RemoveAll("test_logs"); err != nil { + t.Error(err) + } +} + +func TestSendMessage(t *testing.T) { + m := &LogTestStruct{ + System: "go test", + Message: "CI TEST", + Error: TypeError{Code: -112, Description: "Тестирование"}, + } + // Маршим в байтики + b, err := json.Marshal(m) + if err != nil { + t.Error(err) + } + if err := SendMessage("https://devtest.galamart.ru/bus/pub?topic=error&channel=error", b); err != nil { + t.Error(err) + } +} + +func TestSetupLog(t *testing.T) { + l := SetupLog("test") + if l.Prefix() != " [test] " { + t.Error(l.Prefix()) + } +}