• Go编程快闪之 logrus日志库


    战术卧倒

    golang中常见的日志包是logrus, 根据logrus的胚子和我们的生产要求,给出一个生产可用的logrus实践姿势。

    主谓宾定状补

    logrus是一个结构化的、可插拔的、兼容golang标准log api的日志库。

    快速过一下能力

    • 支持对output=TTY增加关键字颜色
    • 内置JSONFormatter和TextFormatter(默认)两种Formatter
    • 支持输出logger所在的函数行位置 log.SetReportCaller(true)
    • 可以兼容golang内置的标准log库, 建议无脑替换
    • 鼓励输出可解析的日志字段,而不是大段的无法结构化的文本日志
    log.WithFields(log.Fields{
     "event": event,
     "topic": topic,
     "key": key,
    }).Fatal("Failed to send event")
    

    基于现状,凑了6个钱包上生产,下面给出一些自己的生产实践。

    添砖加瓦

    1. logrus不支持滚动日志

    好马配好鞍 https://github.com/lestrrat-go/file-rotatelogs 让你下雨天不再哭泣。

    它会根据配置自动按照时间切分日志,并滚动清理日志(不用配磁盘报警,不用担心磁盘满故障)。

    	logf, err := rotatelogs.New(
      	cfg.Log.LogDir+logName+".%Y%m%d%H%M",
      	rotatelogs.WithLinkName(cfg.Log.LogDir+logName),
      	rotatelogs.WithMaxAge(24*time.Hour),
      	rotatelogs.WithRotationTime(time.Hour),
      )
      if err != nil {
      	stdLog.Printf("failed to create rotatelogs: %s", err)
      	return
      }
    

    2. 日志格式化

    java生态默认日志输出格式:

    11:44:44.827 WARN [93ef3E0120160803114444] [main] [ClassPathXmlApplicationContext] Exception encountered during context initialization - cancelling refresh attempt
    

    在公司中javaer占据主流,故java的默认格式就成了公司集中式日志的"标准"格式。

    很明显,logrus默认的两种Formatter都不匹配。

    github.com/antonfisher/nested-logrus-formatter 让你柳暗花明。

    log.SetFormatter(&nested.Formatter{ // 嵌套日志兼容skynet日志格式
    		HideKeys:        true,
    		FieldsOrder:     []string{"region", "node", "topic"},
    		TimestampFormat: "2006-01-02 15:04:05.000", // 显示ms
    	})
    

    3. 自定义Hook用法:输出默认字段

    写本文的时候,发现logrus官方本身支持输出默认日志字段。

    requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
    requestLogger.Warn("something not great happened")
    

    Hook: 通常 钩子函数用于在触发某种事件时附带一些动作。

    logrus的Hook定义:logEntry满足指定的logLevel日志时, 你想要做的动作(你甚至可以不设置output直接在hook输出日志, 这就是内置write hook的实现)。

    type Hook interface {
    	Levels() []Level
    	Fire(*Entry) error
    }
    

    示例代码为logLevel>=info的logEntry,固定了2个日志字段。

    type FixedFieldHook struct {
    	LogLevels  []logrus.Level
    	FixedField map[string]string
    }
    
    // Fire will be called when some logging function is called with current hook
    // It will format log entry to string and write it to appropriate writer
    func (hook *FixedFieldHook) Fire(entry *logrus.Entry) error {
    	for k, v := range hook.FixedField {
    		entry.Data[k] = v
    	}
    	return nil
    }
    
    log.AddHook(&FixedFieldHook{ // Set fixed field
    		FixedField: map[string]string{"region": cfg.LocalRegion, "node": ip},
    		LogLevels: []logrus.Level{
    			logrus.InfoLevel,
    			logrus.ErrorLevel,
    			logrus.WarnLevel,
    			logrus.FatalLevel,
    		},
    	})
    

    抛砖引玉,战术卧倒。

    使用时是这样:

    func initLog(cfg config, logName string, log *logrus.Logger) {
    	_, err := os.Stat(cfg.Log.LogDir)
    	if os.IsNotExist(err) {
    		// stdLod.Debug("folder does not  exists.")
    		os.MkdirAll(cfg.Log.LogDir, os.ModeDir)
    	}
    	logf, err := rotatelogs.New(                             // 基于file形成时间滚动日志
    		cfg.Log.LogDir+logName+".%Y%m%d%H%M",
    		rotatelogs.WithLinkName(cfg.Log.LogDir+logName), // 让你始终在一个位置查看文件,即使文件已经滚动切分
    		rotatelogs.WithMaxAge(24*time.Hour),
    		rotatelogs.WithRotationTime(time.Hour),
    	)
    	if err != nil {
    		stdLog.Printf("failed to create rotatelogs: %s", err)
    		return
    	}
    	log.SetFormatter(&nested.Formatter{    // 设置nested日志格式
    		HideKeys:        true,
    		FieldsOrder:     []string{"region", "node", "topic"},
    		TimestampFormat: "2006-01-02 15:04:05.000", // 显示ms
    		NoColors:        true,
    	})
    	log.ReportCaller = true
    	log.AddHook(&FixedFieldHook{           // 设置默认字段
    		FixedField: map[string]string{"region": cfg.LocalRegion, "node": ip},
    		LogLevels: []logrus.Level{
    			logrus.InfoLevel,
    			logrus.ErrorLevel,
    			logrus.WarnLevel,
    			logrus.FatalLevel,
    		},
    	})
    	if !cfg.Log.Debug {
    		log.SetOutput(logf)
    		log.SetLevel(logrus.InfoLevel)
    	} else {
    		fileAndStdoutWriter := io.MultiWriter(logf, os.Stdout)
    		log.SetOutput(fileAndStdoutWriter)
    		log.SetLevel(logrus.DebugLevel)
    	}
    }
    
    
  • 相关阅读:
    【3D游戏建模全流程教学】使用3dsmax与UE4制作世界末日地铁场景
    测试八股文-Selenium
    一个案例搞懂工厂模式和单例模式
    第三十二节——组合式API计算属性+watch
    vue2.0-3.0的区别
    [RK3568][Android11] Tasklet
    大数据ClickHouse进阶(二十三):ClickHouse用户配置
    【JavaWeb】01_Tomcat、Servlet、Thymeleaf
    TensorFlow入门(十五、数据读取机制(2))
    CFdiv2-The Number of Imposters-(两种点集图上染色问题总结)
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/17428673.html