From 1cbab38d249318fb6f0ccb51004e5bca652c06e1 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Tue, 5 Mar 2024 01:27:22 +0300 Subject: [PATCH] tracer and logger improvements Signed-off-by: Vasiliy Tolstov --- logger/options.go | 18 ++++- logger/slog/slog.go | 23 +++++- logger/slog/slog_test.go | 3 +- tracer/memory/memory.go | 143 +++++++++++++++++++++++++++++++++++ tracer/memory/memory_test.go | 38 ++++++++++ tracer/noop.go | 23 ++---- 6 files changed, 226 insertions(+), 22 deletions(-) create mode 100644 tracer/memory/memory.go create mode 100644 tracer/memory/memory_test.go diff --git a/logger/options.go b/logger/options.go index 02f586e2..2458af8d 100644 --- a/logger/options.go +++ b/logger/options.go @@ -33,6 +33,8 @@ type Options struct { LevelKey string // MessageKey is the key used for the message of the log call MessageKey string + // ErrorKey is the key used for the error info + ErrorKey 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 @@ -52,10 +54,10 @@ func NewOptions(opts ...options.Option) Options { ContextAttrFuncs: DefaultContextAttrFuncs, } - WithMicroKeys()(&options) + _ = WithMicroKeys()(&options) for _, o := range opts { - o(&options) + _ = o(&options) } return options } @@ -123,6 +125,9 @@ func WithZapKeys() options.Option { if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { return err } + if err = options.Set(src, "error", ".ErrorKey"); err != nil { + return err + } return nil } } @@ -145,6 +150,9 @@ func WithZerologKeys() options.Option { if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { return err } + if err = options.Set(src, "error", ".ErrorKey"); err != nil { + return err + } return nil } } @@ -167,6 +175,9 @@ func WithSlogKeys() options.Option { if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { return err } + if err = options.Set(src, "error", ".ErrorKey"); err != nil { + return err + } return nil } } @@ -189,6 +200,9 @@ func WithMicroKeys() options.Option { if err = options.Set(src, "stacktrace", ".StacktraceKey"); err != nil { return err } + if err = options.Set(src, "error", ".ErrorKey"); err != nil { + return err + } return nil } } diff --git a/logger/slog/slog.go b/logger/slog/slog.go index f3413eb3..34eae039 100644 --- a/logger/slog/slog.go +++ b/logger/slog/slog.go @@ -174,6 +174,12 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attr for _, fn := range s.opts.ContextAttrFuncs { attrs = append(attrs, fn(ctx)...) } + for _, a := range attrs { + if ve, ok := a.(error); ok && ve != nil { + attrs = append(attrs, slog.String(s.opts.ErrorKey, ve.Error())) + break + } + } if s.opts.Stacktrace && lvl == logger.ErrorLevel { stackInfo := make([]byte, 1024*1024) if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { @@ -184,6 +190,15 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attr } } r.Add(attrs...) + 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) } @@ -239,6 +254,12 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{} for _, fn := range s.opts.ContextAttrFuncs { attrs = append(attrs, fn(ctx)...) } + for _, a := range attrs { + if ve, ok := a.(error); ok && ve != nil { + attrs = append(attrs, slog.String(s.opts.ErrorKey, ve.Error())) + break + } + } if s.opts.Stacktrace { stackInfo := make([]byte, 1024*1024) if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { @@ -250,7 +271,7 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{} } 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 diff --git a/logger/slog/slog_test.go b/logger/slog/slog_test.go index 3455a68c..38a9cdda 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" @@ -16,7 +17,7 @@ func TestError(t *testing.T) { if err := l.Init(); err != nil { t.Fatal(err) } - l.Error(ctx, "message") + l.Error(ctx, "msg", fmt.Errorf("message")) if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) { t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes()) } diff --git a/tracer/memory/memory.go b/tracer/memory/memory.go new file mode 100644 index 00000000..c98e7413 --- /dev/null +++ b/tracer/memory/memory.go @@ -0,0 +1,143 @@ +package memory + +import ( + "context" + "time" + + "go.unistack.org/micro/v4/options" + "go.unistack.org/micro/v4/tracer" + "go.unistack.org/micro/v4/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 ...options.Option) (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 ...options.Option) error { + var err error + for _, o := range opts { + if err = o(&t.opts); err != nil { + return err + } + } + 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(opts ...options.Option) { + 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 ...options.Option) { + 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 ...options.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..f681d166 --- /dev/null +++ b/tracer/memory/memory_test.go @@ -0,0 +1,38 @@ +package memory + +import ( + "bytes" + "context" + "fmt" + "strings" + "testing" + + "go.unistack.org/micro/v4/logger" + "go.unistack.org/micro/v4/logger/slog" + "go.unistack.org/micro/v4/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 tracer id: %s", buf.Bytes()) + } + + _, _ = tr.Start(ctx, "test2") + + for _, s := range tr.Spans() { + t.Logf("span %#+v\n", s) + } +} diff --git a/tracer/noop.go b/tracer/noop.go index c2b91c66..d9655177 100644 --- a/tracer/noop.go +++ b/tracer/noop.go @@ -24,7 +24,6 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...options.Opt name: name, ctx: ctx, tracer: t, - labels: options.Labels, kind: options.Kind, } span.spanID.s, _ = id.New() @@ -36,7 +35,7 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...options.Opt return NewSpanContext(ctx, span), span } -func (t *noopTracer) Flush(ctx context.Context) error { +func (t *noopTracer) Flush(_ context.Context) error { return nil } @@ -51,11 +50,6 @@ func (t *noopTracer) Name() string { return t.opts.Name } -type noopEvent struct { - name string - labels []interface{} -} - type noopStringer struct { s string } @@ -71,14 +65,11 @@ type noopSpan struct { statusMsg string traceID noopStringer spanID noopStringer - events []*noopEvent - labels []interface{} - logs []interface{} kind SpanKind status SpanStatus } -func (s *noopSpan) Finish(opts ...options.Option) { +func (s *noopSpan) Finish(_ ...options.Option) { } func (s *noopSpan) Context() context.Context { @@ -89,21 +80,17 @@ func (s *noopSpan) Tracer() Tracer { return s.tracer } -func (s *noopSpan) AddEvent(name string, opts ...options.Option) { - options := NewEventOptions(opts...) - s.events = append(s.events, &noopEvent{name: name, labels: options.Labels}) +func (s *noopSpan) AddEvent(_ string, _ ...options.Option) { } func (s *noopSpan) SetName(name string) { s.name = name } -func (s *noopSpan) AddLogs(kv ...interface{}) { - s.logs = append(s.logs, kv...) +func (s *noopSpan) AddLogs(_ ...interface{}) { } -func (s *noopSpan) AddLabels(kv ...interface{}) { - s.labels = append(s.labels, kv...) +func (s *noopSpan) AddLabels(_ ...interface{}) { } func (s *noopSpan) Kind() SpanKind {