diff --git a/logger/logger.go b/logger/logger.go index 6448ccaf..76801d33 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -6,6 +6,10 @@ import ( "os" ) +type ContextAttrFunc func(ctx context.Context) []interface{} + +var DefaultContextAttrFuncs []ContextAttrFunc + var ( // DefaultLogger variable DefaultLogger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL")))) diff --git a/logger/options.go b/logger/options.go index 452273dc..ed95acb4 100644 --- a/logger/options.go +++ b/logger/options.go @@ -3,6 +3,7 @@ package logger import ( "context" "io" + "log/slog" "os" ) @@ -21,6 +22,18 @@ type Options struct { Fields []interface{} // CallerSkipCount number of frmaes to skip CallerSkipCount int + // ContextAttrFuncs contains funcs that executed before log func on context + ContextAttrFuncs []ContextAttrFunc + // TimeKey is the key used for the time of the log call + TimeKey string + // LevelKey is the key used for the level of the log call + LevelKey string + // MessageKey is the key used for the message of the log call + MessageKey string + // SourceKey is the key used for the source file and line of the log call + SourceKey string + // StacktraceKey is the key used for the stacktrace + StacktraceKey string // Stacktrace controls writing of stacktaces on error Stacktrace bool // The logging level the logger should log @@ -30,18 +43,29 @@ type Options struct { // NewOptions creates new options struct func NewOptions(opts ...Option) Options { options := Options{ - Level: DefaultLevel, - Fields: make([]interface{}, 0, 6), - Out: os.Stderr, - CallerSkipCount: DefaultCallerSkipCount, - Context: context.Background(), + Level: DefaultLevel, + Fields: make([]interface{}, 0, 6), + Out: os.Stderr, + CallerSkipCount: DefaultCallerSkipCount, + Context: context.Background(), + ContextAttrFuncs: DefaultContextAttrFuncs, } + + WithMicroKeys()(&options) + for _, o := range opts { o(&options) } return options } +// WithContextAttrFuncs appends default funcs for the context arrts filler +func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option { + return func(o *Options) { + o.ContextAttrFuncs = append(o.ContextAttrFuncs, fncs...) + } +} + // WithFields set default fields for the logger func WithFields(fields ...interface{}) Option { return func(o *Options) { @@ -90,3 +114,43 @@ func WithName(n string) Option { o.Name = n } } + +func WithZapKeys() Option { + return func(o *Options) { + o.TimeKey = "@timestamp" + o.LevelKey = "level" + o.MessageKey = "msg" + o.SourceKey = "caller" + o.StacktraceKey = "stacktrace" + } +} + +func WithZerologKeys() Option { + return func(o *Options) { + o.TimeKey = "time" + o.LevelKey = "level" + o.MessageKey = "message" + o.SourceKey = "caller" + o.StacktraceKey = "stacktrace" + } +} + +func WithSlogKeys() Option { + return func(o *Options) { + o.TimeKey = slog.TimeKey + o.LevelKey = slog.LevelKey + o.MessageKey = slog.MessageKey + o.SourceKey = slog.SourceKey + o.StacktraceKey = "stacktrace" + } +} + +func WithMicroKeys() Option { + return func(o *Options) { + o.TimeKey = "timestamp" + o.LevelKey = "level" + o.MessageKey = "msg" + o.SourceKey = "caller" + o.StacktraceKey = "stacktrace" + } +} diff --git a/logger/slog/options.go b/logger/slog/options.go deleted file mode 100644 index 58e277bc..00000000 --- a/logger/slog/options.go +++ /dev/null @@ -1,27 +0,0 @@ -package slog - -import "go.unistack.org/micro/v3/logger" - -type sourceKey struct{} - -func WithSourceKey(v string) logger.Option { - return logger.SetOption(sourceKey{}, v) -} - -type timeKey struct{} - -func WithTimeKey(v string) logger.Option { - return logger.SetOption(timeKey{}, v) -} - -type messageKey struct{} - -func WithMessageKey(v string) logger.Option { - return logger.SetOption(messageKey{}, v) -} - -type levelKey struct{} - -func WithLevelKey(v string) logger.Option { - return logger.SetOption(levelKey{}, v) -} diff --git a/logger/slog/slog.go b/logger/slog/slog.go index 03bf2810..672505ff 100644 --- a/logger/slog/slog.go +++ b/logger/slog/slog.go @@ -17,13 +17,6 @@ import ( var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`) -var ( - DefaultSourceKey = slog.SourceKey - DefaultTimeKey = slog.TimeKey - DefaultMessageKey = slog.MessageKey - DefaultLevelKey = slog.LevelKey -) - var ( traceValue = slog.StringValue("trace") debugValue = slog.StringValue("debug") @@ -38,15 +31,15 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr { case slog.SourceKey: source := a.Value.Any().(*slog.Source) a.Value = slog.StringValue(source.File + ":" + strconv.Itoa(source.Line)) - a.Key = s.sourceKey + a.Key = s.opts.SourceKey case slog.TimeKey: - a.Key = s.timeKey + a.Key = s.opts.TimeKey case slog.MessageKey: - a.Key = s.messageKey + a.Key = s.opts.MessageKey case slog.LevelKey: level := a.Value.Any().(slog.Level) lvl := slogToLoggerLevel(level) - a.Key = s.levelKey + a.Key = s.opts.LevelKey switch { case lvl < logger.DebugLevel: a.Value = traceValue @@ -69,14 +62,10 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr { } type slogLogger struct { - slog *slog.Logger - leveler *slog.LevelVar - levelKey string - messageKey string - sourceKey string - timeKey string - opts logger.Options - mu sync.RWMutex + slog *slog.Logger + leveler *slog.LevelVar + opts logger.Options + mu sync.RWMutex } func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger { @@ -88,24 +77,7 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger { } l := &slogLogger{ - opts: options, - levelKey: s.levelKey, - messageKey: s.messageKey, - sourceKey: s.sourceKey, - timeKey: s.timeKey, - } - - if v, ok := l.opts.Context.Value(levelKey{}).(string); ok && v != "" { - l.levelKey = v - } - if v, ok := l.opts.Context.Value(messageKey{}).(string); ok && v != "" { - l.messageKey = v - } - if v, ok := l.opts.Context.Value(sourceKey{}).(string); ok && v != "" { - l.sourceKey = v - } - if v, ok := l.opts.Context.Value(timeKey{}).(string); ok && v != "" { - l.timeKey = v + opts: options, } l.leveler = new(slog.LevelVar) @@ -137,13 +109,7 @@ func (s *slogLogger) Options() logger.Options { func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger { s.mu.RLock() - nl := &slogLogger{ - opts: s.opts, - levelKey: s.levelKey, - messageKey: s.messageKey, - sourceKey: s.sourceKey, - timeKey: s.timeKey, - } + nl := &slogLogger{opts: s.opts} nl.leveler = new(slog.LevelVar) nl.leveler.Set(s.leveler.Level()) @@ -163,21 +129,13 @@ func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger { func (s *slogLogger) Init(opts ...logger.Option) error { s.mu.Lock() - for _, o := range opts { - o(&s.opts) + + if len(s.opts.ContextAttrFuncs) == 0 { + s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs } - if v, ok := s.opts.Context.Value(levelKey{}).(string); ok && v != "" { - s.levelKey = v - } - if v, ok := s.opts.Context.Value(messageKey{}).(string); ok && v != "" { - s.messageKey = v - } - if v, ok := s.opts.Context.Value(sourceKey{}).(string); ok && v != "" { - s.sourceKey = v - } - if v, ok := s.opts.Context.Value(timeKey{}).(string); ok && v != "" { - s.timeKey = v + for _, o := range opts { + o(&s.opts) } s.leveler = new(slog.LevelVar) @@ -190,8 +148,6 @@ func (s *slogLogger) Init(opts ...logger.Option) error { handler := slog.NewJSONHandler(s.opts.Out, handleOpt) s.slog = slog.New(handler).With(s.opts.Fields...) - slog.SetDefault(s.slog) - s.mu.Unlock() return nil @@ -204,6 +160,15 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interfa var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0]) + if s.opts.Stacktrace && lvl == logger.ErrorLevel { + stackInfo := make([]byte, 1024*1024) + if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { + traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) + if len(traceLines) != 0 { + r.AddAttrs(slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) + } + } + } // r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -215,6 +180,15 @@ func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, att var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf(msg, attrs...), pcs[0]) + if s.opts.Stacktrace && lvl == logger.ErrorLevel { + stackInfo := make([]byte, 1024*1024) + if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { + traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) + if len(traceLines) != 0 { + r.AddAttrs(slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) + } + } + } // r.Add(attrs...) _ = s.slog.Handler().Handle(ctx, r) } @@ -399,24 +373,9 @@ func (s *slogLogger) String() string { func NewLogger(opts ...logger.Option) logger.Logger { s := &slogLogger{ - opts: logger.NewOptions(opts...), - sourceKey: DefaultSourceKey, - timeKey: DefaultTimeKey, - messageKey: DefaultMessageKey, - levelKey: DefaultLevelKey, - } - if v, ok := s.opts.Context.Value(levelKey{}).(string); ok && v != "" { - s.levelKey = v - } - if v, ok := s.opts.Context.Value(messageKey{}).(string); ok && v != "" { - s.messageKey = v - } - if v, ok := s.opts.Context.Value(sourceKey{}).(string); ok && v != "" { - s.sourceKey = v - } - if v, ok := s.opts.Context.Value(timeKey{}).(string); ok && v != "" { - s.timeKey = v + opts: logger.NewOptions(opts...), } + return s }