This commit is contained in:
Кобелев Андрей Андреевич 2022-06-03 10:32:25 +03:00
parent 02db2c5c68
commit 7be4b7fb19
10 changed files with 471 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
logs

20
go.mod Normal file
View File

@ -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
)

36
go.sum Normal file
View File

@ -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=

79
logger.go Normal file
View File

@ -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}
}

58
logger_test.go Normal file
View File

@ -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)
}
}

130
service/log.go Normal file
View File

@ -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)
}

1
service/log_test.go Normal file
View File

@ -0,0 +1 @@
package service

47
service/model.go Normal file
View File

@ -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"}
// )

47
utils/utils.go Normal file
View File

@ -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)
}

52
utils/utils_test.go Normal file
View File

@ -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())
}
}