git.ug-wk.cc/server/log@v0.0.0-20230524075443-2e41d2d64cca/config.go (about)

     1  package log
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/pelletier/go-toml/v2"
     9  
    10  	"github.com/pkg/errors"
    11  	"go.uber.org/zap"
    12  	"go.uber.org/zap/zapcore"
    13  	"gopkg.in/natefinch/lumberjack.v2"
    14  	"gopkg.in/yaml.v3"
    15  )
    16  
    17  const (
    18  	// defaultTimeZone 默认时区
    19  	defaultTimeZone = `Asia/Shanghai`
    20  	// defaultTimeLayout 默认时间格式
    21  	defaultTimeLayout       = `2006-01-02 15:04:05.000`
    22  	defaultRotateMaxSize    = 100
    23  	defaultRotateMaxBackups = 50
    24  	defaultRotateMaxAge     = 7
    25  )
    26  
    27  var (
    28  	core          zapcore.Core
    29  	encoder       zapcore.Encoder
    30  	writeSyncer   zapcore.WriteSyncer
    31  	inputCores    []zapcore.Core
    32  	HiddenConsole bool
    33  )
    34  
    35  // RotateConfig rotate 配置
    36  type RotateConfig struct {
    37  	MaxSize    int `yaml:"maxSize"`    // 单个日志文件最大大小,单位为MB
    38  	MaxBackups int `yaml:"maxBackups"` // 最大部分数量
    39  	MaxAge     int `yaml:"maxAge"`     // 最大保留时间,单位为天
    40  }
    41  
    42  // Config 日志器配置
    43  type Config struct {
    44  	Service     string            `yaml:"service"`     // 日志名称
    45  	Level       zapcore.Level     `yaml:"level"`       // 最低级别
    46  	FilePath    string            `yaml:"filePath"`    // 日志文件路径,如果为空,表示不输出,可以包含路径,最终生成一个FilePath.log.
    47  	TimeZone    string            `yaml:"timeZone"`    // 时区,默认defaultTimeZone,可以从https://www.zeitverschiebung.net/en/ 查询时区信息
    48  	TimeLayout  string            `yaml:"timeLayout"`  // 输出时间格式,默认为defaultTimeLayout,任何Go支持的格式都是合法的
    49  	Debug       bool              `yaml:"debug"`       // 是否调试,调试模式会输出完整的代码行信息,其他模式只会输出项目内部的代码行信息
    50  	Dev         bool              `yaml:"dev"`         // 开发模式,输出完整路径
    51  	JSON        bool              `yaml:"json"`        // 是否输出为一个完整的json,默认为false
    52  	HideConsole bool              `yaml:"hideConsole"` // 是否隐藏终端输出
    53  	Rotate      *RotateConfig     `yaml:"rotate"`      // 日志 rotate
    54  	location    *time.Location    `yaml:"location"`    // 输出time.Time时的时区
    55  	LevelToPath map[string]string `yaml:"levelToPath"`
    56  	levelToPath map[zapcore.Level]string
    57  }
    58  
    59  /*
    60  NewConfig 新建一个配置
    61  参数:
    62  返回值:
    63  *	*Config	*Config
    64  */
    65  func NewConfig() *Config {
    66  	return &Config{}
    67  }
    68  
    69  func (l *Config) tidy() error {
    70  	var (
    71  		level zapcore.Level
    72  		err   error
    73  	)
    74  
    75  	l.levelToPath = make(map[zapcore.Level]string, len(l.LevelToPath))
    76  
    77  	for levelText, path := range l.LevelToPath {
    78  		if level, err = zapcore.ParseLevel(levelText); err != nil {
    79  			return errors.Wrapf(err, `解析level[%s]`, levelText)
    80  		}
    81  		l.levelToPath[level] = path
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  /*
    88  NewConfigFromYamlData 从yaml数据中新建配置
    89  参数:
    90  *	yamlData	io.Reader   yaml数据 reader,不能为空
    91  返回值:
    92  *	config	*Config
    93  *	err   	error
    94  */
    95  func NewConfigFromYamlData(yamlData io.Reader) (config *Config, err error) {
    96  	config = NewConfig()
    97  	if err = yaml.NewDecoder(yamlData).Decode(config); err != nil {
    98  		return nil, errors.Wrap(err, `解析错误`)
    99  	}
   100  
   101  	return config, nil
   102  }
   103  
   104  /*
   105  NewConfigFromToml 从toml配置中构建
   106  参数:
   107  *	tomlData	[]byte 	参数1
   108  返回值:
   109  *	config  	*Config	返回值1
   110  *	err     	error  	返回值2
   111  */
   112  func NewConfigFromToml(tomlData []byte) (config *Config, err error) {
   113  	config = NewConfig()
   114  	if err = toml.Unmarshal(tomlData, config); err != nil {
   115  		return nil, errors.Wrap(err, `解析错误`)
   116  	}
   117  
   118  	return config, nil
   119  }
   120  
   121  /*
   122  Build 构建日志器
   123  参数:
   124  返回值:
   125  *	logger	Logger  日志器
   126  *	err   	error   错误
   127  */
   128  func (l *Config) Build(cores ...zapcore.Core) (logger Logger, err error) {
   129  	var (
   130  		underlyingLogger *zap.Logger
   131  		allCores         []zapcore.Core
   132  	)
   133  
   134  	if err = l.tidy(); err != nil {
   135  		return nil, errors.Wrap(err, `tidy`)
   136  	}
   137  
   138  	HiddenConsole = l.HideConsole
   139  	inputCores = cores
   140  
   141  	cfg := &zap.Config{
   142  		Level:            zap.NewAtomicLevelAt(l.Level),
   143  		Development:      true,
   144  		Encoding:         "console",
   145  		OutputPaths:      []string{"stderr"},
   146  		ErrorOutputPaths: []string{"stderr"},
   147  	}
   148  
   149  	if l.TimeZone == `` {
   150  		l.TimeZone = defaultTimeZone
   151  	}
   152  
   153  	if l.TimeLayout == `` {
   154  		l.TimeLayout = defaultTimeLayout
   155  	}
   156  
   157  	if l.location, err = time.LoadLocation(l.TimeZone); err != nil {
   158  		return nil, errors.Wrapf(err, `加载时区[%s]`, l.TimeZone)
   159  	}
   160  
   161  	// todo: 如何验证一个time layout 是否正确
   162  
   163  	cfg.EncoderConfig = l.newEncoderConfig()
   164  
   165  	if l.JSON {
   166  		encoder = zapcore.NewJSONEncoder(cfg.EncoderConfig)
   167  	} else {
   168  		encoder = zapcore.NewConsoleEncoder(cfg.EncoderConfig)
   169  	}
   170  
   171  	if l.FilePath != `` {
   172  		lumberjackLogger := &lumberjack.Logger{
   173  			Filename:   l.FilePath + ".log",
   174  			MaxSize:    l.Rotate.MaxSize, // megabytes
   175  			MaxBackups: l.Rotate.MaxBackups,
   176  			MaxAge:     l.Rotate.MaxAge, // days
   177  			Compress:   true,
   178  		}
   179  
   180  		fillLumberjack(lumberjackLogger)
   181  
   182  		writeSyncer = zapcore.NewMultiWriteSyncer(zapcore.AddSync(lumberjackLogger))
   183  
   184  		allCores = append(allCores, zapcore.NewCore(
   185  			encoder,
   186  			writeSyncer,
   187  			newLevelEnablerWithExcept(cfg.Level, l.levelToPath),
   188  		))
   189  	}
   190  
   191  	if l.levelToPath != nil {
   192  		for level := range l.levelToPath {
   193  			lumberjackLogger := &lumberjack.Logger{
   194  				Filename:   l.levelToPath[level],
   195  				MaxSize:    l.Rotate.MaxSize, // megabytes
   196  				MaxBackups: l.Rotate.MaxBackups,
   197  				MaxAge:     l.Rotate.MaxAge, // days
   198  				Compress:   true,
   199  			}
   200  
   201  			fillLumberjack(lumberjackLogger)
   202  
   203  			writeSyncer = zapcore.NewMultiWriteSyncer(zapcore.AddSync(lumberjackLogger))
   204  
   205  			allCores = append(allCores, zapcore.NewCore(encoder, writeSyncer, newLevelEnablerWithExcept(level, l.levelToPath, level)))
   206  		}
   207  	}
   208  
   209  	if !l.HideConsole {
   210  		allCores = append(allCores, zapcore.NewCore(encoder, os.Stdout, cfg.Level))
   211  	}
   212  
   213  	allCores = append(allCores, cores...)
   214  
   215  	core = zapcore.NewTee(allCores...)
   216  	underlyingLogger = zap.New(core, zap.AddCaller())
   217  
   218  	return newLogger(underlyingLogger.With(zap.String(`系统`, l.Service)), ``, 1, true, false, l.levelToPath), nil
   219  }
   220  
   221  func NewEasyLogger(debug, hideConsole bool, filePath, service string) (Logger, error) {
   222  	config := NewConfig()
   223  	config.Debug = debug
   224  	config.FilePath = filePath
   225  	config.HideConsole = hideConsole
   226  	config.Service = service
   227  
   228  	return config.Build()
   229  }
   230  
   231  /*
   232  newEncoderConfig 新建编码器配置
   233  参数:
   234  返回值:
   235  *	zapcore.EncoderConfig	zapcore.EncoderConfig
   236  */
   237  func (l *Config) newEncoderConfig() zapcore.EncoderConfig {
   238  	config := zapcore.EncoderConfig{
   239  		// Keys can be anything except the empty string.
   240  		TimeKey:       "T",
   241  		LevelKey:      "L",
   242  		NameKey:       "N",
   243  		CallerKey:     "C",
   244  		FunctionKey:   zapcore.OmitKey,
   245  		MessageKey:    "M",
   246  		StacktraceKey: "S",
   247  		LineEnding:    zapcore.DefaultLineEnding,
   248  		EncodeLevel:   zapcore.CapitalColorLevelEncoder,
   249  		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
   250  			enc.AppendString(t.In(l.location).Format(l.TimeLayout))
   251  		},
   252  		EncodeDuration: zapcore.StringDurationEncoder,
   253  		EncodeCaller:   zapcore.ShortCallerEncoder,
   254  	}
   255  
   256  	if l.Dev {
   257  		config.EncodeCaller = zapcore.FullCallerEncoder
   258  	}
   259  
   260  	return config
   261  }
   262  
   263  func fillLumberjack(lumberjackLogger *lumberjack.Logger) {
   264  	if lumberjackLogger.MaxSize == 0 {
   265  		lumberjackLogger.MaxSize = defaultRotateMaxSize
   266  	}
   267  
   268  	if lumberjackLogger.MaxAge == 0 {
   269  		lumberjackLogger.MaxAge = defaultRotateMaxAge
   270  	}
   271  
   272  	if lumberjackLogger.MaxBackups == 0 {
   273  		lumberjackLogger.MaxBackups = defaultRotateMaxBackups
   274  	}
   275  }
   276  
   277  type levelEnableWithExcept struct {
   278  	zapcore.LevelEnabler
   279  	except map[zapcore.Level]bool
   280  }
   281  
   282  func (l levelEnableWithExcept) Enabled(level zapcore.Level) bool {
   283  	if !l.LevelEnabler.Enabled(level) {
   284  		return false
   285  	}
   286  
   287  	return !l.except[level]
   288  }
   289  func newLevelEnablerWithExcept[T any](enabler zapcore.LevelEnabler, except map[zapcore.Level]T, exceptLevels ...zapcore.Level) levelEnableWithExcept {
   290  	result := levelEnableWithExcept{
   291  		LevelEnabler: enabler,
   292  		except:       make(map[zapcore.Level]bool, len(except)),
   293  	}
   294  
   295  	for level := range except {
   296  		result.except[level] = true
   297  	}
   298  
   299  	for _, exceptLevel := range exceptLevels {
   300  		delete(result.except, exceptLevel)
   301  	}
   302  
   303  	return result
   304  }