Logrus 学习| 青训营笔记

128 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第12天

人生没有白走的路,每一步它都算数——考研政治老师孔昱力

安装

go install  github.com/sirupsen/Logrus@latest

logrus 常用方法

设置日志等级

func main() {
	logrus.Error("Error")
	logrus.Warnln("Warning")
	logrus.Infof("Info")
	logrus.Debugf("Debug")
	logrus.Println("Println")
}
# 输出
time="2023-01-29T12:04:16+08:00" level=error msg=Error
time="2023-01-29T12:04:16+08:00" level=warning msg=Warning
time="2023-01-29T12:04:16+08:00" level=info msg=Info
time="2023-01-29T12:04:16+08:00" level=info msg=Println

可以发现,我们打了五条日志,但输出只有四条,这是因为日志有不同的等级,只会输出大于等于当前设置的等级的日志信息。logrus默认的等级是info,从高到低的等级如下:

PanicLevel  // 会抛一个异常
FatalLevel  // 打印日志之后就会退出
ErrorLevel
WarnLevel
InfoLevel
DebugLevel
TraceLevel  // 低级别

PanicLevel等级的日志输出后,会抛一个异常,程序会停止。FatalLevel等级的日志输出后,程序会退出。日志的级别可根据环境需要来设置,例如,开发环境下想输出更多的日志信息,一般会选择Debug级别。线上环境则不需要太多日志,设为Warnning即可。logrus设置日志方式如下:

logrus.SetLevel(logrus.DebugLevel)

设置默认字段

先看一下怎样设置默认字段,然后再说设置默认字段后的影响是怎样的。

设置默认字段的方式有两种,单个字段设置和批量字段设置。单个字段设置就是设置kv对,然后返回一个log指针。

log := logrus.WithField("host", "localhost")

批量设置就是传递一个map,可以在上一个log指针的基础上继续设置,也可以使用logrus重新设置,返回一个新的对象。

log = log.WithFields(map[string]interface{}{
	"user_id": userID,
	"service": service,
})

log2 := logrus.WithFields(logrus.Fields{
	"user_id": userID,
	"service": service,
})

logrus内置了一个map,叫做logrus.Fields,其实就是map,源码如下:

// Fields type, used to pass to `WithFields`.
type Fields map[string]interface{}

设置了默认字段的输出会在末尾加上我们设置的字段

time="2023-01-29T12:23:32+08:00" level=info msg="测试设置默认字段" host=localhost service=login user_id=1

设置显示格式

上边看到的日志都是key=v的文本形式,也是默认格式。还可以设置为json格式。

logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetFormatter(&logrus.TextFormatter{})

json格式输出效果如下

// 笔者刻意手动格式化了输出
{
    "host":"localhost",
    "level":"info",
    "msg":"json格式输出日志",
    "service":"login",
    "time":"2023-01-29T12:35:20+08:00",
    "user_id":1
}

还有一些其格式

ForceColors:是否强制使用颜色输出。
DisableColors:是否禁用颜色输出。
ForceQuote:是否强制引用所有值。
DisableQuote:是否禁用引用所有值。
DisableTimestamp:是否禁用时间戳记录。
FullTimestamp:是否在连接到 TTY 时输出完整的时间戳。
TimestampFormat:用于输出完整时间戳的时间戳格式。

以设置时间戳格式为例,

logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006/01/02 15:04:05"})
{
    "host":"localhost",
    "level":"info",
    "msg":"时间戳格式设置",
    "service":"login",
    "time":"2023/01/29 12:54:23",
    "user_id":1
}

输出到日志文件

日志默认是输出到控制台,但是出问题后不可能去控制台找啊,当然是去看日志文件。logrus的设置非常人性化,简单粗暴易于理解。打开一个文件后,直接把这个文件指定给logrus即可。

