log/log.go

277 lines
6.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package log
import (
"fmt"
"github.com/ehlxr/lumberjack"
"log"
"os"
"path"
"strings"
"time"
"github.com/ehlxr/log/bufferpool"
"github.com/ehlxr/log/crash"
"github.com/ehlxr/log/encoder"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var logger *zap.SugaredLogger
const (
DebugLevel = zapcore.DebugLevel
InfoLevel = zapcore.InfoLevel
WarnLevel = zapcore.WarnLevel
ErrorLevel = zapcore.ErrorLevel
DPanicLevel = zapcore.DPanicLevel
PanicLevel = zapcore.PanicLevel
FatalLevel = zapcore.FatalLevel
)
type logConfig struct {
Level zapcore.Level
EnableColors bool
CrashLogFilename string
ErrorLogFilename string
EnableLineNumber bool
AddCallerSkip int
// enable the truncation of the level text to 4 characters.
EnableLevelTruncation bool
EnableErrorStacktrace bool
TimestampFormat string
EnableCapitalLevel bool
atomicLevel zap.AtomicLevel
Name string
Fields []zap.Field
*lumberjack.Logger
}
func init() {
config := &logConfig{
Logger: &lumberjack.Logger{
LocalTime: true,
},
}
logger = config.newLogger().Sugar()
}
func (config *logConfig) Init() {
logger = config.newLogger().Sugar()
}
func (config *logConfig) New() *zap.SugaredLogger {
return config.newLogger().Sugar()
}
func NewLogConfig() *logConfig {
return &logConfig{
Level: DebugLevel,
EnableColors: true,
CrashLogFilename: "./logs/crash.log",
ErrorLogFilename: "./logs/error.log",
EnableLineNumber: true,
EnableLevelTruncation: true,
EnableErrorStacktrace: true,
TimestampFormat: "2006-01-02 15:04:05.000",
EnableCapitalLevel: true,
Logger: &lumberjack.Logger{
Filename: "./logs/log.log",
MaxSize: 200,
MaxAge: 0,
MaxBackups: 30,
LocalTime: true,
Compress: false,
BackupTimeFormat: "2006-01-02",
},
}
}
func (config *logConfig) newLogger() *zap.Logger {
if config.CrashLogFilename != "" {
writeCrashLog(config.CrashLogFilename)
}
config.atomicLevel = zap.NewAtomicLevelAt(config.Level)
config.initColor()
cores := []zapcore.Core{
zapcore.NewCore(
encoder.NewTextEncoder(config.encoderConfig()),
zapcore.Lock(os.Stdout),
config.atomicLevel,
)}
if config.Filename != "" {
cores = append(cores, config.fileCore())
}
if config.ErrorLogFilename != "" {
cores = append(cores, config.errorFileCore())
}
core := zapcore.NewTee(cores...)
var options []zap.Option
if config.EnableLineNumber {
options = append(options, zap.AddCaller(), zap.AddCallerSkip(config.AddCallerSkip))
}
if config.EnableErrorStacktrace {
options = append(options, zap.AddStacktrace(zapcore.ErrorLevel))
}
zapLog := zap.New(core, options...)
if config.Name != "" {
zapLog = zapLog.Named(fmt.Sprintf("[%s]", config.Name))
}
return zapLog.With(config.Fields...)
}
func (config *logConfig) encoderConfig() zapcore.EncoderConfig {
el := config.encodeLevel
if config.EnableColors {
el = config.encodeColorLevel
}
return zapcore.EncoderConfig{
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: el,
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
tf := time.RFC3339
if config.TimestampFormat != "" {
tf = config.TimestampFormat
}
enc.AppendString(t.Format(fmt.Sprintf("[%s]", tf)))
},
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(trimCallerFilePath(caller))
},
}
}
func (config *logConfig) fileCore() zapcore.Core {
return zapcore.NewCore(
encoder.NewTextEncoder(config.encoderConfig()),
// zapcore.NewMultiWriteSyncer(
// zapcore.Lock(os.Stdout),
// config.fileWriteSyncer(),
// ),
config.fileWriteSyncer(config.Filename),
config.atomicLevel,
)
}
func (config *logConfig) errorFileCore() zapcore.Core {
return zapcore.NewCore(
encoder.NewTextEncoder(config.encoderConfig()),
config.fileWriteSyncer(config.ErrorLogFilename),
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
}),
)
}
func (config *logConfig) encodeLevel(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
levelString := l.CapitalString()
if config.EnableLevelTruncation {
levelString = levelString[:4]
}
enc.AppendString(fmt.Sprintf("[%s]", levelString))
}
func (config *logConfig) encodeColorLevel(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
s, ok := _levelToColorStrings[l]
if !ok {
s = _unknownLevelColor.Add(l.CapitalString())
}
enc.AppendString(fmt.Sprintf("[%s]", s))
}
func trimCallerFilePath(ec zapcore.EntryCaller) string {
if !ec.Defined {
return "undefined"
}
// Find the last separator.
idx := strings.LastIndexByte(ec.File, '/')
if idx == -1 {
return ec.FullPath()
}
buf := bufferpool.Get()
buf.AppendString(ec.File[idx+1:])
buf.AppendByte(':')
buf.AppendInt(int64(ec.Line))
caller := buf.String()
buf.Free()
return fmt.Sprintf(" %s", caller)
}
func (config *logConfig) fileWriteSyncer(fileName string) zapcore.WriteSyncer {
// go get github.com/lestrrat-go/file-rotatelogs
// writer, err := rotatelogs.New(
// name+".%Y%m%d",
// rotatelogs.WithLinkName(name), // 生成软链,指向最新日志文件
// rotatelogs.WithMaxAge(7*24*time.Hour), // 文件最大保存时间
// rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
// )
// if err != nil {
// log.Fatalf("config normal logger file error. %v", errors.WithStack(err))
// }
writer := &lumberjack.Logger{
Filename: fileName,
MaxSize: config.MaxSize, // 单个日志文件大小MB
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge, // 保留多少天的日志
LocalTime: config.LocalTime,
Compress: config.Compress,
BackupTimeFormat: config.BackupTimeFormat,
}
// Rotating log files daily
runner := cron.New(cron.WithSeconds(), cron.WithLocation(time.Local))
_, _ = runner.AddFunc("0 0 0 * * ?", func() {
_ = writer.Rotate(time.Now().AddDate(0, 0, -1))
})
go runner.Run()
return zapcore.AddSync(writer)
}
func writeCrashLog(file string) {
err := os.MkdirAll(path.Dir(file), os.ModePerm)
if err != nil {
log.Fatalf("make crash log dir error. %v",
err)
}
crash.NewCrashLog(file)
}
func Fields(args ...interface{}) {
logger = logger.With(args...)
}
func With(l *zap.SugaredLogger, args ...interface{}) *zap.SugaredLogger {
return l.With(args...)
}