Skip to content

Commit 4e2e789

Browse files
authored
Slog handler (#6474)
1 parent 3f47e40 commit 4e2e789

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

internal/logger/glog/handler.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package glog
2+
3+
// Custom log levels https://go.dev/src/log/slog/example_custom_levels_test.go - for fatal & trace
4+
5+
import (
6+
"context"
7+
"io"
8+
"log/slog"
9+
"os"
10+
"runtime"
11+
"strconv"
12+
"strings"
13+
"sync"
14+
)
15+
16+
const (
17+
// LevelTrace - Trace Level Logging same as glog.V(3)
18+
LevelTrace = slog.Level(-8)
19+
// LevelDebug - Debug Level Logging same as glog.V(2)
20+
LevelDebug = slog.LevelDebug
21+
// LevelInfo - Info Level Logging same as glog.Info()
22+
LevelInfo = slog.LevelInfo
23+
// LevelWarning - Warn Level Logging same as glog.Warning()
24+
LevelWarning = slog.LevelWarn
25+
// LevelError - Error Level Logging same as glog.Error()
26+
LevelError = slog.LevelError
27+
// LevelFatal - Fatal Level Logging same as glog.Fatal()
28+
LevelFatal = slog.Level(12)
29+
)
30+
31+
// Handler holds all the parameters for the handler
32+
type Handler struct {
33+
opts Options
34+
mu *sync.Mutex
35+
out io.Writer
36+
}
37+
38+
// Options contains the log Level
39+
type Options struct {
40+
// Level reports the minimum level to log.
41+
// Levels with lower levels are discarded.
42+
// If nil, the Handler uses [slog.LevelInfo].
43+
Level slog.Leveler
44+
}
45+
46+
// New - create a new Handler
47+
func New(out io.Writer, opts *Options) *Handler {
48+
h := &Handler{out: out, mu: &sync.Mutex{}}
49+
if opts != nil {
50+
h.opts = *opts
51+
}
52+
if h.opts.Level == nil {
53+
h.opts.Level = slog.LevelInfo
54+
}
55+
return h
56+
}
57+
58+
// Enabled - is this log level enabled?
59+
func (h *Handler) Enabled(_ context.Context, level slog.Level) bool {
60+
return level >= h.opts.Level.Level()
61+
}
62+
63+
// WithGroup - not needed
64+
func (h *Handler) WithGroup(_ string) slog.Handler {
65+
// not needed.
66+
return h
67+
}
68+
69+
// WithAttrs - not needed
70+
func (h *Handler) WithAttrs(_ []slog.Attr) slog.Handler {
71+
// not needed.
72+
return h
73+
}
74+
75+
// Handle log event
76+
// Format F20240920 16:53:18.817844 70741 main.go:285] message
77+
//
78+
// <Level>YYYYMMDD HH:MM:SS.NNNNNN <pid> <file>:<line> <msg>
79+
func (h *Handler) Handle(_ context.Context, r slog.Record) error {
80+
buf := make([]byte, 0, 1024)
81+
// LogLevel
82+
switch r.Level {
83+
case LevelTrace:
84+
buf = append(buf, "I"...)
85+
case LevelDebug:
86+
buf = append(buf, "I"...)
87+
case LevelInfo:
88+
buf = append(buf, "I"...)
89+
case LevelWarning:
90+
buf = append(buf, "W"...)
91+
case LevelError:
92+
buf = append(buf, "E"...)
93+
case LevelFatal:
94+
buf = append(buf, "F"...)
95+
}
96+
97+
// date/time
98+
if !r.Time.IsZero() {
99+
buf = append(buf, r.Time.Format("20060102 15:04:05.000000")...)
100+
}
101+
102+
buf = append(buf, " "...)
103+
104+
// PID
105+
buf = append(buf, strconv.Itoa(os.Getpid())...)
106+
107+
buf = append(buf, " "...)
108+
// Log line
109+
if r.PC != 0 {
110+
fs := runtime.CallersFrames([]uintptr{r.PC})
111+
f, _ := fs.Next()
112+
buf = append(buf, getShortFileName(f.File)...)
113+
buf = append(buf, ":"...)
114+
buf = append(buf, strconv.Itoa(f.Line)...)
115+
}
116+
buf = append(buf, "]"...)
117+
buf = append(buf, " "...)
118+
buf = append(buf, r.Message...)
119+
buf = append(buf, "\n"...)
120+
h.mu.Lock()
121+
defer h.mu.Unlock()
122+
_, err := h.out.Write(buf)
123+
return err
124+
}
125+
126+
func getShortFileName(f string) string {
127+
fp := strings.Split(f, "/")
128+
return fp[len(fp)-1]
129+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package glog
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"log/slog"
7+
"regexp"
8+
"testing"
9+
)
10+
11+
func TestGlogFormat(t *testing.T) {
12+
var buf bytes.Buffer
13+
l := slog.New(New(&buf, nil))
14+
l.Info("hello")
15+
got := buf.String()
16+
wantre := `^\w\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`
17+
re := regexp.MustCompile(wantre)
18+
if !re.MatchString(got) {
19+
t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre)
20+
}
21+
}
22+
23+
func TestGlogLogLevels(t *testing.T) {
24+
testCases := []struct {
25+
name string
26+
level slog.Level
27+
wantre string
28+
}{
29+
{
30+
name: "Trace level log message",
31+
level: LevelTrace,
32+
wantre: `^I\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
33+
},
34+
{
35+
name: "Debug level log message",
36+
level: LevelDebug,
37+
wantre: `^I\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
38+
},
39+
{
40+
name: "Info level log message",
41+
level: LevelInfo,
42+
wantre: `^I\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
43+
},
44+
{
45+
name: "Warning level log message",
46+
level: LevelWarning,
47+
wantre: `^W\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
48+
},
49+
{
50+
name: "Error level log message",
51+
level: LevelError,
52+
wantre: `^E\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
53+
},
54+
{
55+
name: "Fatal level log message",
56+
level: LevelFatal,
57+
wantre: `^F\d{8}\s\d+:\d+:\d+.\d{6}\s+\d+\s\w+\.go:\d+\]\s.*\s$`,
58+
},
59+
}
60+
t.Parallel()
61+
for _, tc := range testCases {
62+
t.Run(tc.name, func(t *testing.T) {
63+
var buf bytes.Buffer
64+
l := slog.New(New(&buf, &Options{Level: tc.level}))
65+
l.Log(context.Background(), tc.level, "test")
66+
got := buf.String()
67+
re := regexp.MustCompile(tc.wantre)
68+
if !re.MatchString(got) {
69+
t.Errorf("\ngot:\n%q\nwant:\n%q", got, tc.wantre)
70+
}
71+
})
72+
}
73+
}
74+
75+
func TestGlogDefaultLevel(t *testing.T) {
76+
var buf bytes.Buffer
77+
l := slog.New(New(&buf, nil))
78+
79+
l.Debug("test")
80+
if got := buf.Len(); got != 0 {
81+
t.Errorf("got buf.Len() = %d, want 0", got)
82+
}
83+
}
84+
85+
func TestGlogHigherLevel(t *testing.T) {
86+
var buf bytes.Buffer
87+
l := slog.New(New(&buf, &Options{Level: LevelError}))
88+
89+
l.Info("test")
90+
if got := buf.Len(); got != 0 {
91+
t.Errorf("got buf.Len() = %d, want 0", got)
92+
}
93+
}

internal/logger/logger.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package log
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"os"
7+
8+
"github.com/nginxinc/kubernetes-ingress/internal/logger/glog"
9+
)
10+
11+
type ctxLogger struct{}
12+
13+
// ContextWithLogger adds logger to context
14+
func ContextWithLogger(ctx context.Context, l *slog.Logger) context.Context {
15+
return context.WithValue(ctx, ctxLogger{}, l)
16+
}
17+
18+
// LoggerFromContext returns logger from context
19+
func LoggerFromContext(ctx context.Context) *slog.Logger {
20+
if l, ok := ctx.Value(ctxLogger{}).(*slog.Logger); ok {
21+
return l
22+
}
23+
return slog.New(glog.New(os.Stdout, nil))
24+
}

0 commit comments

Comments
 (0)