一、简介
Logrus是Go(golang)的结构化logger,与标准库logger完全API兼容,它有以下特点
-
结构化日志记录:Logrus 支持生成结构化的日志条目,这意味着日志不仅仅是文本字符串,而是包含键值对的数据结构,便于日志分析和处理。
-
高性能:设计上注重效率,适合高并发和高性能的应用场景,能够快速处理大量的日志输出。
-
灵活的输出格式:支持多种输出格式,包括文本(默认的 TextFormatter)和 JSON(JSONFormatter),用户也可以自定义格式化器(Formatter)来适配特定需求。
-
可插拔的钩子(Hooks)机制:允许用户自定义日志处理逻辑,比如将日志发送到远程服务器、数据库、消息队列等。钩子可以在日志事件的不同阶段触发,比如日志写入前、写入后。
-
丰富的日志级别:内置了多种日志级别,包括 Trace、Debug、Info、Warning、Error、Fatal 和 Panic,方便根据不同的场景选择合适的日志详细度。
-
兼容性:API 设计上与 Go 标准库的
log
模块兼容,使得从标准库迁移至 Logrus 相对简单,降低了学习和使用成本。 -
国际化和本地化:虽然原生 Logrus 不直接支持国际化,但用户可以通过自定义钩子等方式实现日志的多语言输出。
-
错误处理:通过 Panic 和 Fatal 级别的日志,Logrus 可以在严重错误发生时自动调用 panic 函数或 os.Exit,便于程序的错误管理。
-
轻量级:Logrus 本身代码量不大,且无外部依赖,易于理解和集成。
-
社区支持:作为 GitHub 上 Star 数量最多的 Go 日志库之一,Logrus 拥有活跃的社区支持和丰富的文档资源,许多知名项目如 Docker、Prometheus 等都在使用它。
二、日志级别
1、介绍
package main
import (
"github.com/sirupsen/logrus"
)
// 创建一个新的logger实例。可以创建任意多个。
var log = logrus.New()
func main() {
log.Trace("一些非常低级的东西。")
log.Debug("有用的调试信息。")
log.Info("发生了值得注意的事情!")
log.Warn("这是警告信息,需要关注。")
log.Error("有些事情失败了,但我不会放弃。")
// 假设发生了某个严重错误,记完日志后会调用os.Exit(1)
//log.Fatal("退出。。。") // 记录日志后程序会退出
// 这下面的代码不会被执行
// 记完日志后会调用 panic()
//log.Panic("记完日志后会调用 panic()。")
}
2、设置日志级别
package main
import (
"github.com/sirupsen/logrus"
)
// 创建一个新的logger实例。可以创建任意多个。
var log = logrus.New()
func main() {
// 创建一个新的logrus logger
logger := logrus.New()
// 设置日志级别,这里以Info为例
logger.SetLevel(logrus.InfoLevel)
// 记录不同级别的日志
logger.Debug("这条日志不会被输出,因为Debug级别低于Info级别")
logger.Info("这条是Info级别的日志")
logger.Warn("这条是Warn级别的日志")
logger.Error("这条是Error级别的日志")
// Fatal和Panic级别的日志会立即导致程序退出
// logger.Fatal("这条是Fatal级别的日志,打印完后,程序会在这里退出")
// logger.Panic("这条是Panic级别的日志,打印完后,程序会在这里退出")
}
3、字段
-
Logrus鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。
-
例如,区别于使用
log.Fatalf("Failed to send event %s to topic %s with key %d")
-
你应该使用如下方式记录更容易发现的内容
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
)
func main() {
fmt.Println("3333")
log.WithFields(log.Fields{
"event": "event",
"topic": "topic",
"key": "key",
}).Info("Failed to send event")
}
4、默认字段
-
通常,将一些字段始终附加到应用程序的全部或部分的日志语句中会很有帮助。
-
例如,你可能希望始终在请求的上下文中记录
request_id
和user_ip
。 -
区别于在每一行日志中写上
log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
-
你可以向下面的示例代码一样创建一个
logrus.Entry
去传递这些字段。
package main
import log "github.com/sirupsen/logrus"
func main() {
requestLogger := log.WithFields(log.Fields{"request_id": "请求id", "user_ip": "用户id"})
requestLogger.Info("应要求发生了一些事情") // 将记录request_id和user_ip
requestLogger.Warn("发生了一些不太好的事情") // 将记录request_id和user_ip
}
/*
time="2024-05-23T17:30:13+08:00" level=info msg="应要求发生了一些事情" request_id2="请求id" user_ip="用户id"
time="2024-05-23T17:30:13+08:00" level=warning msg="发生了一些不太好的事情" request_id2="请求id" user_ip="用户id"
*/
5、格式化
package main
import (
"github.com/sirupsen/logrus"
)
var log = logrus.New()
func main() {
log.Formatter = &logrus.JSONFormatter{}
log.SetReportCaller(true) // 可以开启记录函数名,但是会消耗性能
log.WithFields(logrus.Fields{
"event": "xxx事件",
"topic": "xxx内容",
"key": "xxx_key",
}).Info("发送事件失败")
}
/*
{
"event": "xxx事件",
"key": "xxx_key",
"level": "info",
"msg": "发送事件失败",
"time": "2024-05-23T17:42:31+08:00",
"topic": "xxx内容"
}
// 开启记录函数名
{
"event": "xxx事件",
"file": "D:/GoCode/src/gin-mall/main.go:16",
"func": "main.main",
"key": "xxx_key",
"level": "info",
"msg": "发送事件失败",
"time": "2024-05-23T17:43:20+08:00",
"topic": "xxx内容"
}
*/
6、Hooks
在 logrus
中,钩子(Hooks)是一种在记录特定级别日志时执行自定义逻辑的方法。钩子可以用来发送日志到第三方服务,如 Sentry、BugSnag、Logstash 等,或者执行其他任何你想要在日志事件发生时进行的操作。
1. 实现钩子接口
钩子需要实现 logrus.Hook
接口,该接口有两个方法:Levels()
和 Fire()
。
-
Levels()
返回一个包含钩子关心的日志级别的切片。 -
Fire()
接收一个logrus.Entry
,包含了日志的详细信息,当日志触发时执行。
package main
import (
"fmt"
"github.com/sirupsen/logrus"
)
// MyHook 是一个自定义钩子,实现了logrus.Hook接口
type MyHook struct{}
// Levels 方法返回这个钩子关心的日志级别
func (hook *MyHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}
// Fire 方法在记录日志时触发,entry 包含了日志的详细信息
func (hook *MyHook) Fire(entry *logrus.Entry) error {
// 这里可以执行自定义逻辑,例如发送日志到监控系统
// 以下是简单的打印到标准输出的示例
fmt.Printf("Error: %s\n", entry.Message)
// 始终返回nil,表示没有错误
return nil
}
2. 添加钩子到 logger
在 Logrus 中,钩子(Hooks)是一种强大的特性,允许你在日志事件处理流程中的特定点插入自定义的行为,而无需修改日志记录的核心逻辑。这对于实现如日志的额外存储(例如保存到文件、数据库、或者发送到远程服务器)非常有用。
自定义钩子示例
将错误日志同时输出到文件
首先,你需要定义一个实现了 logrus.Hook
接口的结构体。这个接口要求实现三个方法:Levels()
, Fire(*logrus.Entry) error
, 和可选的 SetFormatter(formatter logrus.Formatter)
。
package main
import (
log "github.com/sirupsen/logrus"
"os"
)
// FileHook 将错误日志写入文件
type FileHook struct {
file *os.File
}
// NewFileHook 创建一个新的 FileHook 实例
func NewFileHook(filename string) (*FileHook, error) {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
return &FileHook{file: file}, nil
}
// Levels 钩子关心的日志级别
func (hook *FileHook) Levels() []log.Level {
return []log.Level{log.PanicLevel, log.FatalLevel, log.ErrorLevel}
}
// Fire 当日志事件触发时被调用
func (hook *FileHook) Fire(entry *log.Entry) error {
_, err := hook.file.WriteString(entry.Message + "\n")
if err != nil {
return err
}
return nil
}
func main() {
// 初始化日志
logger := log.New()
logger.SetLevel(log.DebugLevel)
// 创建并添加 FileHook
hook, err := NewFileHook("errors.log")
if err == nil {
logger.AddHook(hook)
} else {
logger.Fatal("无法创建文件挂钩", err)
}
// 记录一些日志
logger.Info("这是一条Info级别消息")
logger.Warn("这是一条Warn级别消息")
logger.Error("这是一条Error级别消息")
//logger.Fatal("这是一条Fatal级别消息")//打印完日志后退出
//logger.Panic("这是一条Panic级别消息")//打印完日志后退出
}
三、gin中使用logrus
package main
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"os"
)
var log = logrus.New()
var logFile *os.File
func init() {
// 以JSON格式而不是默认的ASCII格式器进行日志记录。
log.Formatter = &logrus.JSONFormatter{}
//输出到stdout而不是默认的stderr
//可以是任何io.Writer,请参阅下面的文件示例
//logFile, _ := os.Create("./gin.log")//使用os.Create函数创建文件时,默认情况下新创建的文件是空的,任何写入都会追加到文件末尾
// 使用os.OpenFile而不是os.Create
// flag参数设置为os.O_CREATE和os.O_APPEND,这意味着文件将被创建(如果它不存在)并且写入将追加到文件末尾
// perm参数设置文件的权限(例如0644),这是一个常用的选项,表示文件对所有用户可读写,但只对拥有者可执行
logFile, err := os.OpenFile("./gin.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
//defer f.Close()
log.Out = logFile
gin.SetMode(gin.ReleaseMode)
gin.DefaultWriter = log.Out
// 仅记录警告严重性或更高级别输出
log.Level = logrus.InfoLevel
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
defer logFile.Close()
// GET:请求方式;/hello:请求的路径
// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
r.GET("/hello", func(c *gin.Context) {
log.WithFields(logrus.Fields{
"animal": "狼",
"size": 10,
}).Warn("一群狼在丛林中出现")
// c.JSON:返回JSON格式的数据
c.JSON(200, gin.H{
"message": "Hello 狼!",
})
})
r.Run(":8080")
}