• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-playground / log / 5863334224

15 Aug 2023 03:36AM UTC coverage: 82.793% (-11.0%) from 93.807%
5863334224

Pull #52

github

deankarn
optimizations
Pull Request #52: 1st pass slog support

212 of 212 new or added lines in 5 files covered. (100.0%)

587 of 709 relevant lines covered (82.79%)

10.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.19
/slog.go
1
//go:build go1.21
2
// +build go1.21
3

4
package log
5

6
import (
7
        "context"
8
        runtimeext "github.com/go-playground/pkg/v5/runtime"
9
        "log/slog"
10
        "runtime"
11
)
12

13
var _ slog.Handler = (*slogHandler)(nil)
14

15
type slogHandler struct {
16
        // List of Groups, each subsequent group belongs to the previous group, except the first
17
        // which are the top level fields fields before any grouping.
18
        groups []Field
19
}
20

21
// Enabled returns if the current logging level is enabled. In the case of this log package in this Level has a
22
// handler registered.
23
func (s *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
1✔
24
        rw.RLock()
1✔
25
        _, enabled := logHandlers[convertSlogLevel(level)]
1✔
26
        rw.RUnlock()
1✔
27
        return enabled
1✔
28
}
1✔
29

30
func (s *slogHandler) Handle(ctx context.Context, record slog.Record) error {
1✔
31

1✔
32
        var current Field
1✔
33
        if len(s.groups) == 0 {
2✔
34
                current = G("")
1✔
35
        } else {
1✔
36
                group := s.groups[len(s.groups)-1]
×
37
                last := group.Value.([]Field)
×
38
                fields := make([]Field, len(last), len(last)+record.NumAttrs()+1)
×
39
                copy(fields, last)
×
40

×
41
                current = F(group.Key, fields)
×
42
        }
×
43

44
        if record.NumAttrs() > 0 {
1✔
45
                record.Attrs(func(attr slog.Attr) bool {
×
46
                        current.Value = s.convertAttrToField(current.Value.([]Field), attr)
×
47
                        return true
×
48
                })
×
49
        }
50
        if record.Level >= slog.LevelError && record.PC != 0 {
1✔
51
                fs := runtime.CallersFrames([]uintptr{record.PC})
×
52
                f, _ := fs.Next()
×
53
                sourceBuff := BytePool().Get()
×
54
                sourceBuff.B = extractSource(sourceBuff.B, runtimeext.Frame{Frame: f})
×
55
                current.Value = append(current.Value.([]Field), F(slog.SourceKey, string(sourceBuff.B[:len(sourceBuff.B)-1])))
×
56
                BytePool().Put(sourceBuff)
×
57
        }
×
58

59
        for i := len(s.groups) - 2; i >= 0; i-- {
1✔
60
                group := s.groups[i]
×
61
                gf := group.Value.([]Field)
×
62
                copied := make([]Field, len(gf), len(gf)+1)
×
63
                copy(copied, gf)
×
64
                current = G(group.Key, append(copied, current)...)
×
65
        }
×
66

67
        var e Entry
1✔
68
        if current.Key == "" {
2✔
69
                e = Entry{Fields: current.Value.([]Field)}
1✔
70
        } else {
1✔
71
                e = Entry{Fields: []Field{current}}
×
72
        }
×
73
        e.Message = record.Message
1✔
74
        e.Level = convertSlogLevel(record.Level)
1✔
75
        e.Timestamp = record.Time
1✔
76

1✔
77
        HandleEntry(e)
1✔
78
        return nil
1✔
79
}
80

81
func (s *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
×
82
        var groups []Field
×
83
        if len(s.groups) == 0 {
×
84
                groups = []Field{G("", s.convertAttrsToFields(nil, attrs)...)}
×
85
        } else {
×
86
                groups = make([]Field, len(s.groups))
×
87
                copy(groups, s.groups)
×
88

×
89
                l := len(groups) - 1
×
90
                current := groups[l]
×
91
                currentFields := current.Value.([]Field)
×
92
                copiedFields := make([]Field, len(currentFields), len(currentFields)+len(attrs))
×
93
                copy(copiedFields, currentFields)
×
94
                groups[l].Value = s.convertAttrsToFields(copiedFields, attrs)
×
95
        }
×
96

97
        return &slogHandler{
×
98
                groups: groups,
×
99
        }
×
100
}
101

