123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- package log
- import (
- "context"
- "fmt"
- "github.com/gin-gonic/gin"
- "github.com/spf13/viper"
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
- "os"
- "path/filepath"
- "strings"
- "time"
- )
- const ctxLoggerKey = "zapLogger"
- // ServiceType 服务类型
- type ServiceType int
- const (
- API ServiceType = iota // API服务
- Task // Task服务
- )
- type Logger struct {
- *zap.Logger
- }
- // NewLog 创建一个新的日志实例,兼容旧版本配置
- func NewLog(conf *viper.Viper) *Logger {
- return NewServiceLog(conf, API)
- }
- // NewServiceLog 创建特定服务类型的日志实例
- func NewServiceLog(conf *viper.Viper, serviceType ServiceType) *Logger {
- var logPath string
- // 从环境变量检查服务类型
- envServiceType := os.Getenv("SERVICE_TYPE")
- if envServiceType == "api" {
- serviceType = API
- } else if envServiceType == "task" {
- serviceType = Task
- }
- // 根据服务类型和日期生成日志文件名
- logFormat := conf.GetString("log.log_format") // 日期格式,如"2006-01-02"
- // 确保有默认的日期格式
- if logFormat == "" {
- logFormat = "2006-01-02"
- }
- currentDate := time.Now().Format(logFormat)
- // 显示调试信息
- fmt.Printf("[日志初始化] 服务类型: %s, 日期: %s\n",
- serviceTypeToString(serviceType), currentDate)
- if serviceType == API && conf.IsSet("log.api_log_file") {
- // 使用API日志路径
- logPath = conf.GetString("log.api_log_file")
- logPath = fmt.Sprintf(logPath, currentDate) // 替换日期占位符
- } else if serviceType == Task && conf.IsSet("log.task_log_file") {
- // 使用Task日志路径
- logPath = conf.GetString("log.task_log_file")
- logPath = fmt.Sprintf(logPath, currentDate) // 替换日期占位符
- } else if conf.IsSet("log.log_file_name") {
- // 即使使用旧配置,也添加日期分区
- logPath = conf.GetString("log.log_file_name")
- // 截取扩展名前的部分,添加日期,然后加上扩展名
- ext := filepath.Ext(logPath)
- base := logPath[:len(logPath)-len(ext)]
- logPath = fmt.Sprintf("%s-%s%s", base, currentDate, ext)
- }
- // 处理相对路径,兼容不同的工作目录
- if strings.HasPrefix(logPath, "./") {
- basePath := "/data/app" // 容器中的日志基础路径
- if _, err := os.Stat(basePath); os.IsNotExist(err) {
- // 如果/data/app不存在,则使用当前目录
- workDir, err := os.Getwd()
- if err == nil {
- basePath = workDir
- } else {
- basePath = "."
- }
- }
- // 将相对路径转换为绝对路径
- logPath = strings.Replace(logPath, "./", basePath+"/", 1)
- }
- // 显示实际使用的日志路径
- fmt.Printf("[日志路径] %s\n", logPath)
- // 确保日志目录存在
- if err := ensureDirExists(logPath); err != nil {
- // 如果创建目录失败,回退到临时目录
- fmt.Printf("Error creating log directory: %v, using default path\n", err)
- logPath = "./logs/app.log"
- ensureDirExists(logPath)
- }
- // 获取日志级别
- lv := conf.GetString("log.log_level")
- var level zapcore.Level
- //debug<info<warn<error<fatal<panic
- switch lv {
- case "debug":
- level = zap.DebugLevel
- case "info":
- level = zap.InfoLevel
- case "warn":
- level = zap.WarnLevel
- case "error":
- level = zap.ErrorLevel
- default:
- level = zap.InfoLevel
- }
- // 日志轮转配置 - 使用DateRotator代替lumberjack.Logger
- hook := NewDateRotator(
- logPath,
- conf.GetInt("log.max_size"), // 每个日志文件的最大大小单位:M
- conf.GetInt("log.max_backups"), // 日志文件最多保存的备份数
- conf.GetInt("log.max_age"), // 文件最多保存的天数
- conf.GetBool("log.compress"), // 是否压缩
- logFormat, // 使用相同的日期格式
- )
- // 配置日志编码器
- var encoder zapcore.Encoder
- if conf.GetString("log.encoding") == "console" {
- encoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
- TimeKey: "ts",
- LevelKey: "level",
- NameKey: "Logger",
- CallerKey: "caller",
- MessageKey: "msg",
- StacktraceKey: "stacktrace",
- LineEnding: zapcore.DefaultLineEnding,
- EncodeLevel: zapcore.LowercaseColorLevelEncoder,
- EncodeTime: timeEncoder,
- EncodeDuration: zapcore.SecondsDurationEncoder,
- EncodeCaller: zapcore.FullCallerEncoder,
- })
- } else {
- encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
- TimeKey: "time",
- LevelKey: "level",
- NameKey: "logger",
- CallerKey: "caller",
- FunctionKey: zapcore.OmitKey,
- MessageKey: "msg",
- StacktraceKey: "stacktrace",
- LineEnding: zapcore.DefaultLineEnding,
- EncodeLevel: zapcore.LowercaseLevelEncoder,
- EncodeTime: timeEncoder, // 使用自定义时间格式
- EncodeDuration: zapcore.StringDurationEncoder,
- EncodeCaller: zapcore.ShortCallerEncoder,
- })
- }
- // 创建日志核心
- logOutput := zapcore.NewMultiWriteSyncer(
- zapcore.AddSync(os.Stdout), // 同时输出到控制台
- zapcore.AddSync(hook), // 和文件
- )
- core := zapcore.NewCore(encoder, logOutput, level)
- // 开发环境与生产环境的差异化配置
- var logger *zap.Logger
- if conf.GetString("env") != "prod" {
- logger = zap.New(core,
- zap.Development(),
- zap.AddCaller(),
- zap.AddStacktrace(zap.ErrorLevel),
- )
- } else {
- logger = zap.New(core,
- zap.AddCaller(),
- zap.AddStacktrace(zap.ErrorLevel),
- )
- }
- // 添加服务类型标识
- serviceTypeField := zap.String("service", serviceTypeToString(serviceType))
- logger = logger.With(serviceTypeField)
- return &Logger{logger}
- }
- // ensureDirExists 确保日志目录存在
- func ensureDirExists(filePath string) error {
- dir := filepath.Dir(filePath)
- return os.MkdirAll(dir, 0755)
- }
- // serviceTypeToString 将服务类型转换为字符串
- func serviceTypeToString(serviceType ServiceType) string {
- switch serviceType {
- case API:
- return "api"
- case Task:
- return "task"
- default:
- return "unknown"
- }
- }
- // timeEncoder 自定义时间编码器,提供高精度时间戳
- func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
- enc.AppendString(t.Format("2006-01-02 15:04:05.000000000"))
- }
- // WithValue Adds a field to the specified context
- func (l *Logger) WithValue(ctx context.Context, fields ...zapcore.Field) context.Context {
- if c, ok := ctx.(*gin.Context); ok {
- ctx = c.Request.Context()
- c.Request = c.Request.WithContext(context.WithValue(ctx, ctxLoggerKey, l.WithContext(ctx).With(fields...)))
- return c
- }
- return context.WithValue(ctx, ctxLoggerKey, l.WithContext(ctx).With(fields...))
- }
- // WithContext Returns a zap instance from the specified context
- func (l *Logger) WithContext(ctx context.Context) *Logger {
- if c, ok := ctx.(*gin.Context); ok {
- ctx = c.Request.Context()
- }
- zl := ctx.Value(ctxLoggerKey)
- ctxLogger, ok := zl.(*zap.Logger)
- if ok {
- return &Logger{ctxLogger}
- }
- return l
- }
|