|
|
package logger
|
|
|
|
|
|
import (
|
|
|
"github.com/natefinch/lumberjack"
|
|
|
"go.uber.org/zap"
|
|
|
"go.uber.org/zap/zapcore"
|
|
|
"os"
|
|
|
"path"
|
|
|
"path/filepath"
|
|
|
"runtime"
|
|
|
"strings"
|
|
|
)
|
|
|
|
|
|
var Log *zap.Logger
|
|
|
var SLog *zap.SugaredLogger
|
|
|
|
|
|
const DefaultLogPath = "/logs"
|
|
|
|
|
|
type (
|
|
|
// FileConfig 日志文件配置
|
|
|
FileConfig struct {
|
|
|
Level string // 日志打印级别 debug info warning error
|
|
|
Format string // 输出日志格式 console, json
|
|
|
Enabled bool // 是否开启
|
|
|
|
|
|
Path string // 输出日志文件路径
|
|
|
FileName string // 输出日志文件名称
|
|
|
FileMaxSize int // 【日志分割】单个日志文件最多存储量 单位(mb)
|
|
|
FileMaxBackups int // 【日志分割】日志备份文件最多数量
|
|
|
MaxAge int // 日志保留时间,单位: 天 (day)
|
|
|
Compress bool // 是否压缩日志
|
|
|
}
|
|
|
|
|
|
// ConsoleConfig 控制台日志配置
|
|
|
ConsoleConfig struct {
|
|
|
Level string // 日志打印级别 debug info warning error
|
|
|
Format string // 输出日志格式 console, json
|
|
|
}
|
|
|
)
|
|
|
|
|
|
var logLevel = map[string]zapcore.Level{
|
|
|
"debug": zapcore.DebugLevel,
|
|
|
"info": zapcore.InfoLevel,
|
|
|
"warn": zapcore.WarnLevel,
|
|
|
"error": zapcore.ErrorLevel,
|
|
|
}
|
|
|
|
|
|
func Sync() {
|
|
|
if SLog != nil {
|
|
|
_ = SLog.Sync()
|
|
|
}
|
|
|
if Log != nil {
|
|
|
_ = Log.Sync()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// InitLogger 初始化 log
|
|
|
func InitLogger(fileConf *FileConfig, consoleConf *ConsoleConfig) error {
|
|
|
cores := make([]zapcore.Core, 0, 2)
|
|
|
|
|
|
consoleCore := zapcore.NewCore(getEncoder(consoleConf), zapcore.AddSync(os.Stdout), getLogLevel(consoleConf.Level))
|
|
|
cores = append(cores, consoleCore)
|
|
|
|
|
|
if fileConf.Enabled {
|
|
|
writeSyncer, err := getLogWriter(fileConf) // 日志文件配置 文件位置和切割
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
fileCore := zapcore.NewCore(getEncoder(fileConf), writeSyncer, getLogLevel(fileConf.Level))
|
|
|
cores = append(cores, fileCore)
|
|
|
}
|
|
|
|
|
|
// 控制台/文件 配置分离
|
|
|
core := zapcore.NewTee(cores...)
|
|
|
|
|
|
logger := zap.New(core, zap.AddCaller()) //zap.AddCaller() 输出日志打印文件和行数如: logger/logger_test.go:33
|
|
|
SLog = logger.Sugar()
|
|
|
Log = logger
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// getLogLevel 获取日志打印级别
|
|
|
func getLogLevel(level string) zapcore.Level {
|
|
|
l, ok := logLevel[level] // 日志打印级别
|
|
|
if !ok {
|
|
|
l = logLevel["info"]
|
|
|
}
|
|
|
return l
|
|
|
}
|
|
|
|
|
|
// getLogWriter 获取日志输出方式 日志文件 控制台
|
|
|
func getLogWriter(conf *FileConfig) (zapcore.WriteSyncer, error) {
|
|
|
// 判断日志路径是否存在,如果不存在就创建
|
|
|
if conf.Path == "" {
|
|
|
conf.Path = getCurrentAbPath() + DefaultLogPath
|
|
|
}
|
|
|
if exist := isExist(conf.Path); !exist {
|
|
|
if err := os.MkdirAll(conf.Path, os.ModePerm); err != nil {
|
|
|
conf.Path = getCurrentAbPath() + DefaultLogPath
|
|
|
if err := os.MkdirAll(conf.Path, os.ModePerm); err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 日志文件 与 日志切割 配置
|
|
|
lumberJackLogger := &lumberjack.Logger{
|
|
|
Filename: filepath.Join(conf.Path, conf.FileName), // 日志文件路径
|
|
|
MaxSize: conf.FileMaxSize, // 单个日志文件最大多少 mb
|
|
|
MaxBackups: conf.FileMaxBackups, // 日志备份数量
|
|
|
MaxAge: conf.MaxAge, // 日志最长保留时间
|
|
|
Compress: conf.Compress, // 是否压缩日志
|
|
|
}
|
|
|
return zapcore.AddSync(lumberJackLogger), nil
|
|
|
}
|
|
|
|
|
|
// getEncoder 编码器(如何写入日志)
|
|
|
func getEncoder(conf interface{}) zapcore.Encoder {
|
|
|
encoderConfig := zap.NewProductionEncoderConfig()
|
|
|
encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000Z07") // log 时间格式 例如: 2021-09-11t20:05:54.852+0800
|
|
|
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
|
|
|
|
|
|
var format string
|
|
|
switch conf.(type) {
|
|
|
case FileConfig:
|
|
|
format = conf.(FileConfig).Format
|
|
|
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
|
|
case ConsoleConfig:
|
|
|
format = conf.(ConsoleConfig).Format
|
|
|
// 输出level序列化为全大写字符串,如 INFO DEBUG ERROR 彩色
|
|
|
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
|
|
}
|
|
|
|
|
|
if format == "json" {
|
|
|
return zapcore.NewJSONEncoder(encoderConfig) // 以json格式写入
|
|
|
}
|
|
|
return zapcore.NewConsoleEncoder(encoderConfig) // 以默认console格式写入
|
|
|
}
|
|
|
|
|
|
// isExist 判断文件或者目录是否存在
|
|
|
func isExist(path string) bool {
|
|
|
_, err := os.Stat(path)
|
|
|
return err == nil || os.IsExist(err)
|
|
|
}
|
|
|
|
|
|
// 最终方案-全兼容
|
|
|
func getCurrentAbPath() string {
|
|
|
dir := getCurrentAbPathByExecutable()
|
|
|
if strings.Contains(dir, getTmpDir()) {
|
|
|
return getCurrentAbPathByCaller()
|
|
|
}
|
|
|
return dir
|
|
|
}
|
|
|
|
|
|
// 获取系统临时目录,兼容go run
|
|
|
func getTmpDir() string {
|
|
|
dir := os.Getenv("TEMP")
|
|
|
if dir == "" {
|
|
|
dir = os.Getenv("TMP")
|
|
|
}
|
|
|
res, _ := filepath.EvalSymlinks(dir)
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
// 获取当前执行文件绝对路径
|
|
|
func getCurrentAbPathByExecutable() string {
|
|
|
exePath, _ := os.Executable()
|
|
|
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
|
|
|
return res
|
|
|
}
|
|
|
|
|
|
// 获取当前执行文件绝对路径(go run)
|
|
|
func getCurrentAbPathByCaller() string {
|
|
|
var abPath string
|
|
|
_, filename, _, ok := runtime.Caller(0)
|
|
|
if ok {
|
|
|
abPath = path.Dir(filename)
|
|
|
}
|
|
|
return abPath
|
|
|
}
|