Files
sunhpc-go/pkg/logger/logger.go

355 lines
11 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 保证日志只初始化一次
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"`
}
type LevelFilterWriter struct {
writer io.Writer
maxLevel logrus.Level // 控制台: 只输出 <= 该级别
minLevel logrus.Level // 文件: 只输出 >= 该级别
isConsole bool // 是否是控制台输出
}
// Write 实现io.Writer接口核心过滤逻辑
func (f *LevelFilterWriter) Write(p []byte) (n int, err error) {
// 解析日志级别适配logrus默认格式和CustomFormatter
logLevel := parseLogLevelFromContent(p)
// 控制台:只输出 Info 及以下级别Trace/Debug/Info
if f.isConsole {
if logLevel <= f.maxLevel {
return f.writer.Write(p)
}
return len(p), nil // 过滤掉返回长度避免Writer报错
}
// 文件:只输出 Warn 及以上级别Warn/Error/Fatal/Panic
if logLevel >= f.minLevel {
return f.writer.Write(p)
}
return len(p), nil
}
// parseLogLevelFromContent 解析日志内容中的级别(兼容自定义格式)
func parseLogLevelFromContent(p []byte) logrus.Level {
content := string(p)
// 适配常见的级别关键字兼容你的CustomFormatter
switch {
case contains(content, "TRACE"):
return logrus.TraceLevel
case contains(content, "DEBUG"):
return logrus.DebugLevel
case contains(content, "INFO"):
return logrus.InfoLevel
case contains(content, "WARN") || contains(content, "WARNING"):
return logrus.WarnLevel
case contains(content, "ERROR"):
return logrus.ErrorLevel
case contains(content, "FATAL"):
return logrus.FatalLevel
case contains(content, "PANIC"):
return logrus.PanicLevel
default:
return logrus.InfoLevel // 解析失败默认Info级别
}
}
// contains 辅助函数:判断字符串是否包含子串
func contains(s, substr string) bool {
return len(s) >= len(substr) && indexOf(s, substr) != -1
}
// indexOf 简易字符串查找(避免依赖额外库)
func indexOf(s, substr string) int {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return i
}
}
return -1
}
// 默认配置
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. 先配置日志级别(总开关,必须在输出配置前)
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)
// 3. 配置输出(控制台 + 文件,可选)
var outputs []io.Writer
// 控制台输出: 只输出 Info 及以下级别
consoleWriter := &LevelFilterWriter{
writer: os.Stdout,
minLevel: logrus.InfoLevel,
isConsole: true,
}
outputs = append(outputs, consoleWriter)
// 如果配置了日志文件,添加文件输出
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 {
fileWriter := &LevelFilterWriter{
writer: file,
minLevel: logrus.WarnLevel,
isConsole: false,
}
outputs = append(outputs, fileWriter)
} else {
logrusInst.Warnf("打开日志文件失败: %v,仅输出到控制台", err)
}
}
}
logrusInst.SetOutput(io.MultiWriter(outputs...))
// 4. 配置格式
logrusInst.SetFormatter(&CustomFormatter{
ShowColor: cfg.ShowColor,
})
// 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...) }