github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/xlog_test.go (about) 1 package xlog 2 3 import ( 4 "context" 5 "errors" 6 randv2 "math/rand/v2" 7 "os" 8 "path/filepath" 9 "sync" 10 "testing" 11 12 "github.com/stretchr/testify/require" 13 "go.uber.org/zap" 14 "go.uber.org/zap/zapcore" 15 16 "github.com/benz9527/xboot/lib/id" 17 "github.com/benz9527/xboot/lib/infra" 18 ) 19 20 func TestLogLevelString(t *testing.T) { 21 require.Equal(t, "DEBUG", LogLevelDebug.String()) 22 require.Equal(t, "INFO", LogLevelInfo.String()) 23 require.Equal(t, "WARN", LogLevelWarn.String()) 24 require.Equal(t, "ERROR", LogLevelError.String()) 25 require.Equal(t, zapcore.DebugLevel, LogLevelDebug.zapLevel()) 26 require.Equal(t, zapcore.InfoLevel, LogLevelInfo.zapLevel()) 27 require.Equal(t, zapcore.WarnLevel, LogLevelWarn.zapLevel()) 28 require.Equal(t, zapcore.ErrorLevel, LogLevelError.zapLevel()) 29 } 30 31 type testBanner struct{} 32 33 func (b testBanner) JSON() string { 34 return "{\"app\":\"xboot\"}" 35 } 36 37 func (b *testBanner) PlainText() string { 38 return ` 39 ██╗ ██╗██████╗ ██████╗ ██████╗ ████████╗ 40 ╚██╗██╔╝██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ 41 ╚███╔╝ ██████╔╝██║ ██║██║ ██║ ██║ 42 ██╔██╗ ██╔══██╗██║ ██║██║ ██║ ██║ 43 ██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝ ██║ 44 ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ 45 ` 46 } 47 48 type testMemOutWriter struct { 49 data []byte 50 } 51 52 func (w *testMemOutWriter) Write(p []byte) (n int, err error) { 53 w.data = append(w.data, p...) 54 return len(p), nil 55 } 56 57 func (w *testMemOutWriter) Reset() { 58 w.data = make([]byte, 0, 4096) 59 } 60 61 func TestLoggerPrintBanner(t *testing.T) { 62 w := &testMemOutWriter{data: make([]byte, 0, 4096)} 63 err := writerMap.Put(testMemAsOut, zapcore.AddSync(w)) 64 require.NoError(t, err) 65 66 level := zapcore.DebugLevel 67 cfg := zap.NewDevelopmentEncoderConfig() 68 core := zapcore.NewCore( 69 zapcore.NewJSONEncoder(cfg), 70 zapcore.Lock(os.Stdout), 71 level, 72 ) 73 l := zap.New( 74 zapcore.NewTee(core), 75 zap.AddCaller(), 76 zap.AddStacktrace(zap.ErrorLevel), 77 zap.AddCallerSkip(1), // Use caller filename as service 78 ) 79 80 logger := &xLogger{ 81 writer: testMemAsOut, 82 } 83 logger.logger.Store(l) 84 logger.Banner(&testBanner{}) 85 require.Equal(t, "{\"banner\":\"{\\\"app\\\":\\\"xboot\\\"}\"}\n", string(w.data)) 86 w.Reset() 87 88 printBanner = sync.Once{} 89 core = zapcore.NewCore( 90 zapcore.NewConsoleEncoder(cfg), 91 zapcore.Lock(os.Stdout), 92 level, 93 ) 94 l = zap.New( 95 zapcore.NewTee(core), 96 zap.AddCaller(), 97 zap.AddStacktrace(zap.ErrorLevel), 98 zap.AddCallerSkip(1), // Use caller filename as service 99 ) 100 logger = &xLogger{ 101 writer: testMemAsOut, 102 encoder: PlainText, 103 } 104 logger.logger.Store(l) 105 logger.Banner(&testBanner{}) 106 require.Equal(t, ` 107 ██╗ ██╗██████╗ ██████╗ ██████╗ ████████╗ 108 ╚██╗██╔╝██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ 109 ╚███╔╝ ██████╔╝██║ ██║██║ ██║ ██║ 110 ██╔██╗ ██╔══██╗██║ ██║██║ ██║ ██║ 111 ██╔╝ ██╗██████╔╝╚██████╔╝╚██████╔╝ ██║ 112 ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ 113 114 `, string(w.data)) 115 w.Reset() 116 } 117 118 type testObj1 struct { 119 name string 120 arr []testObj2 121 obj3 testObj3 122 } 123 124 type testObj2 struct { 125 age int 126 } 127 128 type testObj3 struct { 129 o float32 130 } 131 132 func TestXLogger_Zap_AllAPIs(t *testing.T) { 133 testcases := []struct { 134 name string 135 encoder logEncoderType 136 writer logOutWriterType 137 defaultLogger bool 138 ctxM map[string]string 139 clean func() 140 }{ 141 { 142 name: "console json", 143 encoder: JSON, 144 writer: StdOut, 145 ctxM: map[string]string{ 146 "traceId": "TraceID", 147 "service": "Svc", 148 }, 149 }, 150 { 151 name: "file json", 152 encoder: JSON, 153 writer: File, 154 ctxM: map[string]string{ 155 "traceId": "TraceID", 156 "service": "Svc", 157 }, 158 }, 159 { 160 name: "console plaintext", 161 encoder: PlainText, 162 writer: StdOut, 163 ctxM: map[string]string{ 164 "traceId": "traceID", 165 "service": "svc", 166 "abc": "", 167 }, 168 }, 169 { 170 name: "console default json", 171 defaultLogger: true, 172 }, 173 { 174 name: "console default json2", 175 defaultLogger: true, 176 ctxM: map[string]string{ 177 "traceId": "", 178 "service": "", 179 "": "", 180 "abc": "_", 181 }, 182 }, 183 } 184 for _, tc := range testcases { 185 t.Run(tc.name, func(t *testing.T) { 186 var opts []XLoggerOption 187 if tc.defaultLogger { 188 opts = []XLoggerOption{} 189 } else { 190 opts = append(opts, 191 WithXLoggerContext(context.TODO()), 192 WithXLoggerLevel(LogLevelDebug), 193 WithXLoggerEncoder(tc.encoder), 194 WithXLoggerTimeEncoder(zapcore.ISO8601TimeEncoder), 195 WithXLoggerLevelEncoder(zapcore.CapitalLevelEncoder), 196 func() XLoggerOption { 197 switch tc.writer { 198 case StdOut: 199 return WithXLoggerStdOutWriter() 200 case File: 201 nano, err := id.ClassicNanoID(6) 202 require.NoError(t, err) 203 rngLogSuffix := "_" + nano() + "_xlog" 204 cfg := &FileCoreConfig{ 205 FilePath: os.TempDir(), 206 Filename: filepath.Base(os.Args[0]) + rngLogSuffix + ".log", 207 FileCompressible: false, 208 FileMaxBackups: 4, 209 FileMaxAge: "3day", 210 FileMaxSize: "1KB", 211 FileRotateEnable: true, 212 FileBufferSize: "1KB", 213 FileBufferFlushInterval: 500, 214 } 215 tc.clean = func() { 216 removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log") 217 require.GreaterOrEqual(t, removed, cfg.FileMaxBackups+1) 218 } 219 return WithXLoggerFileWriter(cfg) 220 default: 221 } 222 return nil 223 }(), 224 ) 225 } 226 if tc.ctxM != nil { 227 for k, v := range tc.ctxM { 228 opts = append(opts, WithXLoggerContextFieldExtract(k, v)) 229 } 230 } 231 logger := NewXLogger(opts...) 232 233 ctx := context.TODO() 234 ctx = context.WithValue(ctx, "traceId", "1234567890") 235 ctx = context.WithValue(ctx, "service", "xboot") 236 237 logger.Debug("debug message 1") 238 logger.Log(zapcore.DebugLevel, "debug message 1-1") 239 logger.DebugContext(ctx, "debug message 2") 240 logger.Info("info message 1") 241 logger.InfoContext(ctx, "info message 2") 242 logger.Warn("warn message 1") 243 logger.WarnContext(ctx, "warn message 2") 244 err1 := infra.WrapErrorStack(errors.New("error 1")) 245 logger.Error(err1, "error message 1") 246 logger.ErrorContext(ctx, err1, "error message 2") 247 logger.ErrorStack(err1, "error message 1") 248 logger.ErrorStackContext(ctx, err1, "error message 2") 249 250 obj1 := testObj1{ 251 name: "testObj1", 252 arr: []testObj2{ 253 {age: 1}, 254 {age: 2}, 255 }, 256 obj3: testObj3{o: 3.14}, 257 } 258 field := zap.Object("testObj1", zapcore.ObjectMarshalerFunc( 259 func(oe zapcore.ObjectEncoder) error { 260 oe.AddString("name", obj1.name) 261 if err := oe.AddArray("arr", zapcore.ArrayMarshalerFunc( 262 func(ae zapcore.ArrayEncoder) error { 263 for _, v := range obj1.arr { 264 if err := ae.AppendObject(zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error { 265 enc.AddInt("age", v.age) 266 return nil 267 })); err != nil { 268 return err 269 } 270 } 271 return nil 272 })); err != nil { 273 return err 274 } 275 if err := oe.AddObject("obj3", zapcore.ObjectMarshalerFunc( 276 func(oe zapcore.ObjectEncoder) error { 277 oe.AddFloat32("o", obj1.obj3.o) 278 return nil 279 })); err != nil { 280 return err 281 } 282 return nil 283 })) 284 logger.Info("info message 3", field) 285 logger.InfoContext(ctx, "info message 4", field) 286 287 logger.IncreaseLogLevel(zapcore.WarnLevel) 288 require.Equal(t, zapcore.WarnLevel.String(), logger.Level()) 289 logger.Logf(getLogLevelOrDefault(""), "unprintable debug message 3") 290 logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "unprintable debug message 4") 291 logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "unprintable info message 5") 292 logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "printable warn message 3") 293 logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "printable error message 3") 294 logger.ErrorStackf(err1, "error message 4") 295 296 logger.IncreaseLogLevel(zapcore.DebugLevel) 297 require.Equal(t, zapcore.DebugLevel.String(), logger.Level()) 298 logger.Logf(getLogLevelOrDefault(""), "dynamic printable debug message 4") 299 logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "dynamic printable debug message 5") 300 logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "dynamic printable info message 6") 301 logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "dynamic printable warn message 4") 302 logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "dynamic printable error message 4") 303 logger.ErrorStackf(err1, "error message 5") 304 305 logger.IncreaseLogLevel(zapcore.WarnLevel) 306 require.Equal(t, zapcore.WarnLevel.String(), logger.Level()) 307 logger.Logf(getLogLevelOrDefault(""), "unprintable debug message 5") 308 logger.Logf(getLogLevelOrDefault(LogLevelDebug.String()), "unprintable debug message 6") 309 logger.Logf(getLogLevelOrDefault(LogLevelInfo.String()), "unprintable info message 7") 310 logger.Logf(getLogLevelOrDefault(LogLevelWarn.String()), "printable warn message 5") 311 logger.Logf(getLogLevelOrDefault(LogLevelError.String()), "printable error message 5") 312 logger.ErrorStackf(err1, "error message 6") 313 314 err := logger.Sync() 315 if err != nil { 316 t.Log(err) 317 } 318 logger.Close() 319 if tc.clean != nil { 320 tc.clean() 321 } 322 }) 323 } 324 } 325 326 func TestXLogger_Zap_DataRace(t *testing.T) { 327 logger := NewXLogger() 328 lvls := []zapcore.Level{ 329 zapcore.DebugLevel, 330 zapcore.InfoLevel, 331 zapcore.WarnLevel, 332 zapcore.ErrorLevel, 333 } 334 n := int32(len(lvls)) 335 var wg sync.WaitGroup 336 total := 10 337 wg.Add(total) 338 for i := 0; i < total; i++ { 339 go func(i int) { 340 for j := 0; j < 100; j++ { 341 rng := randv2.Int32N(n) 342 if i*total+j == 666 { 343 logger.IncreaseLogLevel(lvls[rng]) 344 } 345 logger.Logf(lvls[rng], "message i: %d; j: %d", i, j) 346 } 347 wg.Done() 348 }(i) 349 } 350 wg.Wait() 351 _ = logger.Sync() 352 } 353 354 func BenchmarkXLogger_Zap(b *testing.B) { 355 logger := NewXLogger() 356 b.ResetTimer() 357 for i := 0; i < b.N; i++ { 358 logger.Info("message") 359 } 360 b.ReportAllocs() 361 }