diff --git a/logger/logger.go b/logger/logger.go index 76801d33..65bcdd16 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -71,76 +71,106 @@ type Logger interface { type Field interface{} // Info writes msg to default logger on info level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Info(ctx context.Context, args ...interface{}) { - DefaultLogger.Info(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Info(ctx, args...) } // Error writes msg to default logger on error level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Error(ctx context.Context, args ...interface{}) { - DefaultLogger.Error(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Error(ctx, args...) } // Debug writes msg to default logger on debug level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Debug(ctx context.Context, args ...interface{}) { - DefaultLogger.Debug(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debug(ctx, args...) } // Warn writes msg to default logger on warn level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Warn(ctx context.Context, args ...interface{}) { - DefaultLogger.Warn(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warn(ctx, args...) } // Trace writes msg to default logger on trace level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Trace(ctx context.Context, args ...interface{}) { - DefaultLogger.Trace(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Trace(ctx, args...) } // Fatal writes msg to default logger on fatal level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Fatal(ctx context.Context, args ...interface{}) { - DefaultLogger.Fatal(ctx, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatal(ctx, args...) } // Infof writes formatted msg to default logger on info level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Infof(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Infof(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Infof(ctx, msg, args...) } // Errorf writes formatted msg to default logger on error level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Errorf(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Errorf(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Errorf(ctx, msg, args...) } // Debugf writes formatted msg to default logger on debug level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Debugf(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Debugf(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debugf(ctx, msg, args...) } // Warnf writes formatted msg to default logger on warn level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Warnf(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Warnf(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warnf(ctx, msg, args...) } // Tracef writes formatted msg to default logger on trace level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Tracef(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Tracef(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Tracef(ctx, msg, args...) } // Fatalf writes formatted msg to default logger on fatal level +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Fatalf(ctx context.Context, msg string, args ...interface{}) { - DefaultLogger.Fatalf(ctx, msg, args...) + DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatalf(ctx, msg, args...) } // V returns true if passed level enabled in default logger +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func V(level Level) bool { return DefaultLogger.V(level) } // Init initialize logger +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Init(opts ...Option) error { return DefaultLogger.Init(opts...) } // Fields create logger with specific fields +// +// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations func Fields(fields ...interface{}) Logger { return DefaultLogger.Fields(fields...) } diff --git a/logger/options.go b/logger/options.go index ed95acb4..170ee10d 100644 --- a/logger/options.go +++ b/logger/options.go @@ -20,6 +20,7 @@ type Options struct { Name string // Fields holds additional metadata Fields []interface{} + // CallerSkipCount number of frmaes to skip CallerSkipCount int // ContextAttrFuncs contains funcs that executed before log func on context @@ -28,14 +29,18 @@ type Options struct { TimeKey string // LevelKey is the key used for the level of the log call LevelKey string + // ErroreKey is the key used for the error of the log call + ErrorKey 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 + // AddStacktrace controls writing of stacktaces on error + AddStacktrace bool + // AddSource enabled writing source file and position in log + AddSource bool // The logging level the logger should log Level Level } @@ -49,6 +54,7 @@ func NewOptions(opts ...Option) Options { CallerSkipCount: DefaultCallerSkipCount, Context: context.Background(), ContextAttrFuncs: DefaultContextAttrFuncs, + AddSource: true, } WithMicroKeys()(&options) @@ -56,6 +62,7 @@ func NewOptions(opts ...Option) Options { for _, o := range opts { o(&options) } + return options } @@ -87,10 +94,17 @@ func WithOutput(out io.Writer) Option { } } -// WithStacktrace controls writing stacktrace on error -func WithStacktrace(v bool) Option { +// WitAddStacktrace controls writing stacktrace on error +func WithAddStacktrace(v bool) Option { return func(o *Options) { - o.Stacktrace = v + o.AddStacktrace = v + } +} + +// WitAddSource controls writing source file and pos in log +func WithAddSource(v bool) Option { + return func(o *Options) { + o.AddSource = v } } @@ -122,6 +136,7 @@ func WithZapKeys() Option { o.MessageKey = "msg" o.SourceKey = "caller" o.StacktraceKey = "stacktrace" + o.ErrorKey = "error" } } @@ -132,6 +147,7 @@ func WithZerologKeys() Option { o.MessageKey = "message" o.SourceKey = "caller" o.StacktraceKey = "stacktrace" + o.ErrorKey = "error" } } @@ -142,6 +158,7 @@ func WithSlogKeys() Option { o.MessageKey = slog.MessageKey o.SourceKey = slog.SourceKey o.StacktraceKey = "stacktrace" + o.ErrorKey = "error" } } @@ -152,5 +169,6 @@ func WithMicroKeys() Option { o.MessageKey = "msg" o.SourceKey = "caller" o.StacktraceKey = "stacktrace" + o.ErrorKey = "error" } } diff --git a/logger/slog/slog.go b/logger/slog/slog.go index 672505ff..483b1bc2 100644 --- a/logger/slog/slog.go +++ b/logger/slog/slog.go @@ -82,9 +82,9 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger { l.leveler = new(slog.LevelVar) handleOpt := &slog.HandlerOptions{ - ReplaceAttr: s.renameAttr, + ReplaceAttr: l.renameAttr, Level: l.leveler, - AddSource: true, + AddSource: l.opts.AddSource, } l.leveler.Set(loggerToSlogLevel(l.opts.Level)) handler := slog.NewJSONHandler(options.Out, handleOpt) @@ -109,22 +109,22 @@ func (s *slogLogger) Options() logger.Options { func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger { s.mu.RLock() - nl := &slogLogger{opts: s.opts} - nl.leveler = new(slog.LevelVar) - nl.leveler.Set(s.leveler.Level()) + l := &slogLogger{opts: s.opts} + l.leveler = new(slog.LevelVar) + l.leveler.Set(s.leveler.Level()) handleOpt := &slog.HandlerOptions{ - ReplaceAttr: nl.renameAttr, - Level: nl.leveler, - AddSource: true, + ReplaceAttr: l.renameAttr, + Level: l.leveler, + AddSource: l.opts.AddSource, } handler := slog.NewJSONHandler(s.opts.Out, handleOpt) - nl.slog = slog.New(handler).With(attrs...) + l.slog = slog.New(handler).With(attrs...) s.mu.RUnlock() - return nl + return l } func (s *slogLogger) Init(opts ...logger.Option) error { @@ -142,7 +142,7 @@ func (s *slogLogger) Init(opts ...logger.Option) error { handleOpt := &slog.HandlerOptions{ ReplaceAttr: s.renameAttr, Level: s.leveler, - AddSource: true, + AddSource: s.opts.AddSource, } s.leveler.Set(loggerToSlogLevel(s.opts.Level)) handler := slog.NewJSONHandler(s.opts.Out, handleOpt) @@ -160,16 +160,35 @@ 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 { + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + if s.opts.AddStacktrace && 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])) + attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])) } } } - // r.Add(attrs[1:]...) + r.Add(attrs[1:]...) + r.Attrs(func(a slog.Attr) bool { + if a.Key == s.opts.ErrorKey { + if span, ok := tracer.SpanFromContext(ctx); ok { + span.SetStatus(tracer.SpanStatusError, a.Value.String()) + return false + } + } + return true + }) _ = s.slog.Handler().Handle(ctx, r) } @@ -179,17 +198,36 @@ 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 { + r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + if s.opts.AddStacktrace && 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])) + attrs = append(attrs, (slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))) } } } - // r.Add(attrs...) + r.Add(attrs[1:]...) + r.Attrs(func(a slog.Attr) bool { + if a.Key == s.opts.ErrorKey { + if span, ok := tracer.SpanFromContext(ctx); ok { + span.SetStatus(tracer.SpanStatusError, a.Value.String()) + return false + } + } + return true + }) _ = s.slog.Handler().Handle(ctx, r) } @@ -200,7 +238,17 @@ func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -210,8 +258,18 @@ func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{} } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) + r := slog.NewRecord(time.Now(), slog.LevelInfo, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs...) _ = s.slog.Handler().Handle(ctx, r) } @@ -222,7 +280,17 @@ func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -232,8 +300,18 @@ func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{ } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) + r := slog.NewRecord(time.Now(), slog.LevelDebug, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs...) _ = s.slog.Handler().Handle(ctx, r) } @@ -244,7 +322,17 @@ func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -254,8 +342,18 @@ func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{ } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) + r := slog.NewRecord(time.Now(), slog.LevelDebug-1, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -266,18 +364,28 @@ func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) - if s.opts.Stacktrace { + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + if s.opts.AddStacktrace { 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("stacktrace", traceLines[len(traceLines)-1])) + attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1])) } } } + r.Add(attrs[1:]...) r.Attrs(func(a slog.Attr) bool { - if a.Key == "error" { + if a.Key == s.opts.ErrorKey { if span, ok := tracer.SpanFromContext(ctx); ok { span.SetStatus(tracer.SpanStatusError, a.Value.String()) return false @@ -294,19 +402,29 @@ func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{ } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) - if s.opts.Stacktrace { + r := slog.NewRecord(time.Now(), slog.LevelError, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + if s.opts.AddStacktrace { 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("stacktrace", traceLines[len(traceLines)-1])) + attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1])) } } } + r.Add(attrs...) r.Attrs(func(a slog.Attr) bool { - if a.Key == "error" { + if a.Key == s.opts.ErrorKey { if span, ok := tracer.SpanFromContext(ctx); ok { span.SetStatus(tracer.SpanStatusError, a.Value.String()) return false @@ -324,7 +442,17 @@ func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) os.Exit(1) } @@ -335,8 +463,18 @@ func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{ } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) + r := slog.NewRecord(time.Now(), slog.LevelError+1, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs...) _ = s.slog.Handler().Handle(ctx, r) os.Exit(1) } @@ -348,7 +486,17 @@ func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) { var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0]) - // r.Add(attrs[1:]...) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } @@ -358,8 +506,18 @@ func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{} } var pcs [1]uintptr runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof] - r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf(msg, attrs...), pcs[0]) - // r.Add(attrs...) + r := slog.NewRecord(time.Now(), slog.LevelWarn, msg, pcs[0]) + for _, fn := range s.opts.ContextAttrFuncs { + attrs = append(attrs, fn(ctx)...) + } + + for idx, attr := range attrs { + if ve, ok := attr.(error); ok && ve != nil { + attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error()) + break + } + } + r.Add(attrs[1:]...) _ = s.slog.Handler().Handle(ctx, r) } diff --git a/logger/slog/slog_test.go b/logger/slog/slog_test.go index 62698e16..8844f175 100644 --- a/logger/slog/slog_test.go +++ b/logger/slog/slog_test.go @@ -3,6 +3,7 @@ package slog import ( "bytes" "context" + "fmt" "log" "testing" @@ -17,10 +18,30 @@ func TestError(t *testing.T) { t.Fatal(err) } - l.Error(ctx, "message") + l.Error(ctx, "message", fmt.Errorf("error message")) if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) } + if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) { + t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) + } +} + +func TestErrorf(t *testing.T) { + ctx := context.TODO() + buf := bytes.NewBuffer(nil) + l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithStacktrace(true)) + if err := l.Init(); err != nil { + t.Fatal(err) + } + + l.Errorf(ctx, "message", fmt.Errorf("error message")) + if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { + t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) + } + if !bytes.Contains(buf.Bytes(), []byte(`"error":"`)) { + t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) + } } func TestContext(t *testing.T) { diff --git a/tracer/memory/memory.go b/tracer/memory/memory.go new file mode 100644 index 00000000..4216d863 --- /dev/null +++ b/tracer/memory/memory.go @@ -0,0 +1,139 @@ +package memory + +import ( + "context" + "time" + + "go.unistack.org/micro/v3/tracer" + "go.unistack.org/micro/v3/util/id" +) + +var _ tracer.Tracer = (*Tracer)(nil) + +type Tracer struct { + opts tracer.Options + spans []tracer.Span +} + +func (t *Tracer) Spans() []tracer.Span { + return t.spans +} + +func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOption) (context.Context, tracer.Span) { + options := tracer.NewSpanOptions(opts...) + span := &Span{ + name: name, + ctx: ctx, + tracer: t, + kind: options.Kind, + startTime: time.Now(), + } + span.spanID.s, _ = id.New() + span.traceID.s, _ = id.New() + if span.ctx == nil { + span.ctx = context.Background() + } + t.spans = append(t.spans, span) + return tracer.NewSpanContext(ctx, span), span +} + +func (t *Tracer) Flush(_ context.Context) error { + return nil +} + +func (t *Tracer) Init(opts ...tracer.Option) error { + for _, o := range opts { + o(&t.opts) + } + return nil +} + +func (t *Tracer) Name() string { + return t.opts.Name +} + +type noopStringer struct { + s string +} + +func (s noopStringer) String() string { + return s.s +} + +type Span struct { + ctx context.Context + tracer tracer.Tracer + name string + statusMsg string + startTime time.Time + finishTime time.Time + traceID noopStringer + spanID noopStringer + events []*Event + labels []interface{} + logs []interface{} + kind tracer.SpanKind + status tracer.SpanStatus +} + +func (s *Span) Finish(_ ...tracer.SpanOption) { + s.finishTime = time.Now() +} + +func (s *Span) Context() context.Context { + return s.ctx +} + +func (s *Span) Tracer() tracer.Tracer { + return s.tracer +} + +type Event struct { + name string + labels []interface{} +} + +func (s *Span) AddEvent(name string, opts ...tracer.EventOption) { + options := tracer.NewEventOptions(opts...) + s.events = append(s.events, &Event{name: name, labels: options.Labels}) +} + +func (s *Span) SetName(name string) { + s.name = name +} + +func (s *Span) AddLogs(kv ...interface{}) { + s.logs = append(s.logs, kv...) +} + +func (s *Span) AddLabels(kv ...interface{}) { + s.labels = append(s.labels, kv...) +} + +func (s *Span) Kind() tracer.SpanKind { + return s.kind +} + +func (s *Span) TraceID() string { + return s.traceID.String() +} + +func (s *Span) SpanID() string { + return s.spanID.String() +} + +func (s *Span) Status() (tracer.SpanStatus, string) { + return s.status, s.statusMsg +} + +func (s *Span) SetStatus(st tracer.SpanStatus, msg string) { + s.status = st + s.statusMsg = msg +} + +// NewTracer returns new memory tracer +func NewTracer(opts ...tracer.Option) *Tracer { + return &Tracer{ + opts: tracer.NewOptions(opts...), + } +} diff --git a/tracer/memory/memory_test.go b/tracer/memory/memory_test.go new file mode 100644 index 00000000..7a6dddd6 --- /dev/null +++ b/tracer/memory/memory_test.go @@ -0,0 +1,38 @@ +package memory + +import ( + "bytes" + "context" + "fmt" + "strings" + "testing" + + "go.unistack.org/micro/v3/logger" + "go.unistack.org/micro/v3/logger/slog" + "go.unistack.org/micro/v3/tracer" +) + +func TestLoggerWithTracer(t *testing.T) { + ctx := context.TODO() + buf := bytes.NewBuffer(nil) + logger.DefaultLogger = slog.NewLogger(logger.WithOutput(buf)) + + if err := logger.Init(); err != nil { + t.Fatal(err) + } + var span tracer.Span + tr := NewTracer() + ctx, span = tr.Start(ctx, "test1") + + logger.Error(ctx, "my test error", fmt.Errorf("error")) + + if !strings.Contains(buf.String(), span.TraceID()) { + t.Fatalf("log does not contains trace id: %s", buf.Bytes()) + } + + _, _ = tr.Start(ctx, "test2") + + for _, s := range tr.Spans() { + _ = s + } +} diff --git a/tracer/noop.go b/tracer/noop.go index 8548dbe8..de4756cc 100644 --- a/tracer/noop.go +++ b/tracer/noop.go @@ -2,6 +2,8 @@ package tracer import ( "context" + + "go.unistack.org/micro/v3/util/id" ) var _ Tracer = (*noopTracer)(nil) @@ -24,6 +26,8 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) labels: options.Labels, kind: options.Kind, } + span.spanID.s, _ = id.New() + span.traceID.s, _ = id.New() if span.ctx == nil { span.ctx = context.Background() } @@ -31,6 +35,14 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) return NewSpanContext(ctx, span), span } +type noopStringer struct { + s string +} + +func (s noopStringer) String() string { + return s.s +} + func (t *noopTracer) Flush(ctx context.Context) error { return nil } @@ -56,6 +68,8 @@ type noopSpan struct { tracer Tracer name string statusMsg string + traceID noopStringer + spanID noopStringer events []*noopEvent labels []interface{} logs []interface{} @@ -63,7 +77,15 @@ type noopSpan struct { status SpanStatus } -func (s *noopSpan) Finish(opts ...SpanOption) { +func (s *noopSpan) TraceID() string { + return s.traceID.String() +} + +func (s *noopSpan) SpanID() string { + return s.spanID.String() +} + +func (s *noopSpan) Finish(_ ...SpanOption) { } func (s *noopSpan) Context() context.Context { diff --git a/tracer/tracer.go b/tracer/tracer.go index fffd1cf4..46c72c41 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -5,11 +5,33 @@ import ( "context" "fmt" "sort" + + "go.unistack.org/micro/v3/logger" ) // DefaultTracer is the global default tracer var DefaultTracer = NewTracer() +var ( + // TraceIDKey is the key used for the trace id in the log call + TraceIDKey = "trace-id" + // SpanIDKey is the key used for the span id in the log call + SpanIDKey = "span-id" +) + +func init() { + logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, + func(ctx context.Context) []interface{} { + if span, ok := SpanFromContext(ctx); ok { + return []interface{}{ + TraceIDKey, span.TraceID(), + SpanIDKey, span.SpanID(), + } + } + return nil + }) +} + // Tracer is an interface for distributed tracing type Tracer interface { // Name return tracer name @@ -43,6 +65,10 @@ type Span interface { AddLogs(kv ...interface{}) // Kind returns span kind Kind() SpanKind + // TraceID returns trace id + TraceID() string + // SpanID returns span id + SpanID() string } // sort labels alphabeticaly by label name