diff --git a/go.mod b/go.mod index 9047b01..c6c935d 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.21 require ( github.com/onsi/ginkgo/v2 v2.9.5 github.com/onsi/gomega v1.27.7 + github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 golang.org/x/net v0.17.0 golang.org/x/tools v0.9.1 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 k8s.io/api v0.27.2 k8s.io/apimachinery v0.27.2 k8s.io/client-go v0.27.2 diff --git a/go.sum b/go.sum index 10b758c..c32c82b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -124,6 +125,8 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -193,6 +196,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -253,6 +257,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/logger/logger_utils.go b/internal/logger/logger_utils.go new file mode 100644 index 0000000..414b333 --- /dev/null +++ b/internal/logger/logger_utils.go @@ -0,0 +1,137 @@ +package logger + +import ( + "context" + "fmt" + "github.com/sirupsen/logrus" + "gopkg.in/natefinch/lumberjack.v2" + "path/filepath" + "strconv" + "time" +) + +type LogLevel string + +const ( + InfoLevel LogLevel = "info" + WarnLevel LogLevel = "warn" + ErrorLevel LogLevel = "error" + TraceID string = "traceID" +) + +type Fields map[string]interface{} + +type Logger struct { + moduleName string + logger *logrus.Entry +} + +type Config struct { + ModuleNameToLogFileDir map[string]string + LogLevel LogLevel + LogFormat logrus.Formatter +} + +type TextVFormatter struct{} + +func (f *TextVFormatter) Format(entry *logrus.Entry) ([]byte, error) { + timestamp := entry.Time.Format("2006-01-02T15:04:05.999Z07:00") + traceID := entry.Data[TraceID] + logLevel := entry.Level.String() + message := entry.Message + log := fmt.Sprintf("%s [%s][%s] %s\n", timestamp, traceID, logLevel, message) + return []byte(log), nil +} + +var FileNameToLoggers map[string]*logrus.Logger +var config Config + +func Initialize(cfg Config) { + config = cfg + FileNameToLoggers = make(map[string]*logrus.Logger) + setLogLevel() + + for moduleName, logFileDir := range config.ModuleNameToLogFileDir { + logger := createLogger(moduleName, logFileDir, InfoLevel) + FileNameToLoggers[fmt.Sprintf("%s_%v", moduleName, InfoLevel)] = logger + logger = createLogger(moduleName, logFileDir, ErrorLevel) + FileNameToLoggers[fmt.Sprintf("%s_%v", moduleName, ErrorLevel)] = logger + } +} + +func createLogger(moduleName, logFileDir string, logLevel LogLevel) *logrus.Logger { + logFilePath := getLogFilePath(logFileDir, moduleName, logLevel) + logger := logrus.New() + logger.Formatter = config.LogFormat + logger.Out = &lumberjack.Logger{ + Filename: logFilePath, + MaxSize: 1024, + MaxBackups: 5, + MaxAge: 28, + Compress: false, + LocalTime: true, + } + return logger +} + +func setLogLevel() { + switch config.LogLevel { + case InfoLevel: + logrus.SetLevel(logrus.InfoLevel) + case WarnLevel: + logrus.SetLevel(logrus.WarnLevel) + case ErrorLevel: + logrus.SetLevel(logrus.ErrorLevel) + default: + logrus.SetLevel(logrus.InfoLevel) + } +} + +func getLogFilePath(logDir, moduleName string, logLevel LogLevel) string { + fileName := fmt.Sprintf("%s_%s.log", moduleName, logLevel) + return filepath.Join(logDir, fileName) +} + +func New(moduleName string) *Logger { + return &Logger{ + moduleName: moduleName, + logger: FileNameToLoggers[fmt.Sprintf("%s_%v", moduleName, InfoLevel)].WithField(TraceID, ""), + } +} + +func NewFromContext(ctx context.Context, moduleName string) *Logger { + traceId, ok := ctx.Value(TraceID).(string) + if !ok { + traceId = "unknown" + } + return &Logger{ + moduleName: moduleName, + logger: FileNameToLoggers[fmt.Sprintf("%s_%v", moduleName, InfoLevel)].WithField(TraceID, traceId), + } +} + +func NewFromContextError(traceID interface{}, moduleName string) *Logger { + return &Logger{ + moduleName: moduleName, + logger: FileNameToLoggers[fmt.Sprintf("%s_%v", moduleName, ErrorLevel)].WithField(TraceID, traceID), + } +} + +func (l *Logger) Info(fields Fields, message string) { + l.logger.WithFields(logrus.Fields(fields)).Info(message) +} + +func (l *Logger) Warn(fields Fields, message string) { + l.logger.WithFields(logrus.Fields(fields)).Warn(message) +} + +func (l *Logger) Error(fields Fields, message string) { + l.logger.WithFields(logrus.Fields(fields)).Error(message) + + NewFromContextError(l.logger.Data[TraceID], l.moduleName).logger.WithFields(logrus.Fields(fields)).Error(message) +} + +func GetTraceId() string { + traceId := time.Now().UnixMicro() + return strconv.FormatInt(traceId, 10) +} diff --git a/internal/logger/logger_utils_test.go b/internal/logger/logger_utils_test.go new file mode 100644 index 0000000..27be7f5 --- /dev/null +++ b/internal/logger/logger_utils_test.go @@ -0,0 +1,58 @@ +package logger + +import ( + "context" + "fmt" + "github.com/sirupsen/logrus" + "testing" + "time" +) + +func TestLogger_Json(t *testing.T) { + config := Config{ModuleNameToLogFileDir: map[string]string{ + "schedule": "logs", + "module_controller": "logs", + }, + LogLevel: InfoLevel, + LogFormat: &logrus.JSONFormatter{TimestampFormat: "2006-01-02T15:04:05.000Z07:00"}, + } + + Initialize(config) + + logger := New("schedule") + logger.Info(Fields{"hello": "word", "foo": "bar"}, "schedule info message") + logger.Error(Fields{"key": "value"}, "schedule error message") + + ctx := context.WithValue(context.Background(), TraceID, GetTraceId()) + logger = NewFromContext(ctx, "module_controller") + logger.Info(Fields{"hello": "word", "foo": "bar"}, "module_controller info message") + logger.Error(Fields{"key": "value"}, "module_controller error message") + + logger.Info(Fields{"hello": "word", "foo": "bar"}, "module_controller info message") + logger.Error(Fields{"key": "value"}, "module_controller error message") + +} + +func TestLogger_CustomVText(t *testing.T) { + config := Config{ModuleNameToLogFileDir: map[string]string{ + "schedule": "logs", + "module_controller": "logs", + }, + LogLevel: ErrorLevel, + LogFormat: &TextVFormatter{}, + } + + Initialize(config) + + logger := New("schedule") + logger.Info(Fields{"hello": "word", "foo": "bar"}, "info message") + logger.Error(Fields{"key": "value"}, "error message") + + ctx := context.WithValue(context.Background(), TraceID, GetTraceId()) + logger = NewFromContext(ctx, "module_controller") + for i := 0; i < 10; i++ { + time.Sleep(time.Millisecond) + logger.Info(Fields{"hello": "word", "foo": "bar"}, fmt.Sprintf("info message %d", i)) + logger.Error(Fields{"hello": "word", "foo": "bar"}, fmt.Sprintf("info message %d", i)) + } +}