• 【Golang】关于Go中logrus的用法


    一、标准日志库log

    在日常开发中,日志是必不可少的功能。虽然有时可以用fmt库输出一些信息,但是灵活性不够。Go 标准库提供了一个日志库log

    1、快速使用

    log是 Go 标准库提供的,不需要另外安装

    package main
    
    import (
      "log"
    )
    
    type User struct {
      Name string
      Age  int
    }
    
    func main() {
      u := User{
        Name: "test",
        Age:  18,
      }
    
      log.Printf("%s login, age:%d", u.Name, u.Age)
      log.Panicf("Oh, system error when %s login", u.Name)
      log.Fatalf("Danger! hacker %s login", u.Name)
    }
    

    log默认输出到标准错误(stderr),每条日志前会自动加上日期和时间。如果日志不是以换行符结尾的,那么log会自动加上换行符。即每条日志会在新行中输出。

    log提供了三组函数:

    • Print/Printf/Println:正常输出日志;
    • Panic/Panicf/Panicln:输出日志后,以拼装好的字符串为参数调用panic
    • Fatal/Fatalf/Fatalln:输出日志后,调用os.Exit(1)退出程序。

    命名比较容易辨别,带f后缀的有格式化功能,带ln后缀的会在日志后增加一个换行符。

    注意,上面的程序中由于调用log.Panicfpanic,所以log.Fatalf并不会调用

    2、自定义选项

    选项

    • Ldate:输出当地时区的日期,如2020/02/07
    • Ltime:输出当地时区的时间,如11:45:45
    • Lmicroseconds:输出的时间精确到微秒,设置了该选项就不用设置Ltime了。如11:45:45.123123
    • Llongfile:输出长文件名+行号,含包名,如github.com/darjun/go-daily-lib/log/flag/main.go:50
    • Lshortfile:输出短文件名+行号,不含包名,如main.go:50
    • LUTC:如果设置了LdateLtime,将输出 UTC 时间,而非当地时区。
    log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds)
    log.SetPrefix("Debug: ")

    3、输出到文件

    file := "./" + "message" + ".txt"
    logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
    if err != nil {
    	panic(err)
    }
    log.SetOutput(logFile) // 将文件设置为log输出的文件
    log.SetPrefix("[qcpz]")
    log.SetFlags(log.LstdFlags | log.Lshortfile | log.Ldate | log.Ltime)
    

    4、自定义输出

    实际上,log库为我们定义了一个默认的Logger,名为std,意为标准日志。我们直接调用的log库的方法,其内部是调用std的对应方法:

    // src/log/log.go
    var std = New(os.Stderr, "", LstdFlags)
    
    func Printf(format string, v ...interface{}) {
      std.Output(2, fmt.Sprintf(format, v...))
    }
    
    func Fatalf(format string, v ...interface{}) {
      std.Output(2, fmt.Sprintf(format, v...))
      os.Exit(1)
    }
    
    func Panicf(format string, v ...interface{}) {
      s := fmt.Sprintf(format, v...)
      std.Output(2, s)
      panic(s)
    }

    log.New接受三个参数:

    • io.Writer:日志都会写到这个Writer中;
    • prefix:前缀,也可以后面调用logger.SetPrefix设置;
    • flag:选项,也可以后面调用logger.SetFlag设置。

    可以使用io.MultiWriter实现多目的地输出

    package main
    
    import (
    	"bytes"
    	"io"
    	"log"
    	"os"
    )
    
    type User struct {
    	Name string
    	Age  int
    }
    
    func main() {
    	u := User{
    		Name: "test",
    		Age:  18,
    	}
    
    	writer1 := &bytes.Buffer{}
    	writer2 := os.Stdout
    	writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755)
    	if err != nil {
    		log.Fatalf("create file log.txt failed: %v", err)
    	}
    
    	logger := log.New(io.MultiWriter(writer1, writer2, writer3), "", log.Lshortfile|log.LstdFlags)
    	logger.Printf("%s login, age:%d", u.Name, u.Age)
    }

    二、logrus的使用

    1、golang日志库

    golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持。所以催生了很多第三方的日志库,但是在golang的世界里,没有一个日志库像slf4j那样在Java中具有绝对统治地位。golang中,流行的日志框架包括logrus、zap、zerolog、seelog等。

    logrus是目前Github上star数量最多的日志库。logrus功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志。

    zap是Uber推出的一个快速、结构化的分级日志库。具有强大的ad-hoc分析功能,并且具有灵活的仪表盘。

    seelog提供了灵活的异步调度、格式化和过滤功能。

    2、logrus特性

    GitHub访问地址:https://github.com/sirupsen/logrus

    logrus具有以下特性:

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

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

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

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

    3、日志格式

    比如,我们约定日志格式为 Text,包含字段如下:

    请求时间日志级别状态码执行时间请求IP请求方式请求路由

    4、使用方法

    package main
    
    import (
        "flag"
        "fmt"
        "os"
        "path"
        "runtime"
        "strings"
        "time"
    
        "github.com/Sirupsen/logrus"
    )
    
    func logrus_test() {
    
        fmt.Printf("<<<<<<<<<logrus test>>>>>>>>>>>>>>
    ")
    
        logrus.WithFields(logrus.Fields{
            "sb": "sbvalue",
        }).Info("A walrus appears")
    
        log1 := logrus.New()
        fmt.Printf("log1 level: %d
    ", log1.Level)
        log1.Debug("log1 debug")
        log1.Debugf("log1 debug f, %d", 10)
        log1.Info("log1 info")
        log1.Warn("log1 warn")
        log1.Error("log1 error")
        // log1.Panic("log1 panic")
    
        log1.SetLevel(logrus.ErrorLevel)
        fmt.Printf("after set log1 level to errorlevel
    ")
        log1.Debug("log1 debug")
    
        fmt.Printf("-------------test formater-------------
    ")
        log1.SetLevel(logrus.DebugLevel)
        log1.Formatter = &logrus.TextFormatter{
            DisableColors:  true,
            FullTimestamp:  true,
            DisableSorting: true,
        }
    
        log1.Debug("log text formatter test")
    
        fmt.Printf("-----------json formatter-------------
    ")
        log1.Formatter = &logrus.JSONFormatter{}
        log1.Debug("log json formatter test")
    
        fmt.Printf("-----------log to file test-----------
    ")
        log2 := logrus.New()
        log2.SetLevel(logrus.DebugLevel)
        log2.Formatter = &logrus.TextFormatter{
            DisableColors:  true,
            FullTimestamp:  true,
            DisableSorting: true,
        }
    
        logger_name := "logrus"
        cur_time := time.Now()
        log_file_name := fmt.Sprintf("%s_%04d-%02d-%02d-%02d-%02d.txt",
            logger_name, cur_time.Year(), cur_time.Month(), cur_time.Day(), cur_time.Hour(), cur_time.Minute())
        log_file, err := os.OpenFile(log_file_name, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeExclusive)
        if err != nil {
            fmt.Printf("try create logfile[%s] error[%s]
    ", log_file_name, err.Error())
            return
        }
    
        defer log_file.Close()
    
        log2.SetOutput(log_file)
    
        for i := 0; i < 10; i++ {
            log2.Debugf("logrus to file test %d", i)
        }
    
    }

    5、简单的示例

    package main
     
    import (
      "os"
      log "github.com/sirupsen/logrus"
    )
     
    func init() {
      // 设置日志格式为json格式
      log.SetFormatter(&log.JSONFormatter{})
     
      // 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
      // 日志消息输出可以是任意的io.writer类型
      log.SetOutput(os.Stdout)
     
      // 设置日志级别为warn以上
      log.SetLevel(log.WarnLevel)
    }
     
    func main() {
      log.WithFields(log.Fields{
        "animal": "walrus",
        "size":  10,
      }).Info("A group of walrus emerges from the ocean")
     
      log.WithFields(log.Fields{
        "omg":  true,
        "number": 122,
      }).Warn("The group's number increased tremendously!")
     
      log.WithFields(log.Fields{
        "omg":  true,
        "number": 100,
      }).Fatal("The ice breaks!")
    }

    6、Logger

    logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logrus实例,即logger对象来记录项目所有的日志。如

    package main
     
    import (
      "github.com/sirupsen/logrus"
      "os"
    )
     
    // logrus提供了New()函数来创建一个logrus的实例。
    // 项目中,可以创建任意数量的logrus实例。
    var log = logrus.New()
     
    func main() {
      // 为当前logrus实例设置消息的输出,同样地,
      // 可以设置logrus实例的输出到任意io.writer
      log.Out = os.Stdout
     
      // 为当前logrus实例设置消息输出格式为json格式。
      // 同样地,也可以单独为某个logrus实例设置日志级别和hook,这里不详细叙述。
      log.Formatter = &logrus.JSONFormatter{}
     
      log.WithFields(logrus.Fields{
        "animal": "walrus",
        "size":  10,
      }).Info("A group of walrus emerges from the ocean")
    }

    7、Fields

    logrus不推荐使用冗长的消息来记录运行信息,它推荐使用Fields来进行精细化的、结构化的信息记录。

    例如下面的记录日志的方式:

    log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key)
    
    //替代方案
    
    log.WithFields(log.Fields{
     "event": event,
     "topic": topic,
     "key": key,
    }).Fatal("Failed to send event")

    8、gin框架日志中间件使用

    package middleware
    
    import (
        "fmt"
        "ginDemo/config"
        "github.com/gin-gonic/gin"
        rotatelogs "github.com/lestrrat-go/file-rotatelogs"
        "github.com/rifflock/lfshook"
        "github.com/sirupsen/logrus"
        "os"
        "path"
        "time"
    )
    
    // 日志记录到文件
    func LoggerToFile() gin.HandlerFunc {
    
        logFilePath := config.Log_FILE_PATH
        logFileName := config.LOG_FILE_NAME
    
        // 日志文件
        fileName := path.Join(logFilePath, logFileName)
    
        // 写入文件
        src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
        if err != nil {
            fmt.Println("err", err)
        }
    
        // 实例化
        logger := logrus.New()
    
        // 设置输出
        logger.Out = src
    
        // 设置日志级别
        logger.SetLevel(logrus.DebugLevel)
    
        // 设置 rotatelogs
        logWriter, err := rotatelogs.New(
            // 分割后的文件名称
            fileName + ".%Y%m%d.log",
    
            // 生成软链,指向最新日志文件
            rotatelogs.WithLinkName(fileName),
    
            // 设置最大保存时间(7天)
            rotatelogs.WithMaxAge(7*24*time.Hour),
    
            // 设置日志切割时间间隔(1天)
            rotatelogs.WithRotationTime(24*time.Hour),
        )
    
        writeMap := lfshook.WriterMap{
            logrus.InfoLevel:  logWriter,
            logrus.FatalLevel: logWriter,
            logrus.DebugLevel: logWriter,
            logrus.WarnLevel:  logWriter,
            logrus.ErrorLevel: logWriter,
            logrus.PanicLevel: logWriter,
        }
    
        lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{
            TimestampFormat:"2006-01-02 15:04:05",
        })
    
        // 新增 Hook
        logger.AddHook(lfHook)
    
        return func(c *gin.Context) {
            // 开始时间
            startTime := time.Now()
    
            // 处理请求
            c.Next()
    
            // 结束时间
            endTime := time.Now()
    
            // 执行时间
            latencyTime := endTime.Sub(startTime)
    
            // 请求方式
            reqMethod := c.Request.Method
    
            // 请求路由
            reqUri := c.Request.RequestURI
    
            // 状态码
            statusCode := c.Writer.Status()
    
            // 请求IP
            clientIP := c.ClientIP()
    
            // 日志格式
            logger.WithFields(logrus.Fields{
                "status_code"  : statusCode,
                "latency_time" : latencyTime,
                "client_ip"    : clientIP,
                "req_method"   : reqMethod,
                "req_uri"      : reqUri,
            }).Info()
        }
    }
    
    // 日志记录到 MongoDB
    func LoggerToMongo() gin.HandlerFunc {
        return func(c *gin.Context) {
    
        }
    }
    
    // 日志记录到 ES
    func LoggerToES() gin.HandlerFunc {
        return func(c *gin.Context) {
    
        }
    }
    
    // 日志记录到 MQ
    func LoggerToMQ() gin.HandlerFunc {
        return func(c *gin.Context) {
    
        }
    }
    

    9、简单的日志切割

    需要引入外部组件

    package main
    
    import (
    	"time"
    
    	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    	log "github.com/sirupsen/logrus"
    )
    
    func init() {
    	path := "message.log"
    	/* 日志轮转相关函数
    	`WithLinkName` 为最新的日志建立软连接
    	`WithRotationTime` 设置日志分割的时间,隔多久分割一次
    	WithMaxAge 和 WithRotationCount二者只能设置一个
    	 `WithMaxAge` 设置文件清理前的最长保存时间
    	 `WithRotationCount` 设置文件清理前最多保存的个数
    	*/
    	// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。
    	writer, _ := rotatelogs.New(
    		path+".%Y%m%d%H%M",
    		rotatelogs.WithLinkName(path),
    		rotatelogs.WithMaxAge(time.Duration(180)*time.Second),
    		rotatelogs.WithRotationTime(time.Duration(60)*time.Second),
    	)
    	log.SetOutput(writer)
    	//log.SetFormatter(&log.JSONFormatter{})
    }
    
    func main() {
    	for {
    		log.Info("hello, world!")
    		time.Sleep(time.Duration(2) * time.Second)
    	}
    }

     10、ZAP的使用方法(性能最高)

    package main
    
    import (
        "flag"
        "fmt"
        "os"
        "path"
        "runtime"
        "strings"
        "time"
    
        "github.com/golang/glog"
    )
    
    func zap_log_test() {
        fmt.Printf("<<<<<<<<<zap log test>>>>>>>>>>>
    ")
        logger := zap.NewExample()
        defer logger.Sync()
    
        const url = "http://example.com"
    
        // In most circumstances, use the SugaredLogger. It's 4-10x faster than most
        // other structured logging packages and has a familiar, loosely-typed API.
        sugar := logger.Sugar()
        sugar.Infow("Failed to fetch URL.",
            // Structured context as loosely typed key-value pairs.
            "url", url,
            "attempt", 3,
            "backoff", time.Second,
        )
        sugar.Infof("Failed to fetch URL: %s", url)
    
        // In the unusual situations where every microsecond matters, use the
        // Logger. It's even faster than the SugaredLogger, but only supports
        // structured logging.
        logger.Info("Failed to fetch URL.",
            // Structured context as strongly typed fields.
            zap.String("url", url),
            zap.Int("attempt", 3),
            zap.Duration("backoff", time.Second),
        )
    }
    

      

      

  • 相关阅读:
    Jmeter——关联与正则
    Jmeter图形插件扩展
    Jmeter——检查点
    Jmeter——集合点
    OpenGL帧缓存对象(FBO:Frame Buffer Object)(转载)
    unicode 和 utf8
    管理node的版本
    pyqt5 开发环境
    cmake
    一些可能常用的工具函数
  • 原文地址:https://www.cnblogs.com/chenpingzhao/p/15389882.html
Copyright © 2020-2023  润新知