github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/xlog.go (about) 1 package xlog 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "sort" 8 "strings" 9 "sync" 10 "sync/atomic" 11 12 "go.uber.org/zap" 13 "go.uber.org/zap/zapcore" 14 15 "github.com/benz9527/xboot/lib/infra" 16 "github.com/benz9527/xboot/lib/kv" 17 ) 18 19 var printBanner = sync.Once{} 20 21 // XLogger is wrapper logger of Uber zap logger. 22 type xLogger struct { 23 cancelFn context.CancelFunc 24 logger atomic.Pointer[zap.Logger] 25 ctxFields kv.ThreadSafeStorer[string, string] 26 dynamicLevelEnabler zap.AtomicLevel 27 writer logOutWriterType 28 encoder logEncoderType 29 } 30 31 func (l *xLogger) zap() *zap.Logger { 32 return l.logger.Load() 33 } 34 35 // IncreaseLogLevel we can increase or decrease the log level concurrently. 36 func (l *xLogger) IncreaseLogLevel(level zapcore.Level) { 37 l.dynamicLevelEnabler.SetLevel(level) 38 } 39 40 func (l *xLogger) Sync() error { 41 return l.logger.Load().Sync() 42 } 43 44 func (l *xLogger) Level() string { 45 return l.dynamicLevelEnabler.Level().String() 46 } 47 48 func (l *xLogger) Close() { 49 if l.cancelFn != nil { 50 l.cancelFn() 51 } 52 } 53 54 func (l *xLogger) Banner(banner Banner) { 55 printBanner.Do(func() { 56 var enc zapcore.Encoder 57 core := zapcore.EncoderConfig{ 58 MessageKey: "banner", // Required, but the plain text will be ignored. 59 LevelKey: coreKeyIgnored, 60 EncodeLevel: nil, 61 TimeKey: coreKeyIgnored, 62 EncodeTime: nil, 63 CallerKey: coreKeyIgnored, 64 EncodeCaller: nil, 65 StacktraceKey: coreKeyIgnored, 66 } 67 switch l.encoder { 68 case JSON: 69 enc = zapcore.NewJSONEncoder(core) 70 case PlainText: 71 enc = zapcore.NewConsoleEncoder(core) 72 } 73 ws := getOutWriterByType(l.writer) 74 lvlEnabler := zap.NewAtomicLevelAt(zapcore.InfoLevel) 75 _l := l.logger.Load().WithOptions( 76 zap.WrapCore(func(core zapcore.Core) zapcore.Core { 77 return zapcore.NewCore(enc, ws, lvlEnabler) 78 }), 79 ) 80 switch l.encoder { 81 case JSON: 82 _l.Info(banner.JSON()) 83 case PlainText: 84 _l.Info(banner.PlainText()) 85 } 86 }) 87 } 88 89 func (l *xLogger) Log(lvl zapcore.Level, msg string, fields ...zap.Field) { 90 l.logger.Load().Log(lvl, msg, fields...) 91 } 92 93 func (l *xLogger) Debug(msg string, fields ...zap.Field) { 94 l.logger.Load().Debug(msg, fields...) 95 } 96 97 func (l *xLogger) Info(msg string, fields ...zap.Field) { 98 l.logger.Load().Info(msg, fields...) 99 } 100 101 func (l *xLogger) Warn(msg string, fields ...zap.Field) { 102 l.logger.Load().Warn(msg, fields...) 103 } 104 105 func (l *xLogger) Error(err error, msg string, fields ...zap.Field) { 106 newFields := make([]zap.Field, 0, len(fields)+1) 107 if err != nil { 108 newFields = append(newFields, zap.String("error", err.Error())) 109 } 110 newFields = append(newFields, fields...) 111 l.logger.Load().Error(msg, newFields...) 112 } 113 114 func (l *xLogger) ErrorStack(err error, msg string, fields ...zap.Field) { 115 var newFields []zap.Field 116 if es, ok := err.(infra.ErrorStack); ok && es != nil { 117 newFields = []zap.Field{ 118 zap.Inline(es), 119 } 120 } 121 newFields = append(newFields, fields...) 122 l.logger.Load().Error(msg, newFields...) 123 } 124 125 func (l *xLogger) DebugContext(ctx context.Context, msg string, fields ...zap.Field) { 126 newFields := extractFieldsFromContext(ctx, l.ctxFields) 127 newFields = append(newFields, fields...) 128 l.logger.Load().Debug(msg, newFields...) 129 } 130 131 func (l *xLogger) InfoContext(ctx context.Context, msg string, fields ...zap.Field) { 132 newFields := extractFieldsFromContext(ctx, l.ctxFields) 133 newFields = append(newFields, fields...) 134 l.logger.Load().Info(msg, newFields...) 135 } 136 137 func (l *xLogger) WarnContext(ctx context.Context, msg string, fields ...zap.Field) { 138 newFields := extractFieldsFromContext(ctx, l.ctxFields) 139 newFields = append(newFields, fields...) 140 l.logger.Load().Warn(msg, newFields...) 141 } 142 143 func (l *xLogger) ErrorContext(ctx context.Context, err error, msg string, fields ...zap.Field) { 144 newFields := extractFieldsFromContext(ctx, l.ctxFields) 145 if err != nil { 146 newFields = append(newFields, zap.String("error", err.Error())) 147 } 148 newFields = append(newFields, fields...) 149 l.logger.Load().Error(msg, newFields...) 150 } 151 152 func (l *xLogger) ErrorStackContext(ctx context.Context, err error, msg string, fields ...zap.Field) { 153 newFields := extractFieldsFromContext(ctx, l.ctxFields) 154 if es, ok := err.(infra.ErrorStack); ok && es != nil { 155 newFields = append(newFields, zap.Inline(es)) 156 } 157 newFields = append(newFields, fields...) 158 l.logger.Load().Error(msg, newFields...) 159 } 160 161 func (l *xLogger) Logf(lvl zapcore.Level, format string, args ...any) { 162 l.logger.Load().Log(lvl, fmt.Sprintf(format, args...)) 163 } 164 165 func (l *xLogger) ErrorStackf(err error, format string, args ...any) { 166 var newFields []zap.Field 167 if es, ok := err.(infra.ErrorStack); ok && es != nil { 168 newFields = []zap.Field{ 169 zap.Inline(es), 170 } 171 } 172 l.logger.Load().Log(zap.ErrorLevel, fmt.Sprintf(format, args...), newFields...) 173 } 174 175 type loggerCfg struct { 176 ctx context.Context 177 cancelFn context.CancelFunc 178 ctxFields kv.ThreadSafeStorer[string, string] 179 encoderType *logEncoderType 180 lvlEncoder zapcore.LevelEncoder 181 tsEncoder zapcore.TimeEncoder 182 level *zapcore.Level 183 coreConstructors []XLogCoreConstructor 184 cores []xLogCore 185 } 186 187 func (cfg *loggerCfg) apply(l *xLogger) { 188 if cfg.encoderType != nil { 189 l.encoder = *cfg.encoderType 190 } else { 191 l.encoder = JSON 192 } 193 194 if cfg.level != nil { 195 l.dynamicLevelEnabler = zap.NewAtomicLevelAt(*cfg.level) 196 } else { 197 l.dynamicLevelEnabler = zap.NewAtomicLevelAt(getLogLevelOrDefault(os.Getenv("XLOG_LVL"))) 198 } 199 200 l.ctxFields = cfg.ctxFields 201 202 if cfg.lvlEncoder == nil { 203 cfg.lvlEncoder = zapcore.CapitalLevelEncoder 204 } 205 206 if cfg.tsEncoder == nil { 207 cfg.tsEncoder = zapcore.ISO8601TimeEncoder 208 } 209 210 if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 { 211 cfg.coreConstructors = []XLogCoreConstructor{ 212 newConsoleCore, 213 } 214 } 215 216 if cfg.ctx == nil { 217 cfg.ctx, l.cancelFn = context.WithCancel(context.Background()) 218 } else { 219 cfg.ctx, l.cancelFn = context.WithCancel(cfg.ctx) 220 } 221 222 cfg.cores = make([]xLogCore, 0, 16) 223 for _, cc := range cfg.coreConstructors { 224 cfg.cores = append(cfg.cores, cc( 225 cfg.ctx, 226 l.dynamicLevelEnabler, 227 l.encoder, 228 cfg.lvlEncoder, 229 cfg.tsEncoder, 230 )) 231 } 232 } 233 234 type XLoggerOption func(*loggerCfg) error 235 236 func NewXLogger(opts ...XLoggerOption) XLogger { 237 cfg := &loggerCfg{} 238 for _, o := range opts { 239 if o == nil { 240 continue 241 } 242 if err := o(cfg); err != nil { 243 panic(err) 244 } 245 } 246 xl := &xLogger{} 247 cfg.apply(xl) 248 249 // Disable zap logger error stack. 250 l := zap.New( 251 XLogTeeCore(cfg.cores...), 252 zap.AddCallerSkip(1), // Use caller filename as service 253 zap.AddCaller(), 254 ) 255 xl.logger.Store(l) 256 return xl 257 } 258 259 func WithXLoggerContext(ctx context.Context) XLoggerOption { 260 return func(cfg *loggerCfg) error { 261 cfg.ctx = ctx 262 return nil 263 } 264 } 265 266 func WithXLoggerStdOutWriter() XLoggerOption { 267 return func(cfg *loggerCfg) error { 268 if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 { 269 cfg.coreConstructors = make([]XLogCoreConstructor, 0, 8) 270 } 271 cfg.coreConstructors = append(cfg.coreConstructors, newConsoleCore) 272 return nil 273 } 274 } 275 276 func WithXLoggerFileWriter(coreCfg *FileCoreConfig) XLoggerOption { 277 return func(cfg *loggerCfg) error { 278 if cfg.coreConstructors == nil || len(cfg.coreConstructors) == 0 { 279 cfg.coreConstructors = make([]XLogCoreConstructor, 0, 8) 280 } 281 cfg.coreConstructors = append(cfg.coreConstructors, newFileCore(coreCfg)) 282 return nil 283 } 284 } 285 286 func WithXLoggerEncoder(logEnc logEncoderType) XLoggerOption { 287 return func(cfg *loggerCfg) error { 288 if logEnc == _encMax { 289 return infra.NewErrorStack("unknown xlogger encoder") 290 } 291 cfg.encoderType = &logEnc 292 return nil 293 } 294 } 295 296 func WithXLoggerLevel(lvl logLevel) XLoggerOption { 297 return func(cfg *loggerCfg) error { 298 _lvl := lvl.zapLevel() 299 cfg.level = &_lvl 300 return nil 301 } 302 } 303 304 func WithXLoggerLevelEncoder(lvlEnc zapcore.LevelEncoder) XLoggerOption { 305 return func(cfg *loggerCfg) error { 306 if lvlEnc == nil { 307 lvlEnc = zapcore.CapitalColorLevelEncoder 308 } 309 cfg.lvlEncoder = lvlEnc 310 return nil 311 } 312 } 313 314 func WithXLoggerTimeEncoder(tsEnc zapcore.TimeEncoder) XLoggerOption { 315 return func(cfg *loggerCfg) error { 316 if tsEnc == nil { 317 tsEnc = zapcore.ISO8601TimeEncoder 318 } 319 cfg.tsEncoder = tsEnc 320 return nil 321 } 322 } 323 324 func WithXLoggerContextFieldExtract(field string, mapTo ...string) XLoggerOption { 325 return func(cfg *loggerCfg) error { 326 if len(field) == 0 { 327 return nil 328 } 329 if cfg.ctxFields == nil { 330 cfg.ctxFields = kv.NewThreadSafeMap[string, string]() 331 } 332 if len(mapTo) == 0 || mapTo[0] == ContextKeyMapToItself { 333 mapTo = []string{field} 334 } 335 return cfg.ctxFields.AddOrUpdate(field, mapTo[0]) 336 } 337 } 338 339 func getLogLevelOrDefault(level string) zapcore.Level { 340 switch strings.ToUpper(level) { 341 case LogLevelInfo.String(): 342 return zapcore.InfoLevel 343 case LogLevelWarn.String(): 344 return zapcore.WarnLevel 345 case LogLevelError.String(): 346 return zapcore.ErrorLevel 347 case LogLevelDebug.String(): 348 fallthrough 349 default: 350 } 351 return zapcore.DebugLevel 352 } 353 354 func extractFieldsFromContext( 355 ctx context.Context, 356 targets kv.ThreadSafeStorer[string, string], 357 ) []zap.Field { 358 if ctx == nil || targets == nil { 359 return []zap.Field{} 360 } 361 362 keys := targets.ListKeys() 363 sort.StringSlice(keys).Sort() 364 newFields := make([]zap.Field, 0, len(keys)) 365 for _, key := range keys { 366 v := ctx.Value(key) 367 mapTo, _ := targets.Get(key) 368 if v == nil && mapTo != ContextKeyMapToOmitempty { 369 newFields = append(newFields, zap.String(mapTo, "nil")) 370 } else if v != nil && mapTo != ContextKeyMapToOmitempty { 371 newFields = append(newFields, zap.Any(mapTo, v)) 372 } 373 } 374 return newFields 375 }