-
Notifications
You must be signed in to change notification settings - Fork 444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
contrib/log/slog: fix WithAttrs and WithGroup implementation #2857
Changes from 3 commits
e0c4515
e2adad9
3f4869f
f048d84
3f29fd3
90e4af3
221dcff
7975c07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import ( | |
"context" | ||
"io" | ||
"log/slog" | ||
"strconv" | ||
|
||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" | ||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" | ||
|
@@ -23,6 +24,8 @@ func init() { | |
tracer.MarkIntegrationImported("log/slog") | ||
} | ||
|
||
var _ slog.Handler = (*handler)(nil) | ||
|
||
// NewJSONHandler is a convenience function that returns a *slog.JSONHandler logger enhanced with | ||
// tracing information. | ||
func NewJSONHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler { | ||
|
@@ -31,21 +34,83 @@ func NewJSONHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler { | |
|
||
// WrapHandler enhances the given logger handler attaching tracing information to logs. | ||
func WrapHandler(h slog.Handler) slog.Handler { | ||
return &handler{h} | ||
return &handler{wrapped: h} | ||
} | ||
|
||
type handler struct { | ||
slog.Handler | ||
wrapped slog.Handler | ||
groups []string | ||
groupAttrs map[string][]slog.Attr | ||
} | ||
|
||
// Enabled calls the wrapped handler Enabled method. | ||
func (h *handler) Enabled(ctx context.Context, level slog.Level) bool { | ||
return h.wrapped.Enabled(ctx, level) | ||
} | ||
|
||
// Handle handles the given Record, attaching tracing information if found. | ||
func (h *handler) Handle(ctx context.Context, rec slog.Record) error { | ||
reqHandler := h.wrapped | ||
|
||
// We need to ensure the trace id and span id keys are set at the root level: | ||
// https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/ | ||
// In case the user has created group loggers, we ignore those and | ||
// set them at the root level. | ||
span, ok := tracer.SpanFromContext(ctx) | ||
if ok { | ||
rec.Add( | ||
slog.Uint64(ext.LogKeyTraceID, span.Context().TraceID()), | ||
slog.Uint64(ext.LogKeySpanID, span.Context().SpanID()), | ||
) | ||
traceID := strconv.FormatUint(span.Context().TraceID(), 10) | ||
spanID := strconv.FormatUint(span.Context().SpanID(), 10) | ||
|
||
attrs := []slog.Attr{ | ||
slog.String(ext.LogKeyTraceID, traceID), | ||
slog.String(ext.LogKeySpanID, spanID), | ||
} | ||
reqHandler = reqHandler.WithAttrs(attrs) | ||
} | ||
for _, group := range h.groups { | ||
reqHandler = reqHandler.WithGroup(group) | ||
if attrs, ok := h.groupAttrs[group]; ok { | ||
reqHandler = reqHandler.WithAttrs(attrs) | ||
} | ||
} | ||
return reqHandler.Handle(ctx, rec) | ||
} | ||
|
||
// WithAttrs saves the provided attributes associated to the current Group. | ||
// If Group was not called for the logger, we just call WithAttrs for the wrapped handler. | ||
func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
if len(h.groups) == 0 { | ||
return &handler{ | ||
wrapped: h.wrapped.WithAttrs(attrs), | ||
groupAttrs: h.groupAttrs, | ||
groups: h.groups, | ||
} | ||
} | ||
curGroup := h.groups[len(h.groups)-1] | ||
|
||
groupAttrs := groupAttrsCopy(h.groupAttrs) | ||
groupAttrs[curGroup] = append(groupAttrs[curGroup], attrs...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is possible that the same group name appears multiple times. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shota3506 you are right, nice catch! 👍 |
||
|
||
return &handler{ | ||
wrapped: h.wrapped, | ||
groupAttrs: groupAttrs, | ||
groups: h.groups, | ||
} | ||
} | ||
|
||
// WithGroup saves the provided group to be used later in the Handle method. | ||
func (h *handler) WithGroup(name string) slog.Handler { | ||
return &handler{ | ||
wrapped: h.wrapped, | ||
groupAttrs: h.groupAttrs, | ||
groups: append(h.groups, name), | ||
} | ||
} | ||
|
||
func groupAttrsCopy(m map[string][]slog.Attr) map[string][]slog.Attr { | ||
cp := make(map[string][]slog.Attr) | ||
for k, v := range m { | ||
cp[k] = append([]slog.Attr{}, v...) | ||
} | ||
return h.Handler.Handle(ctx, rec) | ||
return cp | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So everytime
Handle
is invoked, we have to copy over the existing attributes + the new trace attributes, as well as the existing groups?Is this a performance concern? Is
Handle
called often?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is definitely gonna have a performance impact. Not sure about
slog
internals, but I would assume this is called every time a log entry is created (so depends on the application). Sadly, I don't see another way to solve the.Group
issue without the performance impact.However this is a nice call, I will write a benchmark to measure how worse this is 👍