package logx import ( "context" "strings" "sync" "time" "git.liteyuki.org/LiteyukiStudio/folium/osx" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // 支持的日志等级 type Level string const ( LevelTrace Level = "trace" LevelDebug Level = "debug" LevelInfo Level = "info" LevelWarn Level = "warn" LevelError Level = "error" LevelFatal Level = "fatal" LevelPanic Level = "panic" ) // Entry 表示一条日志的可传输结构 type Entry struct { Level Level `json:"level"` Time time.Time `json:"time"` Msg string `json:"msg"` Fields map[string]interface{} `json:"fields,omitempty"` } // Collector 是外部收集器接口(用户可实现) type Collector interface { Collect(ctx context.Context, e Entry) error Close() error } // Logger 支持本地打印并把日志发送到已注册的 collectors type Logger struct { zap *zap.Logger collectors []Collector mu sync.RWMutex wg sync.WaitGroup minLevel Level // 低于此级别的日志将被忽略 } // 全局单例 var ( Default *Logger defaultOnce sync.Once ) // buildLoggerFromEnv 基于环境构造 Logger(不设置全局变量) func buildLoggerFromEnv() *Logger { // 输出格式 output := strings.ToLower(osx.GetEnv("LOG_OUTPUT", "console")) cfg := zap.NewProductionConfig() if output == "json" { cfg.Encoding = "json" } else { cfg.Encoding = "console" } cfg.EncoderConfig.TimeKey = "ts" cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 让 zap 本身也服从 LOG_LEVEL levelStr := strings.ToLower(osx.GetEnv("LOG_LEVEL", osx.GetEnv("LOG_MIN_LEVEL", "info"))) var zapLevel zapcore.Level switch levelStr { case "trace", "debug": zapLevel = zapcore.DebugLevel case "info": zapLevel = zapcore.InfoLevel case "warn", "warning": zapLevel = zapcore.WarnLevel case "error": zapLevel = zapcore.ErrorLevel case "fatal": zapLevel = zapcore.FatalLevel case "panic": zapLevel = zapcore.PanicLevel default: zapLevel = zapcore.InfoLevel } cfg.Level = zap.NewAtomicLevelAt(zapLevel) // 可通过环境关闭 stacktrace 输出 if strings.ToLower(osx.GetEnv("LOG_DISABLE_STACKTRACE", "false")) == "true" { cfg.DisableStacktrace = true } zl, _ := cfg.Build() l := &Logger{ zap: zl, collectors: nil, minLevel: LevelInfo, } // 最低日志级别(同步 env) switch levelStr { case "trace": l.minLevel = LevelTrace case "debug": l.minLevel = LevelDebug case "info": l.minLevel = LevelInfo case "warn", "warning": l.minLevel = LevelWarn case "error": l.minLevel = LevelError case "fatal": l.minLevel = LevelFatal case "panic": l.minLevel = LevelPanic default: l.minLevel = LevelInfo } // 从环境加载并注册 collectors(实现位于 collector.go) cols := LoadCollectorsFromEnv() for _, c := range cols { l.RegisterCollector(c) } return l } // InitLogger 初始化全局单例(只第一次生效),返回单例 func InitLogger() *Logger { defaultOnce.Do(func() { Default = buildLoggerFromEnv() }) return Default } // GetLogger 返回全局单例,如果尚未初始化则会自动 InitLogger func GetLogger() *Logger { return InitLogger() } // 删除旧的 New / NewDefault,保留 New(...) 风格的局部 logger 创建函数改名为 NewLocal(可选) func NewLocal(zl *zap.Logger) *Logger { if zl == nil { // 使用一个轻量 zap 实例,不触碰全局单例 cfg := zap.NewProductionConfig() cfg.Encoding = "console" cfg.EncoderConfig.TimeKey = "ts" cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder zl2, _ := cfg.Build() return &Logger{ zap: zl2, minLevel: LevelDebug, collectors: nil, } } return &Logger{ zap: zl, minLevel: LevelDebug, collectors: nil, } } // RegisterCollector 在运行时注册一个 Collector(线程安全) func (l *Logger) RegisterCollector(c Collector) { if c == nil { return } l.mu.Lock() defer l.mu.Unlock() l.collectors = append(l.collectors, c) } // UnregisterCollector 注销已注册的 Collector(按指针相等匹配) func (l *Logger) UnregisterCollector(c Collector) { if c == nil { return } l.mu.Lock() defer l.mu.Unlock() for i, col := range l.collectors { if col == c { l.collectors = append(l.collectors[:i], l.collectors[i+1:]...) return } } } // ListCollectors 返回当前已注册的 collectors(副本,线程安全) func (l *Logger) ListCollectors() []Collector { l.mu.RLock() defer l.mu.RUnlock() cp := make([]Collector, len(l.collectors)) copy(cp, l.collectors) return cp } // Close 关闭所有 collectors(阻塞直到异步发送结束) func (l *Logger) Close() error { l.mu.RLock() collectors := append([]Collector(nil), l.collectors...) l.mu.RUnlock() // 等待 async sending 完成 l.wg.Wait() var firstErr error for _, c := range collectors { if err := c.Close(); err != nil && firstErr == nil { firstErr = err } } _ = l.zap.Sync() return firstErr } // SetLevel 设置最低日志级别,低于该级别的日志将被忽略 func (l *Logger) SetLevel(level Level) { l.mu.Lock() defer l.mu.Unlock() l.minLevel = level } // level 排序辅助 var levelOrder = map[Level]int{ LevelTrace: 0, LevelDebug: 1, LevelInfo: 2, LevelWarn: 3, LevelError: 4, LevelFatal: 5, LevelPanic: 6, } func (l *Logger) log(ctx context.Context, level Level, msg string, fields map[string]interface{}) { // 级别过滤 l.mu.RLock() min := l.minLevel l.mu.RUnlock() if levelOrder[level] < levelOrder[min] { return } // 本地打印(保持原有行为),并尽量保持 trace/level 字段 switch level { case LevelTrace: l.zap.Debug(msg, zap.String("level", string(LevelTrace)), zap.Any("fields", fields)) case LevelDebug: l.zap.Debug(msg, zap.Any("fields", fields)) case LevelInfo: l.zap.Info(msg, zap.Any("fields", fields)) case LevelWarn: l.zap.Warn(msg, zap.Any("fields", fields)) case LevelError: l.zap.Error(msg, zap.Any("fields", fields)) case LevelFatal: // Fatal 会调用 os.Exit(1) l.zap.Fatal(msg, zap.Any("fields", fields)) case LevelPanic: // Panic 会 panic l.zap.Panic(msg, zap.Any("fields", fields)) default: l.zap.Info(msg, zap.Any("level", level), zap.Any("fields", fields)) } // 向 collectors 异步发送 l.mu.RLock() collectors := append([]Collector(nil), l.collectors...) l.mu.RUnlock() if len(collectors) == 0 { return } e := Entry{ Level: level, Time: time.Now(), Msg: msg, Fields: fields, } for _, c := range collectors { c := c l.wg.Add(1) go func() { defer l.wg.Done() ctx2, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() if err := c.Collect(ctx2, e); err != nil { // collectors 的错误不影响主流程,记录到本地日志 l.zap.Warn("collector collect error", zap.Error(err)) } }() } } // 便捷方法(增加 Trace, Fatal, Panic) // 保留 Logger 的方法签名:需要 ctx func (l *Logger) Trace(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelTrace, msg, fields) } func (l *Logger) Debug(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelDebug, msg, fields) } func (l *Logger) Info(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelInfo, msg, fields) } func (l *Logger) Warn(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelWarn, msg, fields) } func (l *Logger) Error(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelError, msg, fields) } func (l *Logger) Fatal(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelFatal, msg, fields) } func (l *Logger) Panic(ctx context.Context, msg string, fields map[string]interface{}) { l.log(ctx, LevelPanic, msg, fields) } // SimpleLogger 提供不带 ctx 的简洁 API(可选 fields) type SimpleLogger struct { parent *Logger } func (s *SimpleLogger) Trace(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Trace(context.Background(), msg, f) } func (s *SimpleLogger) Debug(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Debug(context.Background(), msg, f) } func (s *SimpleLogger) Info(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Info(context.Background(), msg, f) } func (s *SimpleLogger) Warn(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Warn(context.Background(), msg, f) } func (s *SimpleLogger) Error(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Error(context.Background(), msg, f) } func (s *SimpleLogger) Fatal(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Fatal(context.Background(), msg, f) } func (s *SimpleLogger) Panic(msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } s.parent.Panic(context.Background(), msg, f) } // Simple 返回一个绑定到当前 Logger 的 SimpleLogger(不创建新实例) func (l *Logger) Simple() *SimpleLogger { return &SimpleLogger{parent: l} } // 包级简洁 API:无需 ctx,自动使用 background ctx(调用者更推荐显式使用 Ctx* 以保留 trace) func Trace(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Trace(msg, fields...) } func Debug(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Debug(msg, fields...) } func Info(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Info(msg, fields...) } func Warn(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Warn(msg, fields...) } func Error(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Error(msg, fields...) } func Fatal(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Fatal(msg, fields...) } func Panic(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Panic(msg, fields...) } // 包级带 ctx 的 API:当你有请求上下文(包含 trace)时使用 func CtxTrace(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Trace(ctx, msg, f) } func CtxDebug(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Debug(ctx, msg, f) } func CtxInfo(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Info(ctx, msg, f) } func CtxWarn(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Warn(ctx, msg, f) } func CtxError(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Error(ctx, msg, f) } func CtxFatal(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Fatal(ctx, msg, f) } func CtxPanic(ctx context.Context, msg string, fields ...map[string]interface{}) { var f map[string]interface{} if len(fields) > 0 { f = fields[0] } GetLogger().Panic(ctx, msg, f) }