这是我参与「第五届青训营 」笔记创作活动的第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")
}