-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathslog.go
More file actions
198 lines (167 loc) · 4.7 KB
/
slog.go
File metadata and controls
198 lines (167 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package lgr
import (
"context"
"fmt"
"log/slog"
"os"
"runtime"
"strings"
"time"
)
// ToSlogHandler converts lgr.L to slog.Handler
func ToSlogHandler(l L) slog.Handler {
return &lgrSlogHandler{lgr: l}
}
// FromSlogHandler creates lgr.L wrapper around slog.Handler
func FromSlogHandler(h slog.Handler) L {
return &slogLgrAdapter{handler: h}
}
// SetupWithSlog sets up the global logger with a slog logger
func SetupWithSlog(logger *slog.Logger) {
options := []Option{SlogHandler(logger.Handler())}
// check if the slog handler is enabled for debug level
// if so, enable debug mode in lgr to prevent filtering
if logger.Handler().Enabled(context.Background(), slog.LevelDebug) {
options = append(options, Debug)
}
Setup(options...)
}
// lgrSlogHandler implements slog.Handler using lgr.L
type lgrSlogHandler struct {
lgr L
attrs []slog.Attr
groups []string
}
// Enabled implements slog.Handler
func (h *lgrSlogHandler) Enabled(_ context.Context, level slog.Level) bool {
switch {
case level < slog.LevelInfo: // debug, Trace
// check if underlying lgr logger is configured to show debug
// since we can't directly query lgr's debug status, we assume enabled
return true
default:
return true
}
}
// Handle implements slog.Handler
func (h *lgrSlogHandler) Handle(_ context.Context, record slog.Record) error {
level := levelToString(record.Level)
// build message with attributes
msg := record.Message
// format attributes as key=value pairs
var attrs strings.Builder
if len(h.attrs) > 0 || record.NumAttrs() > 0 {
attrs.WriteString(" ")
}
// add pre-defined attributes
for _, attr := range h.attrs {
attrs.WriteString(formatAttr(attr, h.groups))
}
// add record attributes
record.Attrs(func(attr slog.Attr) bool {
attrs.WriteString(formatAttr(attr, h.groups))
return true
})
// combine level prefix and message; lgr.Logf adds its own timestamp and level formatting
logMsg := fmt.Sprintf("%s %s%s", level, msg, attrs.String())
h.lgr.Logf(logMsg)
return nil
}
// WithAttrs implements slog.Handler
func (h *lgrSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newHandler := &lgrSlogHandler{
lgr: h.lgr,
attrs: append(h.attrs, attrs...),
groups: h.groups,
}
return newHandler
}
// WithGroup implements slog.Handler
func (h *lgrSlogHandler) WithGroup(name string) slog.Handler {
newHandler := &lgrSlogHandler{
lgr: h.lgr,
attrs: h.attrs,
groups: append(h.groups, name),
}
return newHandler
}
// slogLgrAdapter implements lgr.L using slog.Handler
type slogLgrAdapter struct {
handler slog.Handler
}
// Logf implements lgr.L interface
func (a *slogLgrAdapter) Logf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
level, msg := extractLevel(msg)
// get the caller's PC so slog handlers can resolve source info when AddSource is enabled
var pcs [1]uintptr
runtime.Callers(2, pcs[:]) // skip runtime.Callers and Logf
record := slog.NewRecord(time.Now(), stringToLevel(level), msg, pcs[0])
if err := a.handler.Handle(context.Background(), record); err != nil {
fmt.Fprintf(os.Stderr, "slog handler error: %v\n", err)
}
}
// Helper functions
// levelToString converts slog.Level to string representation used by lgr
func levelToString(level slog.Level) string {
switch {
case level < slog.LevelInfo:
if level <= slog.LevelDebug-4 {
return "TRACE"
}
return "DEBUG"
case level < slog.LevelWarn:
return "INFO"
case level < slog.LevelError:
return "WARN"
default:
return "ERROR"
}
}
// stringToLevel converts lgr level string to slog.Level
func stringToLevel(level string) slog.Level {
switch level {
case "TRACE":
return slog.LevelDebug - 4
case "DEBUG":
return slog.LevelDebug
case "INFO":
return slog.LevelInfo
case "WARN":
return slog.LevelWarn
case "ERROR", "PANIC", "FATAL":
return slog.LevelError
default:
return slog.LevelInfo
}
}
// extractLevel parses lgr-style log message to extract level prefix
func extractLevel(msg string) (level, message string) {
for _, lvl := range levels {
prefix := lvl + " "
bracketPrefix := "[" + lvl + "] "
if strings.HasPrefix(msg, prefix) {
return lvl, strings.TrimPrefix(msg, prefix)
}
if strings.HasPrefix(msg, bracketPrefix) {
return lvl, strings.TrimPrefix(msg, bracketPrefix)
}
}
return "INFO", msg
}
// formatAttr converts slog.Attr to string representation
func formatAttr(attr slog.Attr, groups []string) string {
if attr.Equal(slog.Attr{}) {
return ""
}
key := attr.Key
if len(groups) > 0 {
key = strings.Join(groups, ".") + "." + key
}
val := attr.Value.String()
// handle string values specially by quoting them
if attr.Value.Kind() == slog.KindString {
val = fmt.Sprintf("%q", attr.Value.String())
}
return fmt.Sprintf("%s=%s ", key, val)
}