• 15最兼容标准库的日志库logrus.md


    一 logrus介绍

    1.1 log标准库优缺点

    优点

    Go标准库的log日志库非常简单

    可以便设置任何io.Writer作为日志输出位置

    缺点

    1 仅仅提供了print,panic和fatal三个函数,不支持info/debug等多个级别

    2 记录错误有Fatal和Panic;Fatal通过调用os.Exit(1)来结束程序;Panic在写入日志后抛出一个panic;缺少ERROR日志级别,在不抛出异常和退出程序的情况下记录日志

    3 不支持多输出 - 同时支持标准输出,文件等

    4 缺乏日志格式化的能力,例如:记录函数名和行号,格式化日期和时间格式等

    5 可读性与结构化差,没有Json格式或有分隔符,不方便后续的日志采集、监控等

    6 对于更精细的日志级别、日志文件分割,以及日志分发等,没有提供支持

    1.2 Go中常用第三方日志库

    在Go的世界,流行的日志框架有logrus、zap、zerolog等

    logrus

    目前Github上star数量最多的日志库

    项目地址: https://github.com/sirupsen/logrus

    Stars数量:20.3k

    zap

    是Uber推出的一个快速、结构化的分级日志库

    项目地址:https://github.com/uber-go/zap

    官方文档:https://pkg.go.dev/go.uber.org/zap

    Stars数量:20.3k

    zerolog

    它的 API 设计非常注重开发体验和性能。zerolog只专注于记录 JSON 格式的日志,号称 0 内存分配

    项目地址:https://github.com/rs/zerolog

    Stars数量:6.2k

    二 logrus

    2.1 logrus特点

    优点

    • 完全兼容Go标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果之前项目使用标准库日志模块,完全可以以最低的代价迁移到logrus上
    • 可扩展的Hook机制:允许使用者通过hook的方式将日志分发到任意地方,如本地文件系统、标准输出、linfluxdb、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等
    • 可选的日志输出格式:logrus内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式
    • Field机制:logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志
    • logrus是一个可插拔的、结构化的日志框架,很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志

    缺点

    尽管 logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:

    • 没有提供行号和文件名的支持
    • 输出到本地文件系统没有提供日志分割功能
    • 官方没有提供输出到ELK等日志处理中心的功能

    但是这些功能都可以通过自定义hook来实现

    2.2 logrus配置

    日志级别

    logrus有7个日志级别,依次是Trace --> Debug --> Info --> Warning -->Error --> Fatal -->Panic

    //  只输出不低于当前级别是日志数据
    logrus.SetLevel(logrus.DebugLevel)
    

    日志格式

    logrus内置了JSONFormatterTextFormatter两种格式,也可以通过Formatter接口定义日志格式

     // TextFormatter格式
     logrus.SetFormatter(&logrus.TextFormatter{
        ForceColors:               true,
        EnvironmentOverrideColors: true,
        TimestampFormat:           "2006-01-02 15:04:05", //时间格式
        // FullTimestamp:true,
        // DisableLevelTruncation:true,
     })
     // JSONFormatter格式
     logrus.SetFormatter(&logrus.JSONFormatter{
        PrettyPrint:     false,                 //格式化
        TimestampFormat: "2006-01-02 15:04:05", //时间格式
     })
    

    输出文件

     logfile, _ := os.OpenFile("./log.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
     logrus.SetOutput(logfile) //默认为os.stderr
    //logrus.SetOutput(io.MultiWriter(os.Stdout, logfile)) // 输出到多个位置
    

    日志定位

    定位行号(如:func=main.main file="./xxx.go:38"

    logrus.SetReportCaller(true)
    

    2.3 快速使用

    go get github.com/sirupsen/logrus

    package main
    
    import (
    	"github.com/sirupsen/logrus"
    	"os"
    )
    
    func init() {
    	// 1 日志级别为debug
    	logrus.SetLevel(logrus.DebugLevel)
    	//2 日志格式为json格式
    	logrus.SetFormatter(&logrus.JSONFormatter{
    		TimestampFormat: "2006-01-02 15:04:05",
    	})
    	// 日志格式为文本格式
    	//logrus.SetFormatter(&logrus.TextFormatter{
    	//	ForceColors:               true,
    	//	EnvironmentOverrideColors: true,
    	//	TimestampFormat:           "2006-01-02 15:04:05", //时间格式
    	//	FullTimestamp:true,  // 显示完整时间
    	//	DisableLevelTruncation:true,
    	//})
    
    	//3 输出文件为app.log
    	logfile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    	//logrus.SetOutput(io.MultiWriter(os.Stdout,logfile)) // 即写到控制台,又写到文件中
    	logrus.SetOutput(logfile) // 只写到文件中
    	//不写默认为os.stderr
    	// 4 日志定位--显示打印日志文件和位置
    	logrus.SetReportCaller(true)
    }
    func main() {
    	logrus.Infoln("info--日志数据")
    	logrus.Debugln("debug--日志数据")
    	logrus.Errorln("err--日志数据")
    }
    
    
    

    2.4 两个自带formatter和自定义

    TextFormatter

    type TextFormatter struct {
    	DisableColors bool // 开启颜色显示
    	
    	DisableTimestamp bool // 开启时间显示
    
    	TimestampFormat string	// 自定义时间格式
    
    	QuoteEmptyFields bool	//空字段括在引号中
    
    	CallerPrettyfier func(*runtime.Frame) (function string, file string) //用于自定义方法名和文件名的输出
    }
    

    JsonFormatter

    type JSONFormatter struct {
    	TimestampFormat string // 自定义时间格式
    
    	DisableTimestamp bool // 开启时间显示
    
    	CallerPrettyfier func(*runtime.Frame) (function string, file string) //用于自定义方法名和文件名的输出
    
    	PrettyPrint bool //将缩进所有json日志
    }
    

    自定义Formatter

    //只需要实现该接口
    type Formatter interface {
    	Format(*Entry) ([]byte, error)
    }
    
    // 其中entry参数
    type Entry struct {
    	// Contains all the fields set by the user.
    	Data Fields
    	
    	// Time at which the log entry was created
    	Time time.Time
    
    	// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
    	Level Level
    
    	//Calling method, with package name
    	Caller *runtime.Frame
    
    	//Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic  
    	Message string 
    	
    	//When formatter is called in entry.log(), a Buffer may be set to entry
    	Buffer *bytes.Buffer
    }
    

    2.5 日志打印方法

    FieldLogger接口: FieldLogger定义了所有日志打印的方法

    type FieldLogger interface {
       WithField(key string, value interface{}) *Entry
       WithFields(fields Fields) *Entry
       WithError(err error) *Entry
    
       Debugf(format string, args ...interface{})
       Infof(format string, args ...interface{})
       Printf(format string, args ...interface{})
       Warnf(format string, args ...interface{})
       Warningf(format string, args ...interface{})
       Errorf(format string, args ...interface{})
       Fatalf(format string, args ...interface{})
       Panicf(format string, args ...interface{})
    
       Debug(args ...interface{})
       Info(args ...interface{})
       Print(args ...interface{})
       Warn(args ...interface{})
       Warning(args ...interface{})
       Error(args ...interface{})
       Fatal(args ...interface{})
       Panic(args ...interface{})
    
       Debugln(args ...interface{})
       Infoln(args ...interface{})
       Println(args ...interface{})
       Warnln(args ...interface{})
       Warningln(args ...interface{})
       Errorln(args ...interface{})
       Fatalln(args ...interface{})
       Panicln(args ...interface{})
    }
    

    2.6 logrus实例

    实例日志打印方式一

    默认实例 (函数),即通过logrus包提供的函数(覆盖了FieldLogger接口的所有方法),直接打印日志。但其实logrus包函数是调用了logrus.Loger默认实例。

    // 直接调用包函数
    func main() {
       logrus.Infoln("info--日志")
       logrus.Errorln("err--日志")
    }
    

    实例日志打印方式二

    Logger实例(对象),它实现了FieldLogger接口。

    func main() {
    	//var loger = logrus.New()
    	var loger = logrus.StandardLogger() // 看源码,本质就是logrus.New()
    	loger.Formatter = &logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}
    	loger.Infoln("info--日志")
    }
    

    实例日志打印方式三

    Entry示例(对象),它也实现了FieldLogger接口,是最终是日志打印入口。

    • 这里用到了Field机制,logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
    func main() {
    	logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
    	// Entry实例
    	entry := logrus.WithFields(logrus.Fields{
    		"global": "全局字段-每个日志都会输出",
    	})
    	entry.WithFields(logrus.Fields{"module": "自定义字段--用户模块"}).Info("info--日志")
    	entry.WithFields(logrus.Fields{"module": "自定义字段--商品模块"}).Error("Error--日志")
    }
    

    2.7 HOOK机制

    • hook即钩子,拦截器。它为logrus提供了强大的功能扩展,如将日志分发到任意地方,如本地文件系统、logstashes等,或者切割日志、定义日志内容和格式等。hook接口原型如下:
    type Hook interface {
       Levels() []Level   //日志级别
       Fire(*Entry) error //打印入口(Entry对象)
    }
    

    Hook - 实现日志切割功能

    需要借助于第三方(日志轮转库):github.com/lestrrat-go/file-rotatelogs
    和:github.com/rifflock/lfshook

    package main
    
    import (
    	"github.com/lestrrat-go/file-rotatelogs"
    	"github.com/rifflock/lfshook"
    	"github.com/sirupsen/logrus"
    	"time"
    )
    
    //  说明:按时间切割日志文件(2秒创建一个日志文件)
    func main() {
    	// 保存日志文件名为app_hook开头,2s切换一个日志文件,最多保留5份
    	hook := NewLfsHook("app_hook", time.Second*2, 5)
    	// 加入钩子
    	logrus.AddHook(hook)
    	// 先打印一句日志
    	logrus.Infoln("info---测试开始")
    	// 通过WithFields方式创建log,写入通用内容module
    	log := logrus.WithFields(logrus.Fields{"module": "用户模块"})
    	// 每隔一秒,调用一次info,一次err,最终只保留5个日志文件
    	for i := 0; i < 15; i++ {
    		log.Infoln("info--->成功", i)
    		time.Sleep(time.Second)
    		log.Errorln("err--->成功", i)
    	}
    }
    
    // 日志钩子(日志拦截,并重定向)
    func NewLfsHook(logName string, rotationTime time.Duration, leastDay uint) logrus.Hook {
    	writer, err := rotatelogs.New(
    		// 1 日志文件名字
    		logName+".%Y%m%d%H%M%S",
    		// 2 日志周期(默认每86400秒/一天旋转一次)
    		rotatelogs.WithRotationTime(rotationTime),
    		// 3 清除历史 (WithMaxAge和WithRotationCount只能选其一)
    		//rotatelogs.WithMaxAge(time.Hour*24*7), //默认每7天清除下日志文件
    		rotatelogs.WithRotationCount(leastDay), //只保留最近的N个日志文件
    	)
    	if err != nil {
    		panic(err)
    	}
    
    	// 可设置按不同level创建不同的文件名,咱们把6中日志都写到同一个writer中
    	lfsHook := lfshook.NewHook(lfshook.WriterMap{
    		logrus.DebugLevel: writer,
    		logrus.InfoLevel:  writer,
    		logrus.WarnLevel:  writer,
    		logrus.ErrorLevel: writer,
    		logrus.FatalLevel: writer,
    		logrus.PanicLevel: writer,
    	}, &logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
    
    	return lfsHook
    }
    
    

    Hook - 写入Redis

    将日志输出到redis

    需要借助于第三方模块:github.com/rogierlommers/logrus-redis-hook

    package main
    
    import (
    	logredis "github.com/rogierlommers/logrus-redis-hook"
    	"github.com/sirupsen/logrus"
    )
    
    func init() {
    	hookConfig := logredis.HookConfig{
    		Host:     "localhost",
    		Key:      "test",
    		Format:   "v1",
    		App:      "my_app_name",
    		Port:     6379,
    		Hostname: "my_app_hostname",
    		DB:       0,
    		TTL:      3600,
    	}
    	hook, err := logredis.NewHook(hookConfig)
    	if err == nil {
    		logrus.AddHook(hook)
    	} else {
    		logrus.Errorf("日志写入redis配置出错: %q", err)
    	}
    }
    
    func main() {
    	logrus.WithFields(logrus.Fields{"module": "用户模块"}).Info("info--日志--写入redis")
    	logrus.WithFields(logrus.Fields{"module": "用户模块"}).Error("Error--日志--写入redis")
    
    }
    
    // 测试:
    // 1.启动redis服务: redis-server
    // 2.监控redis数据: redis-cli monitor
    

    其他Hook

    2.8 Fatal处理

    logrus的Fatal输出,会执行os.Exit(1)。logrus提供RegisterExitHandler方法,可以在系统异常时调用一些资源释放api等,让应用正确地关闭。

    func main() {
    	logrus.RegisterExitHandler(func() {
    		fmt.Println("发生了fatal异常,执行关闭文件等工作")
    	})
    
    	logrus.Warnln("warn测试")
    	logrus.Fatalln("fatal测试")
    	logrus.Infoln("info测试") //不会执行
    
    }
    

    三 Gin中集成

    • 将gin框架的日志定向到logrus日志文件
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"github.com/sirupsen/logrus"
    	"io"
    	"os"
    )
    
    func init() {
    	// 日志输出格式
    	logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"})
    
    	// 日志输出路径
    	logfile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    	logrus.SetOutput(io.MultiWriter(os.Stdout, logfile)) // 日志写到控制台和文件中
    
    	// Gin日志重定向
    	gin.DisableConsoleColor()                              //不需要颜色
    	// gin的日志写到控制台和日志文件中
    	gin.DefaultWriter = io.MultiWriter(os.Stdout, logfile) //默认是:os.Stdout
    	//gin.DefaultWriter = logfile 
    }
    
    //测试:curl 0.0.0.0:8080/index
    func main() {
    	log := logrus.WithFields(logrus.Fields{
    		"module": "用户模块",
    	})
    
    	r := gin.Default()
    	r.GET("/", func(c *gin.Context) {
    		log.Infoln("info--->gin日志数据")
    		c.String(200, "ok")
    	})
    	r.Run(":8080")
    }
    
    

    四 logrus线程安全

    • 默认情况下,logrus的api都是线程安全的,其内部通过互斥锁来保护并发写。
    • 互斥锁在调用hooks或者写日志的时候执行。
    • 如果不需要锁,可以调用logger.SetNoLock()来关闭。

    可以关闭logrus互斥锁的情形:

    • 没有设置hook,或者所有的hook都是线程安全的实现。
    • 写日志到logger.Out已经是线程安全的了。例如,logger.Out已经被锁保护,或者写文件时,文件是以O_APPEND方式打开的,并且每次写操作都小于4k。
  • 相关阅读:
    JavaScript获取http,http://请求协议头,域名,端口,url
    JAVA Pattern正则获取大括号中内容,substring字符串截取获取大括号中内容
    系统时间相关
    简单搭建nfs
    电信电话相关
    windows常用设置
    sort用法
    vim查询替换相关
    vim常用命令 技巧
    编绎vim8.2+deepin v15.11
  • 原文地址:https://www.cnblogs.com/liuqingzheng/p/16244545.html
Copyright © 2020-2023  润新知