github.com/openimsdk/tools@v0.0.49/log/zap.go (about) 1 // Copyright © 2023 OpenIM. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package log 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 "time" 23 24 rotatelogs "github.com/openimsdk/tools/log/file-rotatelogs" 25 "github.com/openimsdk/tools/utils/stringutil" 26 27 "go.uber.org/zap" 28 "go.uber.org/zap/zapcore" 29 30 "github.com/openimsdk/protocol/constant" 31 "github.com/openimsdk/tools/mcontext" 32 ) 33 34 type LogFormatter interface { 35 Format() any 36 } 37 38 var ( 39 pkgLogger Logger 40 osStdout Logger 41 sp = string(filepath.Separator) 42 logLevelMap = map[int]zapcore.Level{ 43 6: zapcore.DebugLevel, 44 5: zapcore.DebugLevel, 45 4: zapcore.InfoLevel, 46 3: zapcore.WarnLevel, 47 2: zapcore.ErrorLevel, 48 1: zapcore.FatalLevel, 49 0: zapcore.PanicLevel, 50 } 51 ) 52 53 const callDepth = 2 54 const hoursPerDay = 24 55 56 // InitFromConfig initializes a Zap-based logger. 57 func InitFromConfig( 58 loggerPrefixName, moduleName string, 59 logLevel int, 60 isStdout bool, 61 isJson bool, 62 logLocation string, 63 rotateCount uint, 64 rotationTime uint, 65 moduleVersion string, 66 isSimplify bool, 67 ) error { 68 l, err := NewZapLogger(loggerPrefixName, moduleName, logLevel, isStdout, isJson, logLocation, 69 rotateCount, rotationTime, moduleVersion, isSimplify) 70 if err != nil { 71 return err 72 } 73 pkgLogger = l.WithCallDepth(callDepth) 74 if isJson { 75 pkgLogger = pkgLogger.WithName(moduleName) 76 } 77 return nil 78 } 79 80 // InitConsoleLogger init osStdout and osStderr. 81 func InitConsoleLogger(moduleName string, 82 logLevel int, 83 isJson bool, moduleVersion string) error { 84 l, err := NewConsoleZapLogger(moduleName, logLevel, isJson, moduleVersion, os.Stdout) 85 if err != nil { 86 return err 87 } 88 osStdout = l.WithCallDepth(callDepth) 89 if isJson { 90 osStdout = osStdout.WithName(moduleName) 91 } 92 return nil 93 94 } 95 96 func ZDebug(ctx context.Context, msg string, keysAndValues ...any) { 97 if pkgLogger == nil { 98 return 99 } 100 pkgLogger.Debug(ctx, msg, keysAndValues...) 101 } 102 103 func ZInfo(ctx context.Context, msg string, keysAndValues ...any) { 104 if pkgLogger == nil { 105 return 106 } 107 pkgLogger.Info(ctx, msg, keysAndValues...) 108 } 109 110 func ZWarn(ctx context.Context, msg string, err error, keysAndValues ...any) { 111 if pkgLogger == nil { 112 return 113 } 114 pkgLogger.Warn(ctx, msg, err, keysAndValues...) 115 } 116 117 func ZError(ctx context.Context, msg string, err error, keysAndValues ...any) { 118 if pkgLogger == nil { 119 return 120 } 121 pkgLogger.Error(ctx, msg, err, keysAndValues...) 122 } 123 124 func CInfo(ctx context.Context, msg string, keysAndValues ...any) { 125 if osStdout == nil { 126 return 127 } 128 osStdout.Info(ctx, msg, keysAndValues...) 129 } 130 131 type ZapLogger struct { 132 zap *zap.SugaredLogger 133 level zapcore.Level 134 moduleName string 135 moduleVersion string 136 loggerPrefixName string 137 rotationTime time.Duration 138 isSimplify bool 139 } 140 141 func NewZapLogger( 142 loggerPrefixName, moduleName string, 143 logLevel int, 144 isStdout bool, 145 isJson bool, 146 logLocation string, 147 rotateCount uint, 148 rotationTime uint, 149 moduleVersion string, 150 isSimplify bool, 151 ) (*ZapLogger, error) { 152 zapConfig := zap.Config{ 153 Level: zap.NewAtomicLevelAt(logLevelMap[logLevel]), 154 DisableStacktrace: true, 155 } 156 if isJson { 157 zapConfig.Encoding = "json" 158 } else { 159 zapConfig.Encoding = "console" 160 } 161 zl := &ZapLogger{level: logLevelMap[logLevel], moduleName: moduleName, loggerPrefixName: loggerPrefixName, 162 rotationTime: time.Duration(rotationTime) * time.Hour, moduleVersion: moduleVersion, isSimplify: isSimplify} 163 opts, err := zl.cores(isStdout, isJson, logLocation, rotateCount) 164 if err != nil { 165 return nil, err 166 } 167 l, err := zapConfig.Build(opts) 168 if err != nil { 169 return nil, err 170 } 171 zl.zap = l.Sugar() 172 return zl, nil 173 } 174 175 func NewConsoleZapLogger( 176 moduleName string, 177 logLevel int, 178 isJson bool, 179 moduleVersion string, 180 outPut *os.File) (*ZapLogger, error) { 181 zapConfig := zap.Config{ 182 Level: zap.NewAtomicLevelAt(logLevelMap[logLevel]), 183 DisableStacktrace: true, 184 } 185 if isJson { 186 zapConfig.Encoding = "json" 187 } else { 188 zapConfig.Encoding = "console" 189 } 190 zl := &ZapLogger{level: logLevelMap[logLevel], moduleName: moduleName, moduleVersion: moduleVersion} 191 opts, err := zl.consoleCores(outPut, isJson) 192 if err != nil { 193 return nil, err 194 } 195 l, err := zapConfig.Build(opts) 196 if err != nil { 197 return nil, err 198 } 199 zl.zap = l.Sugar() 200 return zl, nil 201 } 202 func (l *ZapLogger) cores(isStdout bool, isJson bool, logLocation string, rotateCount uint) (zap.Option, error) { 203 c := zap.NewProductionEncoderConfig() 204 c.EncodeTime = l.timeEncoder 205 c.EncodeDuration = zapcore.SecondsDurationEncoder 206 c.MessageKey = "msg" 207 c.LevelKey = "level" 208 c.TimeKey = "time" 209 c.CallerKey = "caller" 210 c.NameKey = "logger" 211 var fileEncoder zapcore.Encoder 212 if isJson { 213 c.EncodeLevel = zapcore.CapitalLevelEncoder 214 fileEncoder = zapcore.NewJSONEncoder(c) 215 fileEncoder.AddInt("PID", os.Getpid()) 216 fileEncoder.AddString("version", l.moduleVersion) 217 } else { 218 c.EncodeLevel = l.capitalColorLevelEncoder 219 c.EncodeCaller = l.customCallerEncoder 220 fileEncoder = zapcore.NewConsoleEncoder(c) 221 } 222 fileEncoder = &alignEncoder{Encoder: fileEncoder} 223 writer, err := l.getWriter(logLocation, rotateCount) 224 if err != nil { 225 return nil, err 226 } 227 var cores []zapcore.Core 228 if logLocation != "" { 229 cores = []zapcore.Core{ 230 zapcore.NewCore(fileEncoder, writer, zap.NewAtomicLevelAt(l.level)), 231 } 232 } 233 if isStdout { 234 cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(os.Stdout), zap.NewAtomicLevelAt(l.level))) 235 // cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(os.Stderr), zap.NewAtomicLevelAt(l.level))) 236 } 237 return zap.WrapCore(func(c zapcore.Core) zapcore.Core { 238 return zapcore.NewTee(cores...) 239 }), nil 240 } 241 242 func (l *ZapLogger) consoleCores(outPut *os.File, isJson bool) (zap.Option, error) { 243 c := zap.NewProductionEncoderConfig() 244 c.EncodeTime = l.timeEncoder 245 c.EncodeDuration = zapcore.SecondsDurationEncoder 246 c.MessageKey = "msg" 247 c.LevelKey = "level" 248 c.TimeKey = "time" 249 c.CallerKey = "caller" 250 c.NameKey = "logger" 251 var fileEncoder zapcore.Encoder 252 if isJson { 253 c.EncodeLevel = zapcore.CapitalLevelEncoder 254 fileEncoder = zapcore.NewJSONEncoder(c) 255 fileEncoder.AddInt("PID", os.Getpid()) 256 fileEncoder.AddString("version", l.moduleVersion) 257 } else { 258 c.EncodeLevel = l.capitalColorLevelEncoder 259 c.EncodeCaller = l.customCallerEncoder 260 fileEncoder = zapcore.NewConsoleEncoder(c) 261 } 262 var cores []zapcore.Core 263 cores = append(cores, zapcore.NewCore(fileEncoder, zapcore.Lock(outPut), zap.NewAtomicLevelAt(l.level))) 264 265 return zap.WrapCore(func(c zapcore.Core) zapcore.Core { 266 return zapcore.NewTee(cores...) 267 }), nil 268 } 269 270 func (l *ZapLogger) customCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 271 fixedLength := 50 272 trimmedPath := caller.TrimmedPath() 273 trimmedPath = "[" + trimmedPath + "]" 274 s := stringutil.FormatString(trimmedPath, fixedLength, true) 275 enc.AppendString(s) 276 } 277 278 func (l *ZapLogger) timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 279 layout := "2006-01-02 15:04:05.000" 280 type appendTimeEncoder interface { 281 AppendTimeLayout(time.Time, string) 282 } 283 if enc, ok := enc.(appendTimeEncoder); ok { 284 enc.AppendTimeLayout(t, layout) 285 return 286 } 287 enc.AppendString(t.Format(layout)) 288 } 289 290 func (l *ZapLogger) getWriter(logLocation string, rorateCount uint) (zapcore.WriteSyncer, error) { 291 var path string 292 if l.rotationTime%(time.Hour*time.Duration(hoursPerDay)) == 0 { 293 path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d" 294 } else if l.rotationTime%time.Hour == 0 { 295 path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d_%H" 296 } else { 297 path = logLocation + sp + l.loggerPrefixName + ".%Y-%m-%d_%H_%M_%S" 298 } 299 logf, err := rotatelogs.New(path, 300 rotatelogs.WithRotationCount(rorateCount), 301 rotatelogs.WithRotationTime(l.rotationTime), 302 ) 303 if err != nil { 304 return nil, err 305 } 306 return zapcore.AddSync(logf), nil 307 } 308 309 func (l *ZapLogger) capitalColorLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { 310 s, ok := _levelToCapitalColorString[level] 311 if !ok { 312 s = _unknownLevelColor[zapcore.ErrorLevel] 313 } 314 pid := stringutil.FormatString(fmt.Sprintf("["+"PID:"+"%d"+"]", os.Getpid()), 15, true) 315 color := _levelToColor[level] 316 enc.AppendString(s) 317 enc.AppendString(color.Add(pid)) 318 if l.moduleName != "" { 319 moduleName := stringutil.FormatString(l.moduleName, 25, true) 320 enc.AppendString(color.Add(moduleName)) 321 } 322 if l.moduleVersion != "" { 323 moduleVersion := stringutil.FormatString(fmt.Sprintf("["+"version:"+"%s"+"]", l.moduleVersion), 17, true) 324 enc.AppendString(moduleVersion) 325 } 326 } 327 328 func (l *ZapLogger) ToZap() *zap.SugaredLogger { 329 return l.zap 330 } 331 332 func (l *ZapLogger) Debug(ctx context.Context, msg string, keysAndValues ...any) { 333 if l.level > zapcore.DebugLevel { 334 return 335 } 336 keysAndValues = l.kvAppend(ctx, keysAndValues) 337 l.zap.Debugw(msg, keysAndValues...) 338 } 339 340 func (l *ZapLogger) Info(ctx context.Context, msg string, keysAndValues ...any) { 341 if l.level > zapcore.InfoLevel { 342 return 343 } 344 keysAndValues = l.kvAppend(ctx, keysAndValues) 345 l.zap.Infow(msg, keysAndValues...) 346 } 347 348 func (l *ZapLogger) Warn(ctx context.Context, msg string, err error, keysAndValues ...any) { 349 if l.level > zapcore.WarnLevel { 350 return 351 } 352 if err != nil { 353 keysAndValues = append(keysAndValues, "error", err.Error()) 354 } 355 keysAndValues = l.kvAppend(ctx, keysAndValues) 356 l.zap.Warnw(msg, keysAndValues...) 357 } 358 359 func (l *ZapLogger) Error(ctx context.Context, msg string, err error, keysAndValues ...any) { 360 if l.level > zapcore.ErrorLevel { 361 return 362 } 363 if err != nil { 364 keysAndValues = append(keysAndValues, "error", err.Error()) 365 } 366 keysAndValues = l.kvAppend(ctx, keysAndValues) 367 l.zap.Errorw(msg, keysAndValues...) 368 } 369 370 func (l *ZapLogger) kvAppend(ctx context.Context, keysAndValues []any) []any { 371 if ctx == nil { 372 return keysAndValues 373 } 374 operationID := mcontext.GetOperationID(ctx) 375 opUserID := mcontext.GetOpUserID(ctx) 376 connID := mcontext.GetConnID(ctx) 377 triggerID := mcontext.GetTriggerID(ctx) 378 opUserPlatform := mcontext.GetOpUserPlatform(ctx) 379 remoteAddr := mcontext.GetRemoteAddr(ctx) 380 381 if l.isSimplify { 382 if len(keysAndValues)%2 == 0 { 383 for i := 1; i < len(keysAndValues); i += 2 { 384 385 if val, ok := keysAndValues[i].(LogFormatter); ok && val != nil { 386 keysAndValues[i] = val.Format() 387 } 388 } 389 } else { 390 ZError(ctx, "keysAndValues length is not even", nil) 391 } 392 } 393 394 for i := 1; i < len(keysAndValues); i += 2 { 395 if s, ok := keysAndValues[i].(interface{ String() string }); ok { 396 keysAndValues[i] = s.String() 397 } 398 } 399 400 if opUserID != "" { 401 keysAndValues = append([]any{constant.OpUserID, opUserID}, keysAndValues...) 402 } 403 if operationID != "" { 404 keysAndValues = append([]any{constant.OperationID, operationID}, keysAndValues...) 405 } 406 if connID != "" { 407 keysAndValues = append([]any{constant.ConnID, connID}, keysAndValues...) 408 } 409 if triggerID != "" { 410 keysAndValues = append([]any{constant.TriggerID, triggerID}, keysAndValues...) 411 } 412 if opUserPlatform != "" { 413 keysAndValues = append([]any{constant.OpUserPlatform, opUserPlatform}, keysAndValues...) 414 } 415 if remoteAddr != "" { 416 keysAndValues = append([]any{constant.RemoteAddr, remoteAddr}, keysAndValues...) 417 } 418 return keysAndValues 419 } 420 421 func (l *ZapLogger) WithValues(keysAndValues ...any) Logger { 422 dup := *l 423 dup.zap = l.zap.With(keysAndValues...) 424 return &dup 425 } 426 427 func (l *ZapLogger) WithName(name string) Logger { 428 dup := *l 429 dup.zap = l.zap.Named(name) 430 return &dup 431 } 432 433 func (l *ZapLogger) WithCallDepth(depth int) Logger { 434 dup := *l 435 dup.zap = l.zap.WithOptions(zap.AddCallerSkip(depth)) 436 return &dup 437 }