package logger import ( "fmt" "io" "log" "os" "path/filepath" "sync" "git.toowon.com/jimmy/go-common/config" ) var ( // defaultLogger 全局默认日志记录器 // 用于中间件和其他需要快速日志记录的场景 defaultLogger *Logger defaultMux sync.RWMutex ) func init() { // 初始化默认logger(同步模式,输出到stdout) var err error defaultLogger, err = NewLogger(nil) if err != nil { // 如果初始化失败,使用nil,后续会降级到标准输出 defaultLogger = nil } } // logMessage 异步日志消息结构 type logMessage struct { level string // debug, info, warn, error format string args []interface{} fields map[string]interface{} // 用于带字段的日志 } // Logger 日志记录器 type Logger struct { infoLog *log.Logger errorLog *log.Logger warnLog *log.Logger debugLog *log.Logger config *config.LoggerConfig // 异步相关字段 async bool // 是否异步模式 logChan chan *logMessage // 异步日志channel done chan struct{} // 用于优雅关闭 wg sync.WaitGroup // 等待所有日志写入完成 closed bool // 是否已关闭 closeMux sync.RWMutex // 保护closed字段 } // NewLogger 创建日志记录器 func NewLogger(cfg *config.LoggerConfig) (*Logger, error) { if cfg == nil { // 使用默认配置 cfg = &config.LoggerConfig{ Level: "info", Output: "stdout", FilePath: "", Async: false, // 默认同步 BufferSize: 1000, // 默认缓冲区大小 } } // 设置默认值 if cfg.Level == "" { cfg.Level = "info" } if cfg.Output == "" { cfg.Output = "stdout" } if cfg.BufferSize <= 0 { cfg.BufferSize = 1000 // 默认缓冲区大小 } // 创建输出目标 var writers []io.Writer switch cfg.Output { case "stdout": writers = append(writers, os.Stdout) case "stderr": writers = append(writers, os.Stderr) case "file": if cfg.FilePath == "" { return nil, fmt.Errorf("file path is required when output is file") } // 确保目录存在 dir := filepath.Dir(cfg.FilePath) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create log directory: %w", err) } file, err := os.OpenFile(cfg.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } writers = append(writers, file) case "both": writers = append(writers, os.Stdout) if cfg.FilePath == "" { return nil, fmt.Errorf("file path is required when output is both") } dir := filepath.Dir(cfg.FilePath) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create log directory: %w", err) } file, err := os.OpenFile(cfg.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } writers = append(writers, file) default: return nil, fmt.Errorf("invalid output type: %s", cfg.Output) } // 创建多写入器 multiWriter := io.MultiWriter(writers...) // 创建日志前缀 prefix := "" if cfg.Prefix != "" { prefix = cfg.Prefix + " " } // 创建日志记录器 logger := &Logger{ config: cfg, async: cfg.Async, } // 根据日志级别创建不同的logger flags := log.LstdFlags if cfg.DisableTimestamp { flags = 0 } if cfg.Level == "debug" || cfg.Level == "info" || cfg.Level == "warn" || cfg.Level == "error" { logger.infoLog = log.New(multiWriter, prefix+"[INFO] ", flags) logger.warnLog = log.New(multiWriter, prefix+"[WARN] ", flags) logger.errorLog = log.New(multiWriter, prefix+"[ERROR] ", flags) if cfg.Level == "debug" { logger.debugLog = log.New(multiWriter, prefix+"[DEBUG] ", flags) } } // 如果启用异步模式,启动goroutine处理日志 if cfg.Async { logger.logChan = make(chan *logMessage, cfg.BufferSize) logger.done = make(chan struct{}) logger.wg.Add(1) go logger.processLogs() } return logger, nil } // processLogs 异步处理日志(goroutine) func (l *Logger) processLogs() { defer l.wg.Done() for { select { case msg := <-l.logChan: if msg == nil { // channel已关闭,退出 return } l.writeLog(msg) case <-l.done: // 收到关闭信号,处理完剩余日志后退出 for { select { case msg := <-l.logChan: if msg == nil { return } l.writeLog(msg) default: return } } } } } // writeLog 实际写入日志(内部方法) func (l *Logger) writeLog(msg *logMessage) { var logger *log.Logger switch msg.level { case "debug": logger = l.debugLog case "info": logger = l.infoLog case "warn": logger = l.warnLog case "error": logger = l.errorLog default: return } if logger == nil { return } // 如果有字段,先格式化字段 format := msg.format if len(msg.fields) > 0 { fieldStr := formatFields(msg.fields) format = fieldStr + format } // 写入日志 logger.Printf(format, msg.args...) } // isClosed 检查logger是否已关闭 func (l *Logger) isClosed() bool { l.closeMux.RLock() defer l.closeMux.RUnlock() return l.closed } // setClosed 设置logger为已关闭状态 func (l *Logger) setClosed() { l.closeMux.Lock() defer l.closeMux.Unlock() l.closed = true } // log 内部日志方法,根据模式选择同步或异步 func (l *Logger) log(level string, format string, args []interface{}, fields map[string]interface{}) { // 如果已关闭,直接返回 if l.isClosed() { return } // 如果是异步模式,发送到channel if l.async { // 检查channel是否已关闭 select { case l.logChan <- &logMessage{ level: level, format: format, args: args, fields: fields, }: // 成功发送 default: // channel已满或已关闭,同步写入(降级处理) l.writeLog(&logMessage{ level: level, format: format, args: args, fields: fields, }) } } else { // 同步模式,直接写入 l.writeLog(&logMessage{ level: level, format: format, args: args, fields: fields, }) } } // Debug 记录调试日志 func (l *Logger) Debug(format string, v ...interface{}) { l.log("debug", format, v, nil) } // Info 记录信息日志 func (l *Logger) Info(format string, v ...interface{}) { l.log("info", format, v, nil) } // Warn 记录警告日志 func (l *Logger) Warn(format string, v ...interface{}) { l.log("warn", format, v, nil) } // Error 记录错误日志 func (l *Logger) Error(format string, v ...interface{}) { l.log("error", format, v, nil) } // Fatal 记录致命错误日志并退出程序(始终同步) func (l *Logger) Fatal(format string, v ...interface{}) { // Fatal必须同步执行,确保日志写入后再退出 if l.errorLog != nil { l.errorLog.Fatalf(format, v...) } else { log.Fatalf(format, v...) } } // Panic 记录恐慌日志并触发panic(始终同步) func (l *Logger) Panic(format string, v ...interface{}) { // Panic必须同步执行,确保日志写入后再panic if l.errorLog != nil { l.errorLog.Panicf(format, v...) } else { log.Panicf(format, v...) } } // WithFields 创建带字段的日志记录器(简化版,返回格式化字符串) func (l *Logger) WithFields(fields map[string]interface{}) *Logger { // 返回自身,实际使用时可以在format中包含fields return l } // formatFields 格式化字段 func formatFields(fields map[string]interface{}) string { if len(fields) == 0 { return "" } result := "" for k, v := range fields { result += fmt.Sprintf("%s=%v ", k, v) } return result } // Debugf 记录调试日志(带字段) func (l *Logger) Debugf(fields map[string]interface{}, format string, v ...interface{}) { l.log("debug", format, v, fields) } // Infof 记录信息日志(带字段) func (l *Logger) Infof(fields map[string]interface{}, format string, v ...interface{}) { l.log("info", format, v, fields) } // Warnf 记录警告日志(带字段) func (l *Logger) Warnf(fields map[string]interface{}, format string, v ...interface{}) { l.log("warn", format, v, fields) } // Errorf 记录错误日志(带字段) func (l *Logger) Errorf(fields map[string]interface{}, format string, v ...interface{}) { l.log("error", format, v, fields) } // Close 优雅关闭logger(仅异步模式需要) // 等待所有日志写入完成后再返回 func (l *Logger) Close() error { if !l.async { // 同步模式不需要关闭 return nil } // 检查是否已关闭 if l.isClosed() { return nil } // 设置关闭状态 l.setClosed() // 发送关闭信号 close(l.done) // 关闭channel(会触发processLogs退出) close(l.logChan) // 等待所有日志写入完成 l.wg.Wait() return nil } // ========== 全局默认Logger相关方法 ========== // SetDefaultLogger 设置全局默认logger // 用于在应用启动时统一配置logger func SetDefaultLogger(log *Logger) { defaultMux.Lock() defer defaultMux.Unlock() // 如果之前有logger,先关闭它 if defaultLogger != nil { defaultLogger.Close() } defaultLogger = log } // GetDefaultLogger 获取全局默认logger func GetDefaultLogger() *Logger { defaultMux.RLock() defer defaultMux.RUnlock() return defaultLogger } // Default 全局日志方法 - Debug func Default() *Logger { return GetDefaultLogger() } // ========== 全局便捷日志方法 ========== // 以下方法使用全局默认logger,方便快速记录日志 // Debug 使用全局logger记录调试日志 func Debug(format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Debug(format, v...) } } // Info 使用全局logger记录信息日志 func Info(format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Info(format, v...) } } // Warn 使用全局logger记录警告日志 func Warn(format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Warn(format, v...) } } // Error 使用全局logger记录错误日志 func Error(format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Error(format, v...) } } // Debugf 使用全局logger记录调试日志(带字段) func Debugf(fields map[string]interface{}, format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Debugf(fields, format, v...) } } // Infof 使用全局logger记录信息日志(带字段) func Infof(fields map[string]interface{}, format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Infof(fields, format, v...) } } // Warnf 使用全局logger记录警告日志(带字段) func Warnf(fields map[string]interface{}, format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Warnf(fields, format, v...) } } // Errorf 使用全局logger记录错误日志(带字段) func Errorf(fields map[string]interface{}, format string, v ...interface{}) { if log := GetDefaultLogger(); log != nil { log.Errorf(fields, format, v...) } }