package logger import ( "encoding/json" stringUtils "eta/eta_mini_ht_api/common/utils/string" "fmt" "github.com/beego/beego/v2/core/logs" "github.com/beego/beego/v2/server/web/context" "log" "os" "path" "strings" "sync" ) const ( DefalutLogFilePath = "./etalogs" LogChannelLen = 10000 ) var ( loggerHandler *CustomLogger logMutex = &sync.Mutex{} ) type logger struct { *logs.BeeLogger filter Filter } type CustomLogger struct { logs []*logger } // Logger interface type Logger interface { Info(msg string, v ...interface{}) Warn(msg string, v ...interface{}) Error(msg string, v ...interface{}) } func Info(msg string, v ...interface{}) { logMutex.Lock() defer logMutex.Unlock() loggerHandler.Info(msg, v...) } func Error(msg string, v ...interface{}) { logMutex.Lock() defer logMutex.Unlock() loggerHandler.Error(msg, v...) } func Warn(msg string, v ...interface{}) { logMutex.Lock() defer logMutex.Unlock() loggerHandler.Warn(msg, v...) } func Debug(msg string, v ...interface{}) { loggerHandler.Debug(msg, v...) } func InfoWithTraceId(ctx *context.Context, msg string, v ...interface{}) { if traceId := ctx.Input.GetData("traceId"); traceId != "" { msg = fmt.Sprintf("[traceId:%v,%v]", traceId, msg) } Info(msg, v...) } func ErrorWithTraceId(ctx *context.Context, msg string, v ...interface{}) { if traceId := ctx.Input.GetData("traceId"); traceId != "" { msg = fmt.Sprintf("[traceId:%v,%v]", traceId, msg) } Error(msg, v...) } func WarnWithTraceId(ctx *context.Context, msg string, v ...interface{}) { if traceId := ctx.Input.GetData("traceId"); traceId != "" { msg = fmt.Sprintf("[traceId:%v,%v]", traceId, msg) } Warn(msg, v...) } func DebugWithTraceId(ctx *context.Context, msg string, v ...interface{}) { if traceId := ctx.Input.GetData("traceId"); traceId != "" { msg = fmt.Sprintf("[traceId:%v,%v]", traceId, msg) } Debug(msg, v...) } func (c *CustomLogger) Debug(msg string, v ...interface{}) { for _, cusLogger := range c.logs { if cusLogger.GetLevel() >= logs.LevelDebug && cusLogger.filter.ShouldLog(msg) { cusLogger.Debug(msg, v...) } } } func (c *CustomLogger) Info(msg string, v ...interface{}) { for _, cusLogger := range c.logs { if cusLogger.GetLevel() >= logs.LevelInfo && cusLogger.filter.ShouldLog(msg) { cusLogger.Info(msg, v...) } } } func (c *CustomLogger) Error(msg string, v ...interface{}) { for _, cusLogger := range c.logs { if cusLogger.GetLevel() >= logs.LevelError && cusLogger.filter.ShouldLog(msg) { cusLogger.Error(msg, v...) } } } func (c *CustomLogger) Warn(msg string, v ...interface{}) { for _, cusLogger := range c.logs { if cusLogger.GetLevel() >= logs.LevelWarning && cusLogger.filter.ShouldLog(msg) { cusLogger.Warn(msg, v...) } } } func init() { var logCfg logConfig configFile, err := os.ReadFile("conf/log/log_config.json") if err != nil { log.Fatalf("Failed to read log config: %v", err) } err = json.Unmarshal(configFile, &logCfg) if err != nil { log.Fatalf("Failed to parse log config: %v", err) } initLogger(logCfg) } var levelTrans = map[string]int{ "emergency": logs.LevelEmergency, "alert": logs.LevelAlert, "critical": logs.LevelCritical, "error": logs.LevelError, "warn": logs.LevelWarning, "notice": logs.LevelNotice, "info": logs.LevelInformational, "debug": logs.LevelDebug, } var terminalType = map[string]string{ "console": logs.AdapterConsole, "file": logs.AdapterFile, } func GetInstance() Logger { return loggerHandler } func initLogger(logCfg logConfig) { if loggerHandler == nil { loggerHandler = new(CustomLogger) } if stringUtils.IsEmptyOrNil(logCfg.FilePath) { logCfg.FilePath = DefalutLogFilePath } for _, appenderItem := range logCfg.Appenders { terminal, ok := terminalType[appenderItem.Type] if !ok { fmt.Println("初始化日志执行器失败:{%s},终端类型不支持{type:%s}", appenderItem.FileName, appenderItem.Type) continue } var beeLogger *logs.BeeLogger if terminal == logs.AdapterConsole { beeLogger = logs.NewLogger(LogChannelLen) err := beeLogger.SetLogger(logs.AdapterConsole) if err != nil { fmt.Println("创建日志执行器失败:{%s} %v", appenderItem.FileName, err) continue } } else { logFile := appenderItem.FileName os.MkdirAll(logCfg.FilePath, os.ModePerm) // 打开文件 appenderItem.FileName = path.Join(logCfg.FilePath, logFile) props, err := convertAppenderToLog(&appenderItem) if err != nil { fmt.Println("初始化日志执行器失败:{%s} %v", appenderItem.FileName, err) continue } b, _ := json.Marshal(props) beeLogger = logs.NewLogger(LogChannelLen) err = beeLogger.SetLogger(logs.AdapterFile, string(b)) if err != nil { fmt.Println("设置日志执行器失败:{%s} %v", appenderItem.FileName, err) continue } beeLogger.EnableFuncCallDepth(true) } loggerHandler.logs = append(loggerHandler.logs, &logger{BeeLogger: beeLogger, filter: NewLevelFilter(appenderItem.Filter)}) } } // ConvertAppenderToLog 将 appender 结构体转换为 log 结构体 func convertAppenderToLog(a *appender) (*logProps, error) { lvl, ok := levelTrans[a.Level] if !ok { return nil, fmt.Errorf("unknown log level: %s", a.Level) } return &logProps{ Prefix: a.Prefix, FileName: a.FileName, MaxLines: a.MaxLines, MaxSize: a.MaxSize, Daily: a.Daily, MaxDays: a.MaxDays, Rotate: a.Rotate, Level: lvl, Color: a.Color, }, nil } // Filter 接口定义 type Filter interface { ShouldLog(message string) bool } // LevelFilter 实现 Filter 接口,根据日志级别过滤日志 type ContentFilter struct { filterStr string } // NewLevelFilter 创建一个新的 LevelFilter 实例 func NewLevelFilter(filterStr string) *ContentFilter { return &ContentFilter{filterStr: filterStr} } // ShouldLog 根据日志级别决定是否记录日志 func (lf *ContentFilter) ShouldLog(message string) bool { return strings.Contains(message, lf.filterStr) } // logConfig 日志配置 type logConfig struct { FilePath string `json:"filepath" description:"日志路径"` Appenders []appender `json:"appenders" description:"日志记录"` } // appender beeLogger JSON配置 type appender struct { Filter string `json:"filter"` Prefix string `json:"perfix" description:"日志前缀"` Type string `json:"type" description:"终端类型"` FileName string `json:"filename" description:"保存的文件名"` MaxLines int `json:"maxlines" description:"每个文件保存的最大行数,默认值 1000000"` MaxSize int `json:"maxsize" description:"每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB"` Daily bool `json:"daily" description:"是否按照每天 logrotate,默认是 true"` MaxDays int `json:"maxdays" description:"文件最多保存多少天,默认保存 7 天"` Rotate bool `json:"rotate" description:"是否开启 logrotate,默认是 true"` Level string `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"` Color bool `json:"color" description:"日志是否输出颜色"` } // logProps beeLogger 配置 type logProps struct { Prefix string `json:"perfix" description:"日志前缀"` FileName string `json:"filename" description:"保存的文件名"` MaxLines int `json:"maxlines" description:"每个文件保存的最大行数,默认值 1000000"` MaxSize int `json:"maxsize" description:"每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB"` Daily bool `json:"daily" description:"是否按照每天 logrotate,默认是 true"` MaxDays int `json:"maxdays" description:"文件最多保存多少天,默认保存 7 天"` Rotate bool `json:"rotate" description:"是否开启 logrotate,默认是 true"` Level int `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"` Color bool `json:"color" description:"日志是否输出颜色"` }