日志的重要性,自不必多说。系统log库,仅仅提供三组接口(print/fatal/panic),功能过于简单,今天来学习目前Github上star数量最多的日志包。很多优秀的开源项目,例如:docker、prometheus等都使用了logrus。
logrus简介
logrus是一个流行的Go语言日志库,用于在Go应用程序中记录日志信息,提供了丰富的特征和灵活的配置选项,使开发人员能够轻松的生成格式化的日志,并将其输出到不同的目标,如标准输出、文件、远程服务器等以下为关键特征:
- 结构化日志记录:意味着可以使用字段来描述日志事件的各个方面,每条日志都可以包含一系列字段,提供更多的上下文信息。
- 多种日志级别:最基本的框架功能,级别包括:Debug、Info、Warning、Error、Fatal,选择适当的级别记录不同程度的日志。
- 定制的输出格式:轻松地自定义日志输出的格式,包括时间戳、级别、消息和其他字段。
- 多种输出目标:允许将日志输出到多个不同的目标,如标准输出、文件、远程服务等。
- 钩子(Hooks):允许在记录日志之前或之后执行自定义操作,可以用于发送告警、记录额外信息等。
- 字段、上下文和附加数据:允许在每条日志中添加附加的字段和上下文信息,以帮助更好地理解日志事件,对于debug非常有用。
话不多说,开干
快速使用
go get -u github.com/sirupsen/logrus
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
log.SetLevel(log.DebugLevel)
}
func main() {
log.WithFields(log.Fields{
"module": "login",
}).Info("User Login")
log.WithFields(log.Fields{
"module": "login",
"fields": "user.name",
}).Warn("parameter does not match the rules")
log.WithFields(log.Fields{
"module": "login",
}).Fatal("user is nil")
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
}
运行后输出:
{"level":"info","module":"login","msg":"User Login","time":"2023-08-21T15:16:21+08:00"}
{"fields":"user.name","level":"warning","module":"login","msg":"parameter does not match the rules","time":"2023-08-21T15:16:21+08:00"}
{"level":"fatal","module":"login","msg":"user is nil","time":"2023-08-21T15:16:21+08:00"}
源码分析:
- 输出中有三个关键信息:time、msg和level,也可以定制化字段输出信息。
- init里面的SetFormatrer,可以根据自身业务,设置为json格式(如上输出),还可以设置为&log.TextFormatter{},则如下:
time="2023-08-21T15:19:39+08:00" level=info msg="User Login" module=login
- init里面的SetOutput,接收所有的io.writer,如stderr或stdout,根据自身配置输出到哪。
- init里面的SetLevel,设置日志级别,从0-7分别为:PanicLevel、FatalLevel、ErrorLevel、WarnLevel、InfoLevel、DebugLevel、TraceLevel,值得注意的是,如果设置为InfoLevel级别,则后面的DebugLevel、TraceLevel级别的日志不输出。
- 由于logrus.Fatal会导致程序退出(查看源码,调用了os.Exit),则下面的所有日志都不会执行到,多说一句,除main函数外,其余任何地方都禁止调用该方法。
- 由上可以看出,日志只是有msg并无具体行号,可以通过 log.SetReportCaller(true) 来显示出具体位置,如下:
time="2023-08-21T15:45:12+08:00" level=fatal msg="user is nil" func=main.main file="E:/workspaces/goland/study_demo/go_logrus/main.go:36" module=login
Hook应用
设置钩子,每条日志输出前都会执行特定方法,比如可以将日志输出到其他云平台等等。
type myHook struct {
}
// Levels 表示该hook,需要挂载在哪些level上面
func (h *myHook) Levels() []log.Level {
return log.AllLevels
}
// Fire 表示在打印之前先触发当前方法
func (h *myHook) Fire(entry *log.Entry) error {
fmt.Println()
fmt.Printf("Fire:%+v \n", entry.Message)
return nil
}
func init() {
log.SetFormatter(&log.TextFormatter{})
log.SetLevel(log.DebugLevel)
log.AddHook(&myHook{})
}
func main() {
log.WithFields(log.Fields{
"module": "login",
}).Info("User Login")
}
输出为:
Fire:User Login
time="2023-08-21T16:06:40+08:00" level=info msg="User Login" module=login
当然,应用场景多种多样,如可以将我们的日志发送到redis、MongoDB等等,这样可以借助第三方的Hook,如:
mgorus:将日志发送到mongodb。
logrus-redis:将日志发送到redis。
logrus-amqp:将日志发送到ActiveMQ。
设置输出至文件
这里可以引用另外一个专门做日志切割的SDK go get -u github.com/lestrrat-go/file-rotatelogs
const LogPath = "runtime/logs/cos.log"
func init() {
log.SetFormatter(&log.TextFormatter{})
log.SetLevel(log.DebugLevel)
writer, _ := getWriter()
log.SetOutput(writer)
}
func getWriter() (io.Writer, error) {
filePath := LogPath
return rotatelogs.New(
filePath+".%Y%m%d%H%M", //指定文件名称
rotatelogs.WithMaxAge(time.Hour*24*30), //日志保存最长时间,如一个月
rotatelogs.WithRotationTime(time.Hour*24)) // 日志多久分割一次,如每天
}
输出至云服务器
推荐使用:"tencent cloud cls log sdk" 是专门为cls量身打造日志上传SDK。 一切只为满足您的需求~ 为什么要使用CLS Log SDK
- 异步发送:发送日志立即返回,无须等待,支持传入callback function。
- 优雅关闭:通过调用close方法,producer会将所有其缓存的数据进行发送,防止日志丢失。
- 感知每一条日志的成功状态: 用户可以自定义CallBack方法的实现,监控每一条日志的状态
- 使用简单: 通过简单配置,就可以实现复杂的日志上传聚合、失败重试等逻辑
- 失败重试: 429、500 等服务端错误,都会进行重试
- 高性能: 得益于go语言的高并发能力
tencent CLS Log 我们可以结合hook,与SDK进行通讯,把日志传送至云。 代码,详参sdk。