master
go 151 lines 3.52 KB
Raw
1 package logger
2
3 import (
4 "context"
5 "io"
6 "log/slog"
7 "runtime"
8 "strings"
9
10 "github.com/lmittmann/tint"
11 )
12
13 func newTextHandler(w io.Writer) slog.Handler {
14 return slog.NewTextHandler(w, &slog.HandlerOptions{
15 Level: Level.lvl,
16 ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
17 switch a.Key {
18 case slog.TimeKey:
19 if isJournal {
20 return slog.Attr{}
21 }
22 case slog.LevelKey:
23 lvl := a.Value.Any().(slog.Level)
24 s, ok := customLevels[lvl]
25 if !ok {
26 s = lvl.String()
27 }
28 return slog.String(a.Key, strings.ToLower(s))
29 }
30 return a
31 },
32 })
33 }
34
35 func newTerminalHandler(w io.Writer) slog.Handler {
36 return tint.NewHandler(w, &tint.Options{
37 NoColor: runtime.GOOS == "windows",
38 AddSource: true,
39 Level: Level.lvl,
40 ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
41 switch a.Key {
42 case slog.TimeKey:
43 return slog.Attr{}
44 case slog.SourceKey:
45 if !Level.Enabled(slog.LevelDebug) {
46 return slog.Attr{}
47 }
48 case slog.LevelKey:
49 lvl := a.Value.Any().(slog.Level)
50 if s, ok := customLevelsTerm[lvl]; ok {
51 return slog.String(a.Key, s)
52 }
53 }
54 return a
55 },
56 })
57 }
58
59 func withCallDepth(depth int, sh slog.Handler) slog.Handler {
60 if v, ok := sh.(*callDepthHandler); ok {
61 sh = v.sh
62 return &callDepthHandler{depth: depth, isTerminal: v.isTerminal, sh: sh}
63 }
64 return &callDepthHandler{depth: depth, sh: sh}
65 }
66
67 func withTerminalCallDepth(depth int, sh slog.Handler) slog.Handler {
68 if v, ok := sh.(*callDepthHandler); ok {
69 sh = v.sh
70 }
71 return &callDepthHandler{depth: depth, isTerminal: true, sh: sh}
72 }
73
74 type callDepthHandler struct {
75 depth int
76 isTerminal bool
77 sh slog.Handler
78 }
79
80 func (h *callDepthHandler) Enabled(ctx context.Context, level slog.Level) bool {
81 return h.sh.Enabled(ctx, level)
82 }
83
84 func (h *callDepthHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
85 if h.isTerminal {
86 return withTerminalCallDepth(h.depth, h.sh.WithAttrs(attrs))
87 }
88 return withCallDepth(h.depth, h.sh.WithAttrs(attrs))
89 }
90
91 func (h *callDepthHandler) WithGroup(name string) slog.Handler {
92 if h.isTerminal {
93 return withTerminalCallDepth(h.depth, h.sh.WithGroup(name))
94 }
95 return withCallDepth(h.depth, h.sh.WithGroup(name))
96 }
97
98 func (h *callDepthHandler) Handle(ctx context.Context, r slog.Record) error {
99 if h.isTerminal && Level.Enabled(slog.LevelDebug) {
100 // Keep fixed-skip math identical to the non-dynamic path.
101 // resolveCallerPC will adjust for its own extra stack frame on fallback.
102 r.PC = resolveCallerPC(h.depth + 2)
103 return h.sh.Handle(ctx, r)
104 }
105
106 // https://pkg.go.dev/log/slog#example-package-Wrapping
107 var pcs [1]uintptr
108 // skip Callers and this function
109 runtime.Callers(h.depth+2, pcs[:])
110 r.PC = pcs[0]
111
112 return h.sh.Handle(ctx, r)
113 }
114
115 var callerSkipPrefixes = []string{
116 "github.com/netdata/netdata/go/plugins/logger.",
117 "log/slog.",
118 "runtime.",
119 }
120
121 func resolveCallerPC(fixedSkip int) uintptr {
122 var pcs [15]uintptr
123 n := runtime.Callers(2, pcs[:]) // skip runtime.Callers + resolveCallerPC
124 if n > 0 {
125 frames := runtime.CallersFrames(pcs[:n])
126 for {
127 frame, more := frames.Next()
128 if !isSkippedCaller(frame.Function) {
129 return frame.PC
130 }
131 if !more {
132 break
133 }
134 }
135 }
136
137 var fallback [1]uintptr
138 // fixedSkip is the skip value used by Handle's fixed path.
139 // Add one extra frame to account for resolveCallerPC itself.
140 runtime.Callers(fixedSkip+1, fallback[:])
141 return fallback[0]
142 }
143
144 func isSkippedCaller(function string) bool {
145 for _, prefix := range callerSkipPrefixes {
146 if strings.HasPrefix(function, prefix) {
147 return true
148 }
149 }
150 return false
151 }