// logger/logger.go package logger import ( "bytes" "fmt" "io" "os" "path/filepath" "runtime" "strings" "sync" "github.com/sirupsen/logrus" ) // -------------------------- 1. ANSI 颜色码常量(关键) -------------------------- const ( // 颜色重置 ColorReset = "\033[0m" // 灰色(日期、文件行号) ColorGray = "\033[90m" // 日志级别颜色 ColorDebug = "\033[36m" // 青色 [d] ColorInfo = "\033[32m" // 绿色 [i] ColorWarn = "\033[33m" // 黄色 [w] ColorError = "\033[31m" // 红色 [e] ColorFatal = "\033[35m" // 紫色 [f] ) // -------------------------- 1. 定义日志接口 -------------------------- // Logger 日志核心接口,定义所有需要的日志方法 // 所有模块都依赖这个接口,而非具体实现 type Logger interface { Debug(args ...interface{}) Debugf(format string, args ...interface{}) Info(args ...interface{}) Infof(format string, args ...interface{}) Warn(args ...interface{}) Warnf(format string, args ...interface{}) Error(args ...interface{}) Errorf(format string, args ...interface{}) Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) } // -------------------------- 2. 基于logrus的默认实现 -------------------------- // logrusLogger 是 Logger 接口的具体实现(基于logrus) type logrusLogger struct { *logrus.Logger } // 实现 Logger 接口的所有方法(直接转发给logrus) func (l *logrusLogger) Debug(args ...interface{}) { l.Logger.Debug(args...) } func (l *logrusLogger) Debugf(format string, args ...interface{}) { l.Logger.Debugf(format, args...) } func (l *logrusLogger) Info(args ...interface{}) { l.Logger.Info(args...) } func (l *logrusLogger) Infof(format string, args ...interface{}) { l.Logger.Infof(format, args...) } func (l *logrusLogger) Warn(args ...interface{}) { l.Logger.Warn(args...) } func (l *logrusLogger) Warnf(format string, args ...interface{}) { l.Logger.Warnf(format, args...) } func (l *logrusLogger) Error(args ...interface{}) { l.Logger.Error(args...) } func (l *logrusLogger) Errorf(format string, args ...interface{}) { l.Logger.Errorf(format, args...) } func (l *logrusLogger) Fatal(args ...interface{}) { l.Logger.Fatal(args...) } func (l *logrusLogger) Fatalf(format string, args ...interface{}) { l.Logger.Fatalf(format, args...) } // -------------------------- 3. 全局默认实例 + 初始化逻辑 -------------------------- var ( // DefaultLogger 全局默认日志实例(所有模块可直接用,也可注入自定义实现) DefaultLogger Logger // once 保证日志只初始化一次 initOnce sync.Once ) // LogConfig 日志配置结构体(和项目的config包对齐) type LogConfig struct { Level string `mapstructure:"level" yaml:"level"` Format string `mapstructure:"format" yaml:"format"` Output string `mapstructure:"output" yaml:"output"` Verbose bool `mapstructure:"verbose" yaml:"verbose"` LogFile string `mapstructure:"log_file" yaml:"log_file"` ShowColor bool `mapstructure:"show_color" yaml:"show_color"` } // 默认配置 var defaultConfig = LogConfig{ Level: "info", Format: "text", Output: "stdout", Verbose: false, ShowColor: true, LogFile: "/var/log/sunhpc/sunhpc.log", } // -------------------------- 5. 自定义Formatter(核心配色逻辑) -------------------------- type CustomFormatter struct { ShowColor bool // 是否显示彩色输出 } // Format 实现 logrus.Formatter 接口,自定义日志格式和颜色 func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) { // 1. 获取日志级别标识和对应颜色 levelStr, levelColor := getLevelInfo(entry.Level) // 2. 获取调用文件和行号(简化路径,只保留文件名+行号) file, line := getCallerInfo() // 3. 格式化时间(灰色) timeStr := entry.Time.Format("2006-01-02 15:04:05") // 构建日志行(按你的格式:日期 [级别] 内容 文件:行号) var buf bytes.Buffer // 颜色开关:如果禁用颜色,所有颜色码置空 colorReset := ColorReset colorGray := ColorGray if !f.ShowColor { levelColor = "" colorGray = "" colorReset = "" } // Debug,打印原始字节,用于调试 // fmt.Printf("%q\n", entry.Message) // 拼接格式: // 灰色日期 + 空格 + 带颜色的[级别] + 空格 + 日志内容 + 空格 + 灰色文件行号 + 重置 fmt.Fprintf(&buf, "%s%s%s %s[%s]%s %s %s%s:%d%s\n", colorGray, // 日期开始灰色 timeStr, // 日期字符串 colorReset, // 日期结束重置颜色 levelColor, // 级别标识颜色 levelStr, // 级别标识(i/d/e/w/f) colorReset, // 级别标识结束重置 entry.Message, // 日志内容(默认色) colorGray, // 文件行号开始灰色 file, // 文件名(如db.go) line, // 行号(如64) colorReset, // 文件行号结束重置 ) return buf.Bytes(), nil } // getLevelInfo 获取日志级别对应的标识和颜色 func getLevelInfo(level logrus.Level) (string, string) { switch level { case logrus.DebugLevel: return "d", ColorDebug case logrus.InfoLevel: return "i", ColorInfo case logrus.WarnLevel: return "w", ColorWarn case logrus.ErrorLevel: return "e", ColorError case logrus.FatalLevel: return "f", ColorFatal default: return "i", ColorInfo } } func getCallerInfo() (string, int) { // 从当前调用开始,逐层向上查找 for i := 2; i < 15; i++ { // i从2开始(跳过getCallerInfo自身) pc, file, line, ok := runtime.Caller(i) if !ok { break } // 获取函数名 funcName := runtime.FuncForPC(pc).Name() // 跳过logrus和logger包内部的调用 if shouldSkipPackage(funcName, file) { continue } // 找到第一个非内部调用的栈帧 return filepath.Base(file), line } return "unknown.go", 0 } // shouldSkipPackage 判断是否需要跳过该调用 func shouldSkipPackage(funcName, file string) bool { // 跳过logrus包 if strings.Contains(funcName, "logrus") || strings.Contains(file, "logrus") { return true } // 跳过logger包(你自己的包装包) if strings.Contains(file, "/logger/") { return true } // 跳过runtime包 if strings.Contains(funcName, "runtime.") { return true } return false } // 递归调整调用栈深度(兼容不同场景) func getCallerInfoWithSkip(skip int) (string, int) { _, file, line, ok := runtime.Caller(skip) if !ok { return "unknown.go", 0 } return filepath.Base(file), line } // Init 初始化全局默认日志实例(全局只执行一次) func Init(cfg LogConfig) { initOnce.Do(func() { // 1. 创建logrus实例 logrusInst := logrus.New() // 2. 配置输出(控制台 + 文件,可选) var outputs []io.Writer outputs = append(outputs, os.Stdout) // 控制台输出 // 如果配置了日志文件,添加文件输出 if cfg.LogFile != "" { // 确保日志目录存在 dir := filepath.Dir(cfg.LogFile) if err := os.MkdirAll(dir, 0755); err != nil { // 目录创建失败,只输出警告,不影响程序运行 logrusInst.Warnf("创建日志目录失败: %v,仅输出到控制台", err) } else { file, err := os.OpenFile(cfg.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err == nil { outputs = append(outputs, file) } else { logrusInst.Warnf("打开日志文件失败: %v,仅输出到控制台", err) } } } logrusInst.SetOutput(io.MultiWriter(outputs...)) // 3. 配置格式 logrusInst.SetFormatter(&CustomFormatter{ ShowColor: cfg.ShowColor, }) // 4. 配置日志级别 lvl, err := logrus.ParseLevel(cfg.Level) if err != nil { lvl = logrus.InfoLevel // 解析失败默认Info级别 } // 开启Verbose则强制设为Debug级别 if cfg.Verbose { lvl = logrus.DebugLevel } logrusInst.SetLevel(lvl) // 启用文件行号(必须开启,否则getCallerInfo拿不到数据) logrusInst.SetReportCaller(true) // 5. 赋值给全局默认实例 DefaultLogger = &logrusLogger{logrusInst} }) } // -------------------------- 4. 全局快捷调用方法(可选,简化使用) -------------------------- // 如果你不想每次都写 logger.DefaultLogger.Info(),可以封装快捷方法 func Debug(args ...any) { DefaultLogger.Debug(args...) } func Debugf(format string, args ...any) { DefaultLogger.Debugf(format, args...) } func Info(args ...any) { DefaultLogger.Info(args...) } func Infof(format string, args ...any) { DefaultLogger.Infof(format, args...) } func Warn(args ...any) { DefaultLogger.Warn(args...) } func Warnf(format string, args ...any) { DefaultLogger.Warnf(format, args...) } func Error(args ...any) { DefaultLogger.Error(args...) } func Errorf(format string, args ...any) { DefaultLogger.Errorf(format, args...) } func Fatal(args ...any) { DefaultLogger.Fatal(args...) } func Fatalf(format string, args ...any) { DefaultLogger.Fatalf(format, args...) }