package utils import ( "encoding/json" "github.com/gin-gonic/gin" rotateLogs "github.com/lestrrat-go/file-rotatelogs" "github.com/pkg/errors" "github.com/sirupsen/logrus" "os" "psi/config" systemRes "psi/models/response" "sync" "time" ) var loggerFactory map[string]*logrus.Logger func InitLogger() error { var err error loggerFactory, err = NewLogger() if err != nil { return err } return nil } // GetLogger 获取日志配置(从 config.yaml 读取) func GetLogger(conf interface{}) error { logConfig := config.AppConfig.Log switch v := conf.(type) { case *struct { Channel map[string]string MaxAge int RotateTime int RootPath string }: v.Channel = logConfig.Channel v.MaxAge = logConfig.MaxAge v.RotateTime = logConfig.RotateTime v.RootPath = logConfig.RootPath default: return errors.New("不支持的配置类型") } return nil } // NewLogger 初始化日志 func NewLogger() (map[string]*logrus.Logger, error) { conf := struct { Channel map[string]string MaxAge int RotateTime int RootPath string }{} if err := GetLogger(&conf); err != nil { return nil, errors.Wrap(err, "logger config get fail") } logger := make(map[string]*logrus.Logger, len(conf.Channel)) var ( wg sync.WaitGroup mLock sync.Mutex ) for k, v := range conf.Channel { wg.Add(1) go func(wg *sync.WaitGroup, channel string, path string, mLock sync.Locker) { defer func() { wg.Done() }() rootPath := conf.RootPath if rootPath == "" { rootPath = os.Getenv("run_path") + "/runtime/logs" } fullPath := rootPath + path log := createLogger(fullPath, conf.MaxAge, conf.RotateTime) mLock.Lock() logger[channel] = log mLock.Unlock() }(&wg, k, v, &mLock) } wg.Wait() return logger, nil } // 创建日志 func createLogger(path string, maxAge int, rotateTime int) *logrus.Logger { // 确保目录存在 dir := path[:len(path)-len("/err.log")] if len(path) > 0 { // 提取目录路径 lastSlash := -1 for i := len(path) - 1; i >= 0; i-- { if path[i] == '/' || path[i] == '\\' { lastSlash = i break } } if lastSlash > 0 { dir = path[:lastSlash] if err := os.MkdirAll(dir, 0755); err != nil { logrus.Errorf("创建日志目录失败: %v, path: %s", err, dir) } } } writer, err := rotateLogs.New( path+"%Y%m%d%H%M", //rotateLogs.WithLinkName(path), rotateLogs.WithMaxAge(time.Duration(maxAge)*time.Hour), rotateLogs.WithRotationTime(time.Duration(rotateTime)*time.Hour), ) if err != nil { logrus.Errorf("创建日志文件失败: %v, path: %s", err, path) // 如果创建失败,使用标准输出作为备选 return createConsoleLogger() } log := new(logrus.Logger) log.SetFormatter(&logrus.JSONFormatter{}) log.SetOutput(writer) log.SetLevel(logrus.InfoLevel) log.SetNoLock() return log } // 创建控制台日志器(备选方案) func createConsoleLogger() *logrus.Logger { log := new(logrus.Logger) log.SetFormatter(&logrus.JSONFormatter{}) log.SetOutput(os.Stdout) log.SetLevel(logrus.InfoLevel) return log } // ErrorLog LEVEL-ERROR func ErrorLog(channel string, params logrus.Fields) { if _, ok := loggerFactory[channel]; !ok { return } loggerFactory[channel].WithFields(params).Error() } // InfoLog LEVEL-INFO func InfoLog(channel string, params logrus.Fields) { if _, ok := loggerFactory[channel]; !ok { return } loggerFactory[channel].WithFields(params).Info(params) } // FailWithRequestLog 失败时记录请求参数并返回错误响应 // channel: 日志渠道(如 LoggerChannelWork, LoggerChannelRequest) // source: 错误来源描述 // err: 错误对象 // c: gin上下文 // req: 请求参数(可选,传入会自动序列化到日志) func FailWithRequestLog(channel string, source string, err error, c *gin.Context, req interface{}) { fields := logrus.Fields{ "source": source, "err_msg": err.Error(), } if req != nil { if reqJSON, jsonErr := json.Marshal(req); jsonErr == nil { fields["request_params"] = string(reqJSON) } else { fields["request_params_error"] = jsonErr.Error() } } ErrorLog(channel, fields) systemRes.FailWithMessage(err.Error(), c) }