github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/logger/logger.go (about) 1 package logger 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/isyscore/isc-gobase/config" 7 "github.com/isyscore/isc-gobase/constants" 8 "github.com/isyscore/isc-gobase/isc" 9 "github.com/isyscore/isc-gobase/listener" 10 "github.com/isyscore/isc-gobase/store" 11 rotatelogs "github.com/lestrrat-go/file-rotatelogs" 12 "github.com/rifflock/lfshook" 13 "github.com/sirupsen/logrus" 14 "os" 15 "runtime" 16 "strings" 17 "sync" 18 "time" 19 ) 20 21 const ( 22 white = 29 23 black = 30 24 red = 31 25 green = 32 26 yellow = 33 27 purple = 35 28 blue = 36 29 gray = 37 30 ) 31 32 var gColor = false 33 var loggerMap map[string]*logrus.Logger 34 var rotateMap map[string]*rotatelogs.RotateLogs 35 var rootLogger *logrus.Logger 36 37 func init() { 38 _loggerMap := map[string]*logrus.Logger{} 39 loggerMap = _loggerMap 40 _rotateMap := map[string]*rotatelogs.RotateLogs{} 41 rotateMap = _rotateMap 42 rootLogger = Group("root") 43 44 _gColor := config.GetValueBoolDefault("base.logger.color.enable", false) 45 gColor = _gColor 46 } 47 48 func Group(groupNames... string) *logrus.Logger { 49 var resultLogger *logrus.Logger 50 groupNamesOfUnContain := []string{} 51 for _, groupName := range groupNames { 52 if logger, exit := loggerMap[groupName]; exit { 53 resultLogger = logger 54 } else { 55 groupNamesOfUnContain = append(groupNamesOfUnContain, groupName) 56 } 57 } 58 59 if resultLogger != nil { 60 return resultLogger 61 } else { 62 resultLogger = logrus.New() 63 resultLogger.SetReportCaller(true) 64 formatters := &StandardFormatter{} 65 resultLogger.Formatter = formatters 66 67 loggerDir := config.GetValueStringDefault("base.logger.home", "./logs/") 68 resultLogger.AddHook(lfshook.NewHook(lfshook.WriterMap{ 69 logrus.DebugLevel: rotateLogWithCache(loggerDir, "debug"), 70 logrus.InfoLevel: rotateLogWithCache(loggerDir, "info"), 71 logrus.WarnLevel: rotateLogWithCache(loggerDir, "warn"), 72 logrus.ErrorLevel: rotateLogWithCache(loggerDir, "error"), 73 logrus.PanicLevel: rotateLogWithCache(loggerDir, "panic"), 74 logrus.FatalLevel: rotateLogWithCache(loggerDir, "fatal"), 75 }, formatters)) 76 } 77 78 // 值最大的级别,对应的level最小,比如Debug对应的数值比Info要大 79 maxValueLevel := logrus.PanicLevel 80 for _, groupName := range groupNamesOfUnContain { 81 var finalGroupLevel string 82 rootLevel := config.GetValueStringDefault("base.logger.level", "info") 83 groupLevel := config.GetValueString("base.logger.group." + groupName + ".level") 84 if groupLevel != "" { 85 finalGroupLevel = groupLevel 86 } else { 87 finalGroupLevel = rootLevel 88 } 89 90 lgLevel, err := logrus.ParseLevel(finalGroupLevel) 91 if err != nil { 92 lgLevel = logrus.InfoLevel 93 } 94 95 if lgLevel > maxValueLevel { 96 maxValueLevel = lgLevel 97 } 98 } 99 100 resultLogger.SetLevel(maxValueLevel) 101 102 for _, groupName := range groupNamesOfUnContain { 103 loggerMap[groupName] = resultLogger 104 } 105 return resultLogger 106 } 107 108 func doGroup(groupName string) *logrus.Logger { 109 if groupName == "" { 110 return rootLogger 111 } 112 if logger, exit := loggerMap[groupName]; exit { 113 return logger 114 } 115 116 if loggerMap == nil { 117 loggerMap = map[string]*logrus.Logger{} 118 } 119 logger := logrus.New() 120 logger.SetReportCaller(true) 121 formatters := &StandardFormatter{} 122 logger.Formatter = formatters 123 124 loggerDir := config.GetValueStringDefault("base.logger.home", "./logs/") 125 logger.AddHook(lfshook.NewHook(lfshook.WriterMap{ 126 logrus.DebugLevel: rotateLogWithCache(loggerDir, "debug"), 127 logrus.InfoLevel: rotateLogWithCache(loggerDir, "info"), 128 logrus.WarnLevel: rotateLogWithCache(loggerDir, "warn"), 129 logrus.ErrorLevel: rotateLogWithCache(loggerDir, "error"), 130 logrus.PanicLevel: rotateLogWithCache(loggerDir, "panic"), 131 logrus.FatalLevel: rotateLogWithCache(loggerDir, "fatal"), 132 }, formatters)) 133 134 var finalGroupLevel string 135 rootLevel := config.GetValueStringDefault("base.logger.level", "info") 136 groupLevel := config.GetValueString("base.logger.group." + groupName + ".level") 137 if groupLevel != "" { 138 finalGroupLevel = groupLevel 139 } else { 140 finalGroupLevel = rootLevel 141 } 142 143 lgLevel, err := logrus.ParseLevel(finalGroupLevel) 144 if err != nil { 145 lgLevel = logrus.InfoLevel 146 } 147 logger.SetLevel(lgLevel) 148 149 loggerMap[groupName] = logger 150 return logger 151 } 152 153 func InitLog() { 154 rootLogger = Group("root") 155 loggerDir := config.GetValueStringDefault("base.logger.home", "./logs/") 156 rootLogger.AddHook(lfshook.NewHook(lfshook.WriterMap{ 157 logrus.DebugLevel: rotateLog(loggerDir, "debug"), 158 logrus.InfoLevel: rotateLog(loggerDir, "info"), 159 logrus.WarnLevel: rotateLog(loggerDir, "warn"), 160 logrus.ErrorLevel: rotateLog(loggerDir, "error"), 161 logrus.PanicLevel: rotateLog(loggerDir, "panic"), 162 logrus.FatalLevel: rotateLog(loggerDir, "fatal"), 163 }, &StandardFormatter{})) 164 lgLevel, err := logrus.ParseLevel(config.GetValueStringDefault("base.logger.level", "info")) 165 if err != nil { 166 lgLevel = logrus.InfoLevel 167 } 168 rootLogger.SetLevel(lgLevel) 169 170 _gColor := config.GetValueBoolDefault("base.logger.color.enable", false) 171 gColor = _gColor 172 173 listener.AddListener(listener.EventOfConfigChange, ConfigChangeListener) 174 } 175 176 func ConfigChangeListener(event listener.BaseEvent) { 177 ev := event.(listener.ConfigChangeEvent) 178 if ev.Key == "base.logger.level" { 179 SetGlobalLevel(ev.Value) 180 } else if strings.HasPrefix(ev.Key, "base.logger.group") { 181 words := strings.Split(ev.Key, ".") 182 if len(words) != 5 { 183 return 184 } 185 _group := words[3] 186 _level := ev.Value 187 le, err := logrus.ParseLevel(_level) 188 if err != nil { 189 return 190 } 191 Group(_group).SetLevel(le) 192 } 193 } 194 195 func SetGlobalLevel(strLevel string) { 196 level, err := logrus.ParseLevel(strLevel) 197 if err == nil { 198 rootLogger.SetLevel(level) 199 } 200 } 201 202 func InfoDirect(v ...any) { 203 rootLogger.Info(v...) 204 } 205 206 func WarnDirect(v ...any) { 207 rootLogger.Warn(v...) 208 } 209 210 func ErrorDirect(v ...any) { 211 rootLogger.Error(v...) 212 } 213 214 func FatalDirect(v ...any) { 215 rootLogger.Fatal(v...) 216 } 217 218 func PanicDirect(v ...any) { 219 rootLogger.Panic(v...) 220 } 221 222 func DebugDirect(v ...any) { 223 rootLogger.Debug(v...) 224 } 225 226 func TraceDirect(v ...any) { 227 rootLogger.Trace(v...) 228 } 229 230 func Info(format string, v ...any) { 231 rootLogger.Infof(format, v...) 232 } 233 234 func Warn(format string, v ...any) { 235 rootLogger.Warnf(format, v...) 236 } 237 238 func Error(format string, v ...any) { 239 rootLogger.Errorf(format, v...) 240 } 241 242 func Debug(format string, v ...any) { 243 rootLogger.Debugf(format, v...) 244 } 245 246 func Trace(format string, v ...any) { 247 rootLogger.Tracef(format, v...) 248 } 249 250 func Panic(format string, v ...any) { 251 rootLogger.Panicf(format, v...) 252 } 253 254 func Fatal(format string, v ...any) { 255 rootLogger.Fatalf(format, v...) 256 } 257 258 func Record(level, format string, v ...any) { 259 switch strings.ToLower(level) { 260 case "debug": 261 Debug(format, v) 262 case "info": 263 Info(format, v) 264 case "warn": 265 Warn(format, v) 266 case "error": 267 Error(format, v) 268 case "panic": 269 Panic(format, v) 270 case "fatal": 271 Fatal(format, v) 272 default: 273 Debug(format, v) 274 } 275 } 276 277 func rotateLog(path, level string) *rotatelogs.RotateLogs { 278 if rotateMap == nil { 279 rotateMap = map[string]*rotatelogs.RotateLogs{} 280 } 281 282 if path == "" { 283 path = "./logs/" 284 } 285 286 maxSizeStr := config.GetValueStringDefault("base.logger.rotate.max-size", "300MB") 287 maxHistoryStr := config.GetValueStringDefault("base.logger.rotate.max-history", "60d") 288 rotateTimeStr := config.GetValueStringDefault("base.logger.rotate.time", "1d") 289 290 rotateOptions := []rotatelogs.Option{rotatelogs.WithLinkName(path + "app-" + level + ".log")} 291 if maxSizeStr != "" { 292 rotateOptions = append(rotateOptions, rotatelogs.WithRotationSize(isc.ParseByteSize(maxSizeStr))) 293 } 294 295 _maxHistory, err := time.ParseDuration(maxHistoryStr) 296 if err == nil { 297 rotateOptions = append(rotateOptions, rotatelogs.WithMaxAge(_maxHistory)) 298 } 299 300 _rotateTime, err := time.ParseDuration(rotateTimeStr) 301 if err == nil { 302 rotateOptions = append(rotateOptions, rotatelogs.WithRotationTime(_rotateTime)) 303 } 304 305 data, _ := rotatelogs.New(path+"app-"+level+".%Y%m%d.log", rotateOptions...) 306 rotateMap[path+"-"+level] = data 307 return data 308 } 309 310 func rotateLogWithCache(path, level string) *rotatelogs.RotateLogs { 311 if pRotateValue, exist := rotateMap[path+"-"+level]; exist { 312 return pRotateValue 313 } 314 315 return rotateLog(path, level) 316 } 317 318 type StandardFormatter struct{} 319 320 func (m *StandardFormatter) Format(entry *logrus.Entry) ([]byte, error) { 321 var b *bytes.Buffer 322 if entry.Buffer != nil { 323 b = entry.Buffer 324 } else { 325 b = &bytes.Buffer{} 326 } 327 328 var fields []string 329 for k, v := range entry.Data { 330 fields = append(fields, fmt.Sprintf("%v=%v", k, v)) 331 } 332 333 level := entry.Level 334 timestamp := entry.Time.Format("2006-01-02 15:04:05") 335 var funPath string 336 if entry.HasCaller() { 337 frame := getCallerFrame() 338 funPath = fmt.Sprintf("%s:%d#%s", shortLogPath(frame.File), frame.Line, functionName(frame)) 339 } else { 340 funPath = fmt.Sprintf("%s", entry.Message) 341 } 342 343 var fieldsStr string 344 if len(fields) != 0 { 345 fieldsStr = fmt.Sprintf("[\x1b[%dm%s\x1b[0m]", blue, strings.Join(fields, " ")) 346 } 347 var newLog string 348 var levelColor = gray 349 if gColor { 350 switch level { 351 case logrus.DebugLevel: 352 levelColor = blue 353 case logrus.InfoLevel: 354 levelColor = green 355 case logrus.WarnLevel: 356 levelColor = yellow 357 case logrus.ErrorLevel: 358 levelColor = red 359 case logrus.FatalLevel: 360 levelColor = red 361 case logrus.PanicLevel: 362 levelColor = red 363 } 364 newLog = fmt.Sprintf("[%s] \x1b[%dm%s [%s]\x1b[0m [%s] [%v] \x1b[%dm%s\x1b[0m \x1b[%dm%s\x1b[0m %s %s\n", 365 timestamp, 366 black, 367 os.Getenv("HOSTNAME"), 368 config.GetValueStringDefault("base.application.name", "isc-gobase"), 369 store.Get(constants.TRACE_HEAD_ID), store.Get(constants.TRACE_HEAD_USER_ID), 370 levelColor, 371 strings.ToUpper(entry.Level.String()), 372 black, 373 funPath, 374 entry.Message, 375 fieldsStr) 376 } else { 377 newLog = fmt.Sprintf("[%s] %s [%s] [%s] [%v] %s %s %s %s\n", 378 timestamp, 379 os.Getenv("HOSTNAME"), 380 config.GetValueStringDefault("base.application.name", "isc-gobase"), 381 store.Get(constants.TRACE_HEAD_ID), store.Get(constants.TRACE_HEAD_USER_ID), 382 strings.ToUpper(entry.Level.String()), 383 funPath, 384 entry.Message, 385 fieldsStr) 386 } 387 388 b.WriteString(newLog) 389 return b.Bytes(), nil 390 } 391 392 const ( 393 maximumCallerDepth int = 25 394 knownBaseLoggerFrames int = 5 395 ) 396 397 var callerInitOnce sync.Once 398 var minimumCallerDepth = 0 399 var baseLoggerPackage string 400 401 func getPackageName(f string) string { 402 for { 403 lastPeriod := strings.LastIndex(f, ".") 404 lastSlash := strings.LastIndex(f, "/") 405 if lastPeriod > lastSlash { 406 f = f[:lastPeriod] 407 } else { 408 break 409 } 410 } 411 return f 412 } 413 414 func getCallerFrame() *runtime.Frame { 415 pcs := make([]uintptr, maximumCallerDepth) 416 callerInitOnce.Do(func() { 417 pcs := make([]uintptr, maximumCallerDepth) 418 _ = runtime.Callers(0, pcs) 419 420 for i := 0; i < maximumCallerDepth; i++ { 421 funcName := runtime.FuncForPC(pcs[i]).Name() 422 if strings.Contains(funcName, "logger.getCallerFrame") { 423 baseLoggerPackage = getPackageName(funcName) 424 break 425 } 426 } 427 428 minimumCallerDepth = knownBaseLoggerFrames 429 }) 430 431 pcs = make([]uintptr, maximumCallerDepth) 432 depth := runtime.Callers(minimumCallerDepth, pcs) 433 frames := runtime.CallersFrames(pcs[:depth]) 434 435 for f, again := frames.Next(); again; f, again = frames.Next() { 436 pkg := getPackageName(f.Function) 437 if pkg != baseLoggerPackage && pkg != "github.com/sirupsen/logrus" { 438 return &f 439 } 440 } 441 return nil 442 } 443 444 func functionName(frame *runtime.Frame) string { 445 pathMeta := strings.Split(frame.Function, ".") 446 if len(pathMeta) > 1 { 447 return pathMeta[len(pathMeta)-1] 448 } 449 return frame.Function 450 } 451 452 func shortLogPath(logPath string) string { 453 loggerPath := config.GetValueStringDefault("base.logger.path.type", "short") 454 if loggerPath == "short" { 455 pathMeta := strings.Split(logPath, string(os.PathSeparator)) 456 if len(pathMeta) > 3 { 457 return pathMeta[len(pathMeta)-3] + string(os.PathSeparator) + pathMeta[len(pathMeta)-2] + string(os.PathSeparator) + pathMeta[len(pathMeta)-1] 458 } 459 return logPath 460 } else if loggerPath == "full" { 461 pathMeta := strings.Split(logPath, "@2/project") 462 if len(pathMeta) > 1 { 463 pathMeta[0] = "../.." 464 return strings.Join(pathMeta, "") 465 } 466 return logPath 467 } else { 468 return logPath 469 } 470 }