277 lines
8.9 KiB
Go
277 lines
8.9 KiB
Go
// 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...) }
|