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 }