Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
		| @@ -48,8 +48,10 @@ type Logger interface { | |||||||
| 	Fatal(ctx context.Context, msg string, attrs ...interface{}) | 	Fatal(ctx context.Context, msg string, attrs ...interface{}) | ||||||
| 	// Log logs message with needed level | 	// Log logs message with needed level | ||||||
| 	Log(ctx context.Context, level Level, msg string, attrs ...interface{}) | 	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() string | ||||||
|  | 	// String returns the name of logger | ||||||
|  | 	Name() string | ||||||
| } | } | ||||||
|  |  | ||||||
| // Info writes formatted msg to default logger on info level | // Info writes formatted msg to default logger on info level | ||||||
|   | |||||||
| @@ -35,6 +35,10 @@ type Options struct { | |||||||
| 	MessageKey string | 	MessageKey string | ||||||
| 	// SourceKey is the key used for the source file and line of the log call | 	// SourceKey is the key used for the source file and line of the log call | ||||||
| 	SourceKey string | 	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 | // NewOptions creates new options struct | ||||||
| @@ -116,6 +120,9 @@ func WithZapKeys() options.Option { | |||||||
| 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 		if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -135,6 +142,9 @@ func WithZerologKeys() options.Option { | |||||||
| 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 		if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -154,6 +164,9 @@ func WithSlogKeys() options.Option { | |||||||
| 		if err = options.Set(src, slog.SourceKey, ".SourceKey"); err != nil { | 		if err = options.Set(src, slog.SourceKey, ".SourceKey"); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 		if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -173,6 +186,16 @@ func WithMicroKeys() options.Option { | |||||||
| 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | 		if err = options.Set(src, "caller", ".SourceKey"); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | 		if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 		return nil | 		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") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,8 +4,10 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"regexp" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"go.unistack.org/micro/v4/logger" | 	"go.unistack.org/micro/v4/logger" | ||||||
| @@ -13,6 +15,8 @@ import ( | |||||||
| 	"go.unistack.org/micro/v4/tracer" | 	"go.unistack.org/micro/v4/tracer" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	traceValue = slog.StringValue("trace") | 	traceValue = slog.StringValue("trace") | ||||||
| 	debugValue = slog.StringValue("debug") | 	debugValue = slog.StringValue("debug") | ||||||
| @@ -61,13 +65,15 @@ type slogLogger struct { | |||||||
| 	slog    *slog.Logger | 	slog    *slog.Logger | ||||||
| 	leveler *slog.LevelVar | 	leveler *slog.LevelVar | ||||||
| 	opts    logger.Options | 	opts    logger.Options | ||||||
|  | 	mu      sync.RWMutex | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *slogLogger) Clone(opts ...options.Option) logger.Logger { | func (s *slogLogger) Clone(opts ...options.Option) logger.Logger { | ||||||
|  | 	s.mu.RLock() | ||||||
| 	options := s.opts | 	options := s.opts | ||||||
|  |  | ||||||
| 	for _, o := range opts { | 	for _, o := range opts { | ||||||
| 		o(&options) | 		_ = o(&options) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	l := &slogLogger{ | 	l := &slogLogger{ | ||||||
| @@ -91,6 +97,8 @@ func (s *slogLogger) Clone(opts ...options.Option) logger.Logger { | |||||||
| 	handler := slog.NewJSONHandler(options.Out, handleOpt) | 	handler := slog.NewJSONHandler(options.Out, handleOpt) | ||||||
| 	l.slog = slog.New(handler).With(options.Attrs...) | 	l.slog = slog.New(handler).With(options.Attrs...) | ||||||
|  |  | ||||||
|  | 	s.mu.RUnlock() | ||||||
|  |  | ||||||
| 	return l | 	return l | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -107,6 +115,7 @@ func (s *slogLogger) Options() logger.Options { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger { | func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger { | ||||||
|  | 	s.mu.RLock() | ||||||
| 	nl := &slogLogger{opts: s.opts} | 	nl := &slogLogger{opts: s.opts} | ||||||
| 	nl.leveler = new(slog.LevelVar) | 	nl.leveler = new(slog.LevelVar) | ||||||
| 	nl.leveler.Set(s.leveler.Level()) | 	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) | 	handler := slog.NewJSONHandler(s.opts.Out, handleOpt) | ||||||
| 	nl.slog = slog.New(handler).With(attrs...) | 	nl.slog = slog.New(handler).With(attrs...) | ||||||
|  |  | ||||||
|  | 	s.mu.RUnlock() | ||||||
|  |  | ||||||
| 	return nl | 	return nl | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -219,6 +230,15 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{} | |||||||
| 	for _, fn := range s.opts.ContextAttrFuncs { | 	for _, fn := range s.opts.ContextAttrFuncs { | ||||||
| 		attrs = append(attrs, fn(ctx)...) | 		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.Add(attrs...) | ||||||
| 	r.Attrs(func(a slog.Attr) bool { | 	r.Attrs(func(a slog.Attr) bool { | ||||||
| 		if a.Key == "error" { | 		if a.Key == "error" { | ||||||
|   | |||||||
| @@ -9,6 +9,19 @@ import ( | |||||||
| 	"go.unistack.org/micro/v4/logger" | 	"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) { | func TestContext(t *testing.T) { | ||||||
| 	ctx := context.TODO() | 	ctx := context.TODO() | ||||||
| 	buf := bytes.NewBuffer(nil) | 	buf := bytes.NewBuffer(nil) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user