102
func (s *slogHandler) convertAttrsToFields(fields []Field, attrs []slog.Attr) []Field {
×
103
        for _, attr := range attrs {
×
104
                if attr.Key == "" {
×
105
                        continue
×
106
                }
107
                if attr.Key == slog.TimeKey && attr.Value.Time().IsZero() {
×
108
                        continue
×
109
                }
110
                fields = s.convertAttrToField(fields, attr)
×
111
        }
112
        return fields
×
113
}
114

115
func (s *slogHandler) convertAttrToField(fields []Field, attr slog.Attr) []Field {
×
116
        var value any
×
117

×
118
        switch attr.Value.Kind() {
×
119
        case slog.KindLogValuer:
×
120
                return s.convertAttrToField(fields, slog.Attr{Key: attr.Key, Value: attr.Value.LogValuer().LogValue()})
×
121

122
        case slog.KindGroup:
×
123
                attrs := attr.Value.Group()
×
124
                groupedFields := make([]Field, 0, len(attrs))
×
125
                value = s.convertAttrsToFields(groupedFields, attrs)
×
126

127
        default:
×
128
                value = attr.Value.Any()
×
129
        }
130
        return append(fields, F(attr.Key, value))
×
131
}
132

133
func (s *slogHandler) WithGroup(name string) slog.Handler {
×
134
        groups := make([]Field, len(s.groups), len(s.groups)+1)
×
135
        copy(groups, s.groups)
×
136

×
137
        return &slogHandler{
×
138
                groups: append(groups, G(name)),
×
139
        }
×
140
}
×
141

142
func convertSlogLevel(level slog.Level) Level {
2✔
143
        switch level {
2✔
144
        case slog.LevelDebug:
×
145
                return DebugLevel
×
146
        case slog.LevelInfo:
2✔
147
                return InfoLevel
2✔
148
        case SlogNoticeLevel:
×
149
                return NoticeLevel
×
150
        case slog.LevelWarn:
×
151
                return WarnLevel
×
152
        case slog.LevelError:
×
153
                return ErrorLevel
×
154
        case SlogPanicLevel:
×
155
                return PanicLevel
×
156
        case SlogAlertLevel:
×
157
                return AlertLevel
×
158
        case SlogFatalLevel:
×
159
                return FatalLevel
×
160
        default:
×
161
                switch {
×
162
                case level > slog.LevelInfo && level < slog.LevelWarn:
×
163
                        return NoticeLevel
×
164
                case level > slog.LevelError && level <= SlogPanicLevel:
×
165
                        return PanicLevel
×
166
                case level > SlogPanicLevel && level <= SlogAlertLevel:
×
167
                        return AlertLevel
×
168
                case level > SlogAlertLevel && level <= SlogFatalLevel:
×
169
                        return FatalLevel
×
170
                }
171
                return InfoLevel
×
172
        }
173
}
174

175
var (
176
        prevSlogLogger *slog.Logger
177
)
178

179
// RedirectGoStdLog is used to redirect Go's internal std log output to this logger AND registers a handler for slog
180
// that redirects slog output to this logger.
181
//
182
// If you intend to use this log interface with another slog handler then you should not use this function and instead
183
// register a handler with slog directly and register the slog redirect, found under the handlers package or other
184
// custom redirect handler with this logger.
185
func RedirectGoStdLog(redirect bool) {
1✔
186
        if redirect {
2✔
187
                prevSlogLogger = slog.Default()
1✔
188
                slog.SetDefault(slog.New(&slogHandler{}))
1✔
189
        } else if prevSlogLogger != nil {
1✔
190
                slog.SetDefault(prevSlogLogger)
×
191
                prevSlogLogger = nil
×
192
        }
×
193
}
194

195
// slog log levels.
196
const (
197
        SlogDebugLevel  slog.Level = slog.LevelDebug
198
        SlogInfoLevel   slog.Level = slog.LevelInfo
199
        SlogWarnLevel   slog.Level = slog.LevelWarn
200
        SlogErrorLevel  slog.Level = slog.LevelError
201
        SlogNoticeLevel slog.Level = slog.LevelInfo + 2
202
        SlogPanicLevel  slog.Level = slog.LevelError + 4
203
        SlogAlertLevel  slog.Level = SlogPanicLevel + 4
204
        SlogFatalLevel  slog.Level = SlogAlertLevel + 4 // same as syslog CRITICAL
205
)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc