github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/slog_test.go (about) 1 //go:build go1.21 2 3 package zlog 4 5 import ( 6 "context" 7 "errors" 8 "log" 9 "log/slog" 10 "runtime/debug" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 "go.uber.org/zap" 16 "go.uber.org/zap/zaptest" 17 ) 18 19 func getErrorWithStackTrace() error { 20 return &errorWithStack{ 21 error: errors.New("test error"), 22 stack: debug.Stack(), 23 } 24 } 25 26 type errorWithStack struct { 27 error 28 stack []byte 29 } 30 31 func TestRedirectStdLog(t *testing.T) { 32 out := &zaptest.Buffer{} 33 l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out) 34 require.Nil(t, err) 35 resetFunc := redirectStdLog(l, false) 36 defer resetFunc() 37 38 log.Printf("[Debug] std log Printf, key1=%v", "value1") 39 slog.Debug("std slog Debug", "key1", "value1") 40 41 lines := out.Lines() 42 require.Len(t, lines, 2) 43 assert.Contains(t, lines[0], "DEBUG stdlog zlog/slog_test.go:") 44 assert.Contains(t, lines[0], "[Debug] std log Printf, key1=value1") 45 assert.Contains(t, lines[1], "DEBUG zlog/slog_test.go:") 46 assert.Contains(t, lines[1], `std slog Debug {"key1": "value1"}`) 47 } 48 49 func TestSetSlogDefault(t *testing.T) { 50 out := &zaptest.Buffer{} 51 l, p, err := NewWithOutput(&Config{Level: "warn", Format: "console"}, out) 52 require.Nil(t, err) 53 resetFunc := ReplaceGlobals(l, p) 54 defer resetFunc() 55 56 SetSlogDefault(NewSlogLogger()) 57 log.Printf("[Warn] std log Printf, key1=%v", "value1") 58 slog.Info("std slog Info", "key1", "value1") 59 slog.Warn("std slog Warn", "key2", "value2") 60 61 lines := out.Lines() 62 require.Len(t, lines, 2) 63 assert.Contains(t, lines[0], "WARN stdlog zlog/slog_test.go:") 64 assert.Contains(t, lines[0], "[Warn] std log Printf, key1=value1") 65 assert.Contains(t, lines[1], "WARN zlog/slog_test.go:") 66 assert.Contains(t, lines[1], `std slog Warn {"key2": "value2"}`) 67 } 68 69 func TestNewSlogLogger(t *testing.T) { 70 r0 := NewSlogLogger() 71 assert.NotNil(t, r0.Handler().(*slogImpl).opts) 72 assert.NotNil(t, r0.Handler().(*slogImpl).l) 73 74 l := L().Named("slogTest").With(zap.String("ns", "default")) 75 r1 := NewSlogLogger(func(options *SlogOptions) { 76 options.Logger = l.Logger 77 }) 78 assert.Equal(t, "slogTest", r1.Handler().(*slogImpl).name) 79 r1.Info("test NewSlogLogger with logger") 80 } 81 82 func TestSlogLoggerLogging(t *testing.T) { 83 out := &zaptest.Buffer{} 84 l, _, err := NewWithOutput(&Config{ 85 Level: "trace", 86 Format: "json", 87 DisableTimestamp: true, 88 DisableStacktrace: true, 89 }, out) 90 require.Nil(t, err) 91 logger := NewSlogLogger(func(options *SlogOptions) { 92 options.Logger = l 93 }) 94 95 logger.Info("test info", "ns", "default", "podnum", 2) 96 lines := out.Lines() 97 assert.Contains(t, lines[0], `"level":"info"`) 98 assert.Contains(t, lines[0], `"caller":"zlog/slog_test.go:`) 99 assert.Contains(t, lines[0], `"ns":"default"`) 100 assert.Contains(t, lines[0], `"podnum":2`) 101 } 102 103 func TestSlogLoggerCtxHandler(t *testing.T) { 104 t.Run("change level", func(t *testing.T) { 105 out := &zaptest.Buffer{} 106 cfg := &Config{Level: "info", Format: "console"} 107 cfg.RedirectStdLog = true 108 cfg.CtxHandler.ChangeLevel = func(ctx context.Context) *Level { 109 if ctx.Value("debug") != nil { 110 level := DebugLevel 111 return &level 112 } 113 return nil 114 } 115 cfg.CtxHandler.WithCtx = func(ctx context.Context) (result CtxResult) { 116 if ctx.Value("debug") != nil { 117 level := DebugLevel 118 result.Level = &level 119 } 120 return 121 } 122 l, p, err := NewWithOutput(cfg, out) 123 require.Nil(t, err) 124 resetFunc := ReplaceGlobals(l, p) 125 defer resetFunc() 126 127 ctx := context.WithValue(context.Background(), "debug", "1") 128 slog.DebugContext(ctx, "a debug message") 129 130 lines := out.Lines() 131 require.Len(t, lines, 1) 132 assert.Contains(t, lines[0], "DEBUG zlog/slog_test.go:") 133 assert.Contains(t, lines[0], `a debug message`) 134 }) 135 136 t.Run("ctx fields", func(t *testing.T) { 137 out := &zaptest.Buffer{} 138 cfg := &Config{Level: "info", Format: "console"} 139 cfg.RedirectStdLog = true 140 cfg.CtxHandler.WithCtx = func(ctx context.Context) (result CtxResult) { 141 result.Fields = append(result.Fields, 142 zap.String("logid", "abcde"), 143 zap.Int64("userID", 12345)) 144 return 145 } 146 l, p, err := NewWithOutput(cfg, out) 147 require.Nil(t, err) 148 resetFunc := ReplaceGlobals(l, p) 149 defer resetFunc() 150 151 slog.InfoContext(context.Background(), "an info message") 152 153 lines := out.Lines() 154 require.Len(t, lines, 1) 155 assert.Contains(t, lines[0], "INFO zlog/slog_test.go:") 156 assert.Contains(t, lines[0], `an info message {"logid": "abcde", "userID": 12345}`) 157 }) 158 } 159 160 func TestSlogOptionsReplaceAttr(t *testing.T) { 161 t.Run("error key", func(t *testing.T) { 162 out := &zaptest.Buffer{} 163 l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out) 164 require.Nil(t, err) 165 logger := NewSlogLogger(func(options *SlogOptions) { 166 options.Logger = l 167 options.ReplaceAttr = func(groups []string, a slog.Attr) (rr ReplaceResult) { 168 if a.Key == "error" || a.Key == "err" && a.Value.Kind() == slog.KindAny { 169 if err, ok := a.Value.Any().(error); ok { 170 rr.Field = zap.Error(err) 171 return 172 } 173 } 174 rr.Field = ConvertAttrToField(a) 175 return 176 } 177 }) 178 179 logger.Error("error log 1", "err", errors.New("test error 1")) 180 logger.Error("error log 2", "error", errors.New("test error 2")) 181 logger.Error("error log 3", "notMatchErrKey", errors.New("test error 3")) 182 183 lines := out.Lines() 184 require.Len(t, lines, 3) 185 assert.Contains(t, lines[0], "ERROR zlog/slog_test.go:") 186 assert.Contains(t, lines[0], `error log 1 {"error": "test error 1"}`) 187 assert.Contains(t, lines[1], "ERROR zlog/slog_test.go:") 188 assert.Contains(t, lines[1], `error log 2 {"error": "test error 2"}`) 189 assert.Contains(t, lines[2], "ERROR zlog/slog_test.go:") 190 assert.Contains(t, lines[2], `error log 3 {"notMatchErrKey": "test error 3"}`) 191 }) 192 193 t.Run("error stacktrace", func(t *testing.T) { 194 out := &zaptest.Buffer{} 195 l, _, err := NewWithOutput(&Config{Level: "debug", Format: "console"}, out) 196 require.Nil(t, err) 197 logger := NewSlogLogger(func(options *SlogOptions) { 198 options.Logger = l 199 options.ReplaceAttr = func(groups []string, a slog.Attr) (rr ReplaceResult) { 200 if a.Key == "error" || a.Key == "err" && a.Value.Kind() == slog.KindAny { 201 if err, ok := a.Value.Any().(error); ok { 202 if inner, ok := err.(*errorWithStack); ok { 203 rr.Multi = []zap.Field{ 204 zap.Error(err), 205 zap.ByteString("stacktrace", inner.stack), 206 } 207 } else { 208 rr.Field = zap.Error(err) 209 } 210 return 211 } 212 } 213 rr.Field = ConvertAttrToField(a) 214 return 215 } 216 }) 217 218 logger.Error("error log", "err", wrapFunc(3, getErrorWithStackTrace)()) 219 220 lines := out.Lines() 221 require.Len(t, lines, 1) 222 assert.Contains(t, lines[0], "ERROR zlog/slog_test.go:") 223 assert.Contains(t, lines[0], `error log {"error": "test error", "stacktrace": "`) 224 assert.Contains(t, lines[0], `zlog.getErrorWithStackTrace()\n\t`) 225 assert.Contains(t, lines[0], "zlog/slog_test.go:23") 226 }) 227 } 228 229 func wrapFunc(depth int, f func() error) func() error { 230 for i := 0; i < depth; i++ { 231 copyFunc := f 232 newFunc := func() error { 233 return copyFunc() 234 } 235 f = newFunc 236 } 237 return f 238 }