logFile, _ := os.OpenFile("user.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logrus.SetOutput(logFile)

同时输出到控制台和日志文件

func SetOutput(out io.Writer) 需要的是一个io.Writer,多个源可以使用MultiWriter(writers ...Writer) Writer组合到一起,然后赋给logrus。

logFile, _ := os.OpenFile("user.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logDest := io.MultiWriter(os.Stdout, logFile)
logrus.SetOutput(logDest)

输出日志所在文件及行号

没有日志的具体输出位置,就算出现问题了定位位置也是个麻烦事。

logrus.SetReportCaller(true)
{
    "file":"D:/Program/GoLang/learn-golang/logrus/logrus常用方法.go:24",
    "func":"main.main",
    "host":"localhost",
    "level":"info",
    "msg":"时间戳格式设置",
    "time":"2023/01/29 13:13:02",
    "user_id":1
}

Hook

logrus支持类似于gin中间件的机制,那就是Hook,他可以在打印每个日志之前都干一些事情。Hook的定义不像gin那样写一个方法,然后给gin设置方法就可以了。它需要实现一个Hook接口,然后给logrus设置该类。

// logrus在记录Levels()返回的日志级别的消息时会触发HOOK,
// 按照Fire方法定义的内容修改logrus.Entry。
type Hook interface {
  Levels() []Level
  Fire(*Entry) error
}

例如,实现一个Hook,在打印所有日志前都添加一个field

type MyHook struct{}

func (hook MyHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (hook MyHook) Fire(entry *logrus.Entry) error {
	entry.Data["hook"] = "这是Hook打印的信息"
	return nil
}

func main() {
	logrus.SetLevel(logrus.TraceLevel)
	logrus.AddHook(&MyHook{})
	logrus.Infoln("info")
	logrus.Traceln("trace")
}
time="2023-01-29T13:50:45+08:00" level=info msg=info hook="这是Hook打印的信息"
time="2023-01-29T13:50:45+08:00" level=trace msg=trace hook="这是Hook打印的信息"

日志分割

根据时间分割日志

下边给出了一个分钟级的日志实例

package main

import (
	"fmt"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"os"
	"time"
)

type LogSplitedByDateHook struct {
	curLogFile *os.File
	LogPath    string
	baseTime   string // 当前日志文件基于的时间点
}

func (hook LogSplitedByDateHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (hook LogSplitedByDateHook) Fire(entry *logrus.Entry) error {
	baseTime := entry.Time.Format("2006-01-02_15-04")
	logText, _ := entry.String()
	
	// 如果与当前的日志文件的baseTime不同,更换baseTime,创建新的日志文件
	if hook.baseTime != baseTime {
		logFilePath := fmt.Sprintf("%s/%s.log", hook.LogPath, baseTime)
		hook.curLogFile, _ = os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
		defer hook.curLogFile.Close()
		hook.baseTime = baseTime
	}

	// 向当前日志文件写入日志
	hook.curLogFile.Write([]byte(logText))
	defer hook.curLogFile.Close()

	return nil
}

func InitLog(logPath string) error {
	//创建目录
	err := os.MkdirAll(fmt.Sprintf("%s", logPath), os.ModePerm)
	if err != nil {
		return errors.Wrap(err, "Failed to create log directory")
	}
	
	splitterHook := LogSplitedByDateHook{
		LogPath: logPath,
	}
	logrus.AddHook(splitterHook)
	logrus.SetLevel(logrus.DebugLevel)
	return nil
}

func main() {
	InitLog("log")

	for {
		logrus.Infoln("Infof")
		time.Sleep(3 * time.Second)
		logrus.Debugln("debug")
	}
}

根据日志等级分割日志

package main

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"os"
)

const (
	allLog  = "all"
	errLog  = "err"
	warnLog = "warn"
	infoLog = "info"
)

type FileLevelHook struct {
	file     *os.File
	errFile  *os.File
	warnFile *os.File
	infoFile *os.File
	logPath  string
}

func (hook FileLevelHook) Levels() []logrus.Level {
	return logrus.AllLevels
}
func (hook FileLevelHook) Fire(entry *logrus.Entry) error {
	line, _ := entry.String()
	switch entry.Level {
	case logrus.ErrorLevel:
		hook.errFile.Write([]byte(line))
	case logrus.WarnLevel:
		hook.warnFile.Write([]byte(line))
	case logrus.InfoLevel:
		hook.infoFile.Write([]byte(line))
	}
	hook.file.Write([]byte(line))
	return nil
}

func InitLevel(logPath string) {
	err := os.MkdirAll(fmt.Sprintf("%s", logPath), os.ModePerm)
	if err != nil {
		logrus.Error(err)
		return
	}
	allFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, allLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	errFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, errLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	warnFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, warnLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	infoFile, err := os.OpenFile(fmt.Sprintf("%s/%s.log", logPath, infoLog), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
	fileHook := FileLevelHook{allFile, errFile, warnFile, infoFile, logPath}
	logrus.AddHook(&fileHook)
}

func main() {
	InitLevel("logrus_study/log_level")
	logrus.Errorln("你好")
	logrus.Errorln("err")
	logrus.Warnln("warn")
	logrus.Infof("info")
	logrus.Println("print")
}