parent
7d08058df6
commit
99a53ac87c
@ -0,0 +1,26 @@ |
||||
package logger |
||||
|
||||
import ( |
||||
"time" |
||||
) |
||||
|
||||
type Configuration struct { |
||||
Stdout struct { |
||||
Level string `yaml:"level"` |
||||
} `yaml:"console"` |
||||
File struct { |
||||
Enabled bool `yaml:"enabled"` |
||||
Level string `yaml:"level"` |
||||
Filename string `yaml:"filename"` |
||||
MaxSize int `yaml:"maxsize"` |
||||
MaxAge int `yaml:"maxage"` |
||||
MaxBackups int `yaml:"maxbackups"` |
||||
LocalTime bool `yaml:"localtime"` |
||||
Compress bool `yaml:"compress"` |
||||
} |
||||
Sentry struct { |
||||
Level string `yaml:"level"` |
||||
DSN string `yaml:"dsn"` |
||||
Timeout time.Duration `yaml:"timeout"` |
||||
} `yaml:"sentry"` |
||||
} |
@ -0,0 +1,24 @@ |
||||
package logger |
||||
|
||||
import ( |
||||
"github.com/stretchr/testify/assert" |
||||
"testing" |
||||
) |
||||
|
||||
func Test_buildErrorsFilename(t *testing.T) { |
||||
testCases := []struct { |
||||
logFileName string |
||||
expectedFileName string |
||||
}{ |
||||
{"logs/logs.log", "logs/logs-errors.log"}, |
||||
{"logs/advisor.log", "logs/advisor-errors.log"}, |
||||
{"logs/country.log", "logs/country-errors.log"}, |
||||
{"logs/country.old.log", "logs/country.old-errors.log"}, |
||||
} |
||||
|
||||
for _, test := range testCases { |
||||
t.Run(test.logFileName, func(t *testing.T) { |
||||
assert.Equal(t, test.expectedFileName, buildErrorsFilename(test.logFileName), "result") |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
module go-logger |
||||
|
||||
go 1.23 |
||||
|
||||
require ( |
||||
github.com/tchap/zapext v1.0.0 |
||||
go.uber.org/zap v1.27.0 |
||||
) |
||||
|
||||
require ( |
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect |
||||
github.com/davecgh/go-spew v1.1.1 // indirect |
||||
github.com/getsentry/raven-go v0.2.0 // indirect |
||||
github.com/pkg/errors v0.9.1 // indirect |
||||
github.com/pmezard/go-difflib v1.0.0 // indirect |
||||
github.com/stretchr/testify v1.9.0 // indirect |
||||
go.uber.org/multierr v1.10.0 // indirect |
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect |
||||
gopkg.in/yaml.v3 v3.0.1 // indirect |
||||
) |
@ -0,0 +1,23 @@ |
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= |
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= |
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= |
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= |
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= |
||||
github.com/tchap/zapext v1.0.0 h1:qPxfRLzqYzemT+Pgs5VoH8NGU5YS7cgCnhcqRGkmrXc= |
||||
github.com/tchap/zapext v1.0.0/go.mod h1:0VgDSQ0xHJRqkxrwu3G2i2762jSnAJMz7rYxiZGpW1U= |
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= |
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= |
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= |
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= |
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,141 @@ |
||||
package logger |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/getsentry/raven-go" |
||||
"github.com/tchap/zapext/zapsentry" |
||||
"go.uber.org/zap" |
||||
"go.uber.org/zap/zapcore" |
||||
"gopkg.in/natefinch/lumberjack.v2" |
||||
) |
||||
|
||||
var zapLogger *zap.Logger |
||||
|
||||
func Named(name string) *zap.Logger { |
||||
return zapLogger.Named(name) |
||||
} |
||||
|
||||
func With(fields ...zap.Field) *zap.Logger { |
||||
return zapLogger.With(fields...) |
||||
} |
||||
|
||||
func LogPanic(msg string) { |
||||
if r := recover(); r != nil { |
||||
buf := make([]byte, 10240) |
||||
n := runtime.Stack(buf, false) |
||||
err := fmt.Sprintf("%v\n\n%s", r, buf[:n]) |
||||
Error(msg, zap.String("error", err)) |
||||
time.Sleep(5 * time.Second) // wait to write logs and sent to sentry
|
||||
} |
||||
} |
||||
|
||||
func Fatal(msg string, fields ...zap.Field) { |
||||
zapLogger.Fatal(msg, fields...) |
||||
} |
||||
|
||||
func Error(msg string, fields ...zap.Field) { |
||||
zapLogger.Error(msg, fields...) |
||||
} |
||||
|
||||
func Warn(msg string, fields ...zap.Field) { |
||||
zapLogger.Warn(msg, fields...) |
||||
} |
||||
|
||||
func Info(msg string, fields ...zap.Field) { |
||||
zapLogger.Info(msg, fields...) |
||||
} |
||||
|
||||
func Debug(msg string, fields ...zap.Field) { |
||||
zapLogger.Debug(msg, fields...) |
||||
} |
||||
|
||||
func Sync() error { |
||||
return zapLogger.Sync() |
||||
} |
||||
|
||||
func buildErrorsFilename(logFileName string) string { |
||||
ext := path.Ext(logFileName) |
||||
errFileName := strings.TrimSuffix(path.Base(logFileName), ext) |
||||
errFileName += "-errors" + ext |
||||
return filepath.Join(path.Dir(logFileName), errFileName) |
||||
} |
||||
|
||||
func Init(conf *Configuration) { |
||||
cfg := zap.NewProductionEncoderConfig() |
||||
cfg.EncodeTime = zapcore.RFC3339NanoTimeEncoder |
||||
|
||||
cores := []zapcore.Core{ |
||||
zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.Lock(os.Stdout), loggingLevelGTE(conf.Stdout.Level)), |
||||
} |
||||
|
||||
// file logger
|
||||
if conf.File.Enabled { |
||||
loggerConf := conf.File |
||||
fileLevelEnabler := loggingLevelGTE(loggerConf.Level) |
||||
writer := zapcore.AddSync( |
||||
&lumberjack.Logger{ |
||||
Filename: loggerConf.Filename, |
||||
LocalTime: loggerConf.LocalTime, |
||||
MaxSize: loggerConf.MaxSize, |
||||
MaxBackups: loggerConf.MaxBackups, |
||||
}, |
||||
) |
||||
cores = append(cores, zapcore.NewCore(zapcore.NewJSONEncoder(cfg), writer, fileLevelEnabler)) |
||||
|
||||
redirectStderrToFile(conf) |
||||
} |
||||
|
||||
// sentry
|
||||
if conf.Sentry.DSN != "" { |
||||
loggerConf := conf.Sentry |
||||
tags := map[string]string{ |
||||
"env": os.Getenv("ENVIRONMENT"), |
||||
} |
||||
client, err := raven.NewWithTags(loggerConf.DSN, tags) |
||||
if err != nil { |
||||
log.Fatal("failed to get a Sentry client", err) |
||||
} |
||||
sentryLevelEnabler := loggingLevelGTE(loggerConf.Level) |
||||
cores = append(cores, zapsentry.NewCore(sentryLevelEnabler, client)) |
||||
} |
||||
|
||||
core := zapcore.NewTee(cores...) |
||||
|
||||
logger := zap.New(core) |
||||
defer func() { |
||||
_ = logger.Sync() |
||||
}() |
||||
SetLogger(logger) |
||||
} |
||||
|
||||
type levelComparator func(msgLevel zapcore.Level, loggingLevel zapcore.Level) bool |
||||
|
||||
func loggingLevelGTE(level string) zap.LevelEnablerFunc { |
||||
return loggingLevelEnabledFunc( |
||||
level, func(msgLevel zapcore.Level, loggingLevel zapcore.Level) bool { |
||||
return msgLevel >= loggingLevel |
||||
}, |
||||
) |
||||
} |
||||
|
||||
func loggingLevelEnabledFunc(level string, comparator levelComparator) zap.LevelEnablerFunc { |
||||
var loggingLevel zapcore.Level |
||||
if err := loggingLevel.UnmarshalText([]byte(level)); err != nil { |
||||
log.Fatal("Fail parse console log level", err) |
||||
} |
||||
return func(msgLevel zapcore.Level) bool { |
||||
return comparator(msgLevel, loggingLevel) |
||||
} |
||||
} |
||||
|
||||
func SetLogger(logger *zap.Logger) { |
||||
zapLogger = logger |
||||
} |
@ -0,0 +1,20 @@ |
||||
//go:build !windows
|
||||
|
||||
package logger |
||||
|
||||
import ( |
||||
"log" |
||||
"os" |
||||
"syscall" |
||||
) |
||||
|
||||
func redirectStderrToFile(conf *Configuration) { |
||||
fileName := buildErrorsFilename(conf.File.Filename) |
||||
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
||||
if err != nil { |
||||
log.Fatalf("open %s: %s", fileName, err) |
||||
} |
||||
if err = syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd())); err != nil { |
||||
log.Fatalf("Failed to redirect stderr to file: %s", err) |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
//go:build windows
|
||||
|
||||
package logger |
||||
|
||||
func redirectStderrToFile(conf *Configuration) { |
||||
} |
Loading…
Reference in new issue