package utils import ( rotatelogs "github.com/lestrrat-go/file-rotatelogs" "go.uber.org/zap" "go.uber.org/zap/zapcore" "io" "os" "strings" "sync" "time" ) var zapOnce sync.Once var innerSugarLogger *zap.SugaredLogger func InitNomadGlobalLog() { zapOnce.Do(func() { pwdDir, _ := os.Getwd() infoLogFilePath := pwdDir + "/alloc/logs/demo-log-main-task.stdout.0" errLogFilePath := pwdDir + "/alloc/logs/demo-log-main-task.stderr.0" serverName := "demo-log" if v := os.Getenv("APP_NAME"); v != "" { serverName = v } branchName := "main" if v := os.Getenv("CI_COMMIT_BRANCH"); v != "" { branchName = v } else { branchName = "main" } infoLogFilePath = strings.ReplaceAll(infoLogFilePath, "/app", "") errLogFilePath = strings.ReplaceAll(errLogFilePath, "/app", "") // 替换 serverName infoLogFilePath = strings.ReplaceAll(infoLogFilePath, "demo-log", serverName) errLogFilePath = strings.ReplaceAll(errLogFilePath, "demo-log", serverName) // 替换 branchName infoLogFilePath = strings.ReplaceAll(infoLogFilePath, "main", branchName) errLogFilePath = strings.ReplaceAll(errLogFilePath, "main", branchName) // 设置一些基本日志格式 具体含义还比较好理解,直接看zap源码也不难懂 encoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ MessageKey: "msg", LevelKey: "level", EncodeLevel: zapcore.CapitalLevelEncoder, TimeKey: "ts", EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05")) }, EncodeName: zapcore.FullNameEncoder, CallerKey: "file", EncodeCaller: zapcore.ShortCallerEncoder, EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) { enc.AppendInt64(int64(d) / 1000000) }, }) // 实现两个判断日志等级的interface infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.InfoLevel }) errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.ErrorLevel }) // 获取 info、error日志文件的io.Writer 抽象 getWriter() 在下方实现 infoWriter := getWriter(infoLogFilePath) errorWriter := getWriter(errLogFilePath) // 最后创建具体的Logger core := zapcore.NewTee( zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel), zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel), ) logger := zap.New(core, zap.AddCaller()) // 需要传入 zap.AddCaller() 才会显示打日志点的文件名和行数, 有点小坑 innerSugarLogger = logger.Sugar().With("appName", serverName) }) } type logger struct { log *zap.SugaredLogger traceId string } func NewLog(traceId string) *logger { if innerSugarLogger == nil { InitNomadGlobalLog() } return &logger{ log: innerSugarLogger.With(zap.String("trace_id", traceId)), traceId: traceId, } } func getWriter(filename string) io.Writer { // 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH // demo.log是指向最新日志的链接 // 保存7天内的日志,每1小时(整点)分割一次日志 hook, err := rotatelogs.New( strings.Replace(filename, ".0", "", -1)+".%Y%m%d%H", // 没有使用go风格反人类的format格式 rotatelogs.WithLinkName(filename), rotatelogs.WithMaxAge(time.Hour*24), rotatelogs.WithRotationTime(time.Hour), ) if err != nil { panic(err) } return hook } func (l logger) Debug(args ...interface{}) { l.log.Debug(args...) } func (l logger) Debugf(template string, args ...interface{}) { l.log.Debugf(template, args...) } func (l logger) Info(args ...interface{}) { l.log.Info(args...) } func (l logger) Infof(template string, args ...interface{}) { l.log.Infof(template, args...) } func (l logger) Warn(args ...interface{}) { l.log.Warn(args...) } func (l logger) Warnf(template string, args ...interface{}) { l.log.Warnf(template, args...) } func (l logger) Error(args ...interface{}) { l.log.Error(args...) } func (l logger) Errorf(template string, args ...interface{}) { l.log.Errorf(template, args...) } func (l logger) DPanic(args ...interface{}) { l.log.DPanic(args...) } func (l logger) DPanicf(template string, args ...interface{}) { l.log.DPanicf(template, args...) } func (l logger) Panic(args ...interface{}) { l.log.Panic(args...) } func (l logger) Panicf(template string, args ...interface{}) { l.log.Panicf(template, args...) } func (l logger) Fatal(args ...interface{}) { l.log.Fatal(args...) } func (l logger) Fatalf(template string, args ...interface{}) { l.log.Fatalf(template, args...) }