From 4c7e1607d4ce0a1c3b15d9b76f1befec718dac1d Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Mon, 4 Mar 2024 22:54:11 +0300 Subject: [PATCH] logger/slog: add stacktrace support Signed-off-by: Vasiliy Tolstov --- logger/slog/slog.go | 21 +++++++++++++++++++++ logger/slog/slog_test.go | 14 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/logger/slog/slog.go b/logger/slog/slog.go index b2b756c3..bb569075 100644 --- a/logger/slog/slog.go +++ b/logger/slog/slog.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "os" + "regexp" "runtime" "strconv" "sync" @@ -14,6 +15,8 @@ import ( "go.unistack.org/micro/v3/tracer" ) +var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`) + var ( DefaultSourceKey string = slog.SourceKey DefaultTimeKey string = slog.TimeKey @@ -290,6 +293,15 @@ func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) { 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 { + 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])) + } + } + } r.Attrs(func(a slog.Attr) bool { if a.Key == "error" { if span, ok := tracer.SpanFromContext(ctx); ok { @@ -310,6 +322,15 @@ func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{ 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 { + 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])) + } + } + } r.Attrs(func(a slog.Attr) bool { if a.Key == "error" { if span, ok := tracer.SpanFromContext(ctx); ok { diff --git a/logger/slog/slog_test.go b/logger/slog/slog_test.go index a6846c6f..62698e16 100644 --- a/logger/slog/slog_test.go +++ b/logger/slog/slog_test.go @@ -9,6 +9,20 @@ import ( "go.unistack.org/micro/v3/logger" ) +func TestError(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.Error(ctx, "message") + if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { + t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) + } +} + func TestContext(t *testing.T) { ctx := context.TODO() buf := bytes.NewBuffer(nil)