github.com/wfusion/gofusion@v1.1.14/log/logger.go (about) 1 package log 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "sync" 8 "syscall" 9 10 "github.com/pkg/errors" 11 "go.uber.org/zap" 12 "go.uber.org/zap/zapcore" 13 14 "github.com/wfusion/gofusion/common/infra/watermill" 15 "github.com/wfusion/gofusion/common/utils" 16 "github.com/wfusion/gofusion/config" 17 "github.com/wfusion/gofusion/log/encoder" 18 19 fusCtx "github.com/wfusion/gofusion/context" 20 ) 21 22 var ( 23 globalLogger = defaultLogger(false) 24 25 rwlock = new(sync.RWMutex) 26 appInstances map[string]map[string]*logger 27 ) 28 29 type logger struct { 30 name string 31 logger *zap.Logger 32 sugaredLogger *zap.SugaredLogger 33 } 34 35 type useOption struct { 36 appName string 37 } 38 39 func AppName(name string) utils.OptionFunc[useOption] { 40 return func(o *useOption) { 41 o.appName = name 42 } 43 } 44 45 func Use(name string, opts ...utils.OptionExtender) Loggable { 46 opt := utils.ApplyOptions[useOption](opts...) 47 48 rwlock.RLock() 49 defer rwlock.RUnlock() 50 instances, ok := appInstances[opt.appName] 51 if !ok { 52 globalLogger.Debug(context.Background(), "%v [Gofusion] %s instance not found for app: %s", 53 syscall.Getpid(), config.ComponentLog, opt.appName, Fields{"component": "log"}) 54 return globalLogger 55 } 56 instance, ok := instances[name] 57 if !ok { 58 instance, ok = instances[config.DefaultInstanceKey] 59 if ok { 60 instance.Debug(context.Background(), "%v [Gofusion] %s instance not found for name: %s", 61 syscall.Getpid(), config.ComponentLog, name, Fields{"component": "log"}) 62 return instance 63 } 64 globalLogger.Debug(context.Background(), "%v [Gofusion] %s instance not found for name: %s", 65 syscall.Getpid(), config.ComponentLog, name, Fields{"component": "log"}) 66 return globalLogger 67 } 68 69 return instance 70 } 71 72 func defaultLogger(colorful bool) Loggable { 73 devCfg := zap.NewDevelopmentConfig() 74 devCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) 75 if colorful { 76 devCfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder 77 } 78 devCfg.EncoderConfig.EncodeCaller = encoder.SkipCallerEncoder(encoder.SkipCallers, true) 79 zapLogger, _ := devCfg.Build( 80 zap.AddStacktrace(zap.PanicLevel), 81 zap.AddCaller(), 82 zap.Hooks(), 83 ) 84 return &logger{ 85 logger: zapLogger, 86 sugaredLogger: zapLogger.Sugar(), 87 } 88 } 89 func (l *logger) Debug(ctx context.Context, format string, args ...any) { 90 lg, msg, fields := l.sweeten(ctx, format, args...) 91 if logger, ok := lg.(*logger); ok { 92 logger.logger.Debug(msg, fields...) 93 } else { 94 lg.Debug(ctx, msg, args...) 95 } 96 } 97 func (l *logger) Info(ctx context.Context, format string, args ...any) { 98 lg, msg, fields := l.sweeten(ctx, format, args...) 99 if logger, ok := lg.(*logger); ok { 100 logger.logger.Info(msg, fields...) 101 } else { 102 lg.Info(ctx, msg, args...) 103 } 104 } 105 func (l *logger) Warn(ctx context.Context, format string, args ...any) { 106 lg, msg, fields := l.sweeten(ctx, format, args...) 107 if logger, ok := lg.(*logger); ok { 108 logger.logger.Warn(msg, fields...) 109 } else { 110 lg.Warn(ctx, msg, args...) 111 } 112 } 113 func (l *logger) Error(ctx context.Context, format string, args ...any) { 114 lg, msg, fields := l.sweeten(ctx, format, args...) 115 if logger, ok := lg.(*logger); ok { 116 logger.logger.Error(msg, fields...) 117 } else { 118 lg.Error(ctx, msg, args...) 119 } 120 } 121 func (l *logger) Panic(ctx context.Context, format string, args ...any) { 122 lg, msg, fields := l.sweeten(ctx, format, args...) 123 if logger, ok := lg.(*logger); ok { 124 logger.logger.Panic(msg, fields...) 125 } else { 126 lg.Panic(ctx, msg, args...) 127 } 128 } 129 func (l *logger) Fatal(ctx context.Context, format string, args ...any) { 130 lg, msg, fields := l.sweeten(ctx, format, args...) 131 if logger, ok := lg.(*logger); ok { 132 logger.logger.Fatal(msg, fields...) 133 } else { 134 lg.Fatal(ctx, msg, args...) 135 } 136 } 137 func (l *logger) Level(ctx context.Context) Level { 138 ctxLogger := GetCtxLogger(ctx, l) 139 lg, ok := ctxLogger.(*logger) 140 if !ok { 141 return InvalidLevel 142 } 143 switch lg.logger.Level() { 144 case zap.DebugLevel: 145 return DebugLevel 146 case zap.InfoLevel: 147 return InfoLevel 148 case zap.WarnLevel: 149 return WarnLevel 150 case zap.ErrorLevel: 151 return ErrorLevel 152 case zap.PanicLevel: 153 return PanicLevel 154 case zap.FatalLevel: 155 return FatalLevel 156 default: 157 return InfoLevel 158 } 159 } 160 func (l *logger) flush() { 161 ignore := func(err error) bool { 162 // ENOTTY: 163 // ignore sync /dev/stdout: inappropriate ioctl for device errors, 164 // which happens when redirect stderr to stdout 165 // EINVAL: 166 // ignore sync /dev/stdout: invalid argument 167 for _, target := range []error{syscall.EINVAL, syscall.ENOTTY} { 168 if errors.Is(err, target) { 169 return true 170 } 171 } 172 return false 173 } 174 175 pid := syscall.Getpid() 176 if _, err := utils.Catch(l.logger.Sync); err != nil && !ignore(err) { 177 log.Printf("%v [Gofusion] %s flush %s logger error: %s", pid, config.ComponentLog, l.name, err) 178 } 179 if _, err := utils.Catch(l.sugaredLogger.Sync); err != nil && !ignore(err) { 180 log.Printf("%v [Gofusion] %s flush %s sugared logger error: %s", 181 pid, config.ComponentLog, l.name, err) 182 } 183 } 184 185 func (l *logger) sweeten(ctx context.Context, format string, raw ...any) ( 186 log Loggable, msg string, fields []zap.Field) { 187 args := make([]any, 0, len(raw)) 188 fields = getContextZapFields(ctx) 189 for _, arg := range raw { 190 if f, ok := arg.(Fields); ok { 191 fields = append(fields, convertFieldsToZapFields(f)...) 192 continue 193 } 194 args = append(args, arg) 195 } 196 197 msg = fmt.Sprintf(format, args...) 198 if userID := fusCtx.GetUserID(ctx); utils.IsStrNotBlank(userID) { 199 fields = append(fields, zap.String("user_id", userID)) 200 } 201 if traceID := fusCtx.GetTraceID(ctx); utils.IsStrNotBlank(traceID) { 202 fields = append(fields, zap.String("trace_id", traceID)) 203 } 204 if taskID := fusCtx.GetCronTaskID(ctx); utils.IsStrNotBlank(taskID) { 205 fields = append(fields, zap.String("cron_task_id", taskID)) 206 } 207 if taskName := fusCtx.GetCronTaskName(ctx); utils.IsStrNotBlank(taskName) { 208 fields = append(fields, zap.String("cron_task_name", taskName)) 209 } 210 if id := utils.GetCtxAny[string](ctx, watermill.ContextKeyMessageUUID); utils.IsStrNotBlank(id) { 211 fields = append(fields, zap.String("message_uuid", id)) 212 } 213 if id := utils.GetCtxAny[string](ctx, watermill.ContextKeyRawMessageID); utils.IsStrNotBlank(id) { 214 fields = append(fields, zap.String("message_raw_id", id)) 215 } 216 217 log = GetCtxLogger(ctx, l) 218 return 219 }