Files
sunhpc-go/pkg/logger/logger.go
2026-02-20 18:44:43 +08:00

299 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 保证日志只初始化一次
once sync.Once
)
// LogConfig 日志配置结构体和项目的config包对齐
type LogConfig struct {
Verbose bool // 是否开启详细模式Debug级别
Level string // 日志级别debug/info/warn/error
ShowColor bool // 是否显示彩色输出
LogFile string // 日志文件路径(可选,空则只输出到控制台)
}
// 默认配置
var defaultConfig = LogConfig{
Verbose: false,
Level: "info",
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 = ""
}
// 拼接格式:
// 灰色日期 + 空格 + 带颜色的[级别] + 空格 + 日志内容 + 空格 + 灰色文件行号 + 重置
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
}
}
// getCallerInfo 获取调用日志的文件和行号跳过logrus内部调用
func _getCallerInfo() (string, int) {
// 跳过的调用栈深度根据实际情况调整这里跳过logrus和logger包的调用
skip := 6
pc, file, line, ok := runtime.Caller(skip)
if !ok {
return "unknown.go", 0
}
// 只保留文件名(如 db.go去掉完整路径
fileName := filepath.Base(file)
// 过滤logrus内部调用可选
funcName := runtime.FuncForPC(pc).Name()
if funcName == "" || filepath.Base(funcName) == "logrus" {
return getCallerInfoWithSkip(skip + 1)
}
return fileName, line
}
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(funcName, "your/package/logger") ||
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) {
once.Do(func() {
// 合并配置:传入的配置为空则用默认值
if cfg.Level == "" {
cfg.Level = defaultConfig.Level
}
if cfg.LogFile == "" {
cfg.LogFile = defaultConfig.LogFile
}
// 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...) }