logger/slog: add stacktrace support
Some checks failed
/ autoupdate (push) Failing after 1m22s

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2024-03-04 23:42:15 +03:00
parent 1cbc353479
commit f28f8e13b3
4 changed files with 60 additions and 2 deletions

View File

@ -48,8 +48,10 @@ type Logger interface {
Fatal(ctx context.Context, msg string, attrs ...interface{})
// Log logs message with needed level
Log(ctx context.Context, level Level, msg string, attrs ...interface{})
// String returns the name of logger
// String returns the type name of logger
String() string
// String returns the name of logger
Name() string
}
// Info writes formatted msg to default logger on info level

View File

@ -35,6 +35,10 @@ type Options struct {
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
}
// NewOptions creates new options struct
@ -116,6 +120,9 @@ func WithZapKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
return nil
}
}
@ -135,6 +142,9 @@ func WithZerologKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
return nil
}
}
@ -154,6 +164,9 @@ func WithSlogKeys() options.Option {
if err = options.Set(src, slog.SourceKey, ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
return nil
}
}
@ -173,6 +186,16 @@ func WithMicroKeys() options.Option {
if err = options.Set(src, "caller", ".SourceKey"); err != nil {
return err
}
if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil {
return err
}
return nil
}
}
// WithStacktrace controls writing stacktrace on error
func WithStacktrace(v bool) options.Option {
return func(src interface{}) error {
return options.Set(src, v, ".Stacktrace")
}
}

View File

@ -4,8 +4,10 @@ import (
"context"
"log/slog"
"os"
"regexp"
"runtime"
"strconv"
"sync"
"time"
"go.unistack.org/micro/v4/logger"
@ -13,6 +15,8 @@ import (
"go.unistack.org/micro/v4/tracer"
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
var (
traceValue = slog.StringValue("trace")
debugValue = slog.StringValue("debug")
@ -61,13 +65,15 @@ type slogLogger struct {
slog *slog.Logger
leveler *slog.LevelVar
opts logger.Options
mu sync.RWMutex
}
func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
s.mu.RLock()
options := s.opts
for _, o := range opts {
o(&options)
_ = o(&options)
}
l := &slogLogger{
@ -91,6 +97,8 @@ func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
handler := slog.NewJSONHandler(options.Out, handleOpt)
l.slog = slog.New(handler).With(options.Attrs...)
s.mu.RUnlock()
return l
}
@ -107,6 +115,7 @@ func (s *slogLogger) Options() logger.Options {
}
func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
s.mu.RLock()
nl := &slogLogger{opts: s.opts}
nl.leveler = new(slog.LevelVar)
nl.leveler.Set(s.leveler.Level())
@ -120,6 +129,8 @@ func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
nl.slog = slog.New(handler).With(attrs...)
s.mu.RUnlock()
return nl
}
@ -219,6 +230,15 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
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 {
attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == "error" {

View File

@ -9,6 +9,19 @@ import (
"go.unistack.org/micro/v4/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)