完成大项目时发现将各种信息输出到控制台太麻烦,并且容易与gorm的数据库查询输出相混淆,难以Debug,因此项项目引入了日志库Logrus,这里简单介绍Logrus库的基本使用方法。
1 安装
go安装logrus非常简单,只需要运行如下命令
go get github.com/sirupsen/logrus
2 基本配置
2.1 日志格式配置
Logrus支持多种日志格式,包括json、纯文本等,可分别使用如下语法进行设置:
- TextFormatter: Logrus的默认日志格式。将日志输出为易读的文本格式,包括时间戳、日志级别和消息内容。
log.SetFormatter(&log.TextFormatter{})
- JSONFormatter: Logrus支持JSON格式。它将日志输出为结构化的JSON格式,可以更容易地处理和分析。
log.SetFormatter(&log.JSONFormatter{})
- LogstashFormatter: 适用于将日志发送到Logstash进行集中式处理。它输出一个包含日志字段的JSON结构。
log.SetFormatter(&log.LogstashFormatter{})
- FluentdFormatter: 类似于LogstashFormatter,适用于将日志发送到Fluentd进行集中式处理。
log.SetFormatter(&log.FluentdFormatter{})
- TextFormatterWithColor: 这是TextFormatter的一种扩展,支持在终端中使用不同颜色来区分不同级别的日志。
log.SetFormatter(&log.TextFormatter{ForceColors: true})
- CustomFormatter: 你还可以根据需要创建自定义的日志格式。为此,你可以实现
logrus.Formatter接口并根据自己的需求格式化日志。
在使用这些格式时,只需将适当的格式器实例传递给log.SetFormatter()方法。通常而已,json是最长使用的日志格式。
2.2 配置日志级别
2.2.1 不同日志级别的特点
Logrus 提供了七个不同的日志级别,用于将不同重要性的日志消息进行分类和输出:
-
Trace: 最低级别的日志,用于输出最详细的调试信息。通常情况下,它用于跟踪程序的内部流程和变量值。然而,在大多数情况下,Trace 级别的日志可能会输出非常大量的信息,因此在生产环境中可能需要谨慎使用,以避免产生过多的日志。
-
Debug: Debug 级别,用于输出详细的调试信息,但相较于 Trace 级别来说,输出的信息量会更加可控。它对于在调试和开发过程中了解程序行为非常有用。
-
Info: Info 级别用于输出程序的运行信息,如关键事件、流程状态变化等。这些消息对于了解程序的整体运行状态以及处理流程很有帮助。
-
Warn: 当出现一些警告性质的情况,但并不会导致程序出现错误或失败时,可以使用 Warn 级别。这些消息表明程序可能遇到了一些潜在的问题,但仍然可以继续运行。
-
Error: Error 级别用于输出真正的错误消息,表示程序出现了一些不正常的情况,但仍然可以继续执行。这些错误可能需要被及时注意和处理,以确保程序的正确运行。
-
Fatal: 当程序遇到无法继续执行的严重错误时,可以使用 Fatal 级别。这会导致程序立即退出,并输出相关的错误消息。在调试和开发过程中,它可以帮助你迅速定位问题。
-
Panic: Panic 级别用于在遇到无法恢复的错误时引发 panic。与 Fatal 不同的是,Panic 级别的日志会触发 panic,导致程序崩溃。在生产环境中,应该避免在日志中使用 Panic 级别,因为它会中断程序的正常运行。
通常而言,Trace 和 Debug 级别的日志非常有用。可以提供最详细的日志信息以供开发者深入了解程序内部的流程、变量值以及调用堆栈。这对于调试代码、解决问题以及验证逻辑是否按预期运行非常有帮助。运营阶段中,更多地关注于 Info、Warn 和 Error 级别的日志,以及及时响应潜在的问题和异常情况。
2.2.2 不同日志的输出语法
可以在程序中指定具体的日志级别进行输出:
// 使用日志级别输出日志
log.Debug("This is a debug message")
log.Info("This is an info message")
log.Warn("This is a warning message")
log.Error("This is an error message")
log.Fatal("This is a fatal message")
log.Panic("This is a panic message")
2.2.3 全局日志级别
使用SetLevel可以设置全局日志级别,以下为设置全局Debug级别日志的语法:
log.SetLevel(logrus.DebugLevel)
log.SetLevel() 方法设置了整个日志实例的日志级别。这意味着,如果设置了一个特定的日志级别(如 Debug、Info、Warn 等),那么在以后的日志输出中,只有达到设置的级别或更高级别的日志消息才会被输出。例如,如果将日志级别设置为 Debug,那么所有的 Debug、Info、Warn、Error、Fatal 和 Panic 级别的日志消息都会被输出。
2.3 输出重定向
1.标准输出 Logrus库的默认输出为标准输出,所有的日志消息将在终端上显示,也可以手动如下设置:
log.SetOutput(os.Stdout)
- 重定向到文件 将文件描述符传递给SetOutput即可设置日志输出到该文件。
log.SetOutput(file)
需要注意文件描述符需要设置文件权限和写入方式,例如:
file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
尤其要注意使用os.O_APPEND追加写入的方式记录日志。
3 基本日志使用方法
3.1 实例创建
Logrus库默认提供了一个全局的默认实例。可以直接通过 logrus.Debug()、logrus.Info() 等方法来输出日志,而无需显式地创建新的实例。当然,也可以使用 logrus.New() 来手动创建一个新的 Logrus 实例:
log := logrus.New()
3.2 WithFields字段记录
使用 WithFields 可以将自定义的字段添加到日志消息中,从而在日志输出中包含额外的上下文信息。使用 WithFields 添加字段后,调用输出日志的级别即可完成输出,案例如下:
log.WithFields(logrus.Fields{
"user_id": user.ID,
"user_token": user.Token,
}).Info("Processing request")
实际上,WithFields的参数类型为 map[string]interface{},因此,实际上可以结合反射完成对整个结构体的日志输出,以下为一个将结构体转化为map的代码:
func StructToMap(data interface{}) map[string]interface{} {
result := make(map[string]interface{})
value := reflect.ValueOf(data)
// 确保传入的是结构体指针
if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
return result
}
value = value.Elem()
typ := value.Type()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fieldValue := value.Field(i).Interface()
result[field.Name] = fieldValue
}
return result
}
利用该函数,传入结构体的指针,得到的返回值即可直接输出到日志中。
3.3 WithField链式调用
WithField 方法允许通过链式调用将多个字段添加到日志消息中,而不是一次性添加所有字段。这样可以使日志消息的构建更具有结构性,提高代码的可读性。可以使用多次 WithField 调用来逐步构建日志消息。
以下是一个使用 WithField 链式调用的示例:
log.WithField("request_id", requestId).
WithField("user_id", userId).
Info("Processing request")
上述代码将输出:
Output: {"level":"info","msg":"Processing request","request_id":"abc123","user_id":42}
上述代码多次使用 WithField 进行链式调用,逐步添加了 "request_id" 和 "user_id" 两个字段。这样可以使日志消息的结构更加清晰,从而更好地表达了日志的含义。
同时,可以结合使用 WithFields 和 WithField,以便同时添加多个字段和单个字段:
log.WithFields(logrus.Fields{
"request_id": requestId,
"user_agent": userAgent,
}).WithField("user_id", userId).Info("Processing request")
4 Hook使用方法
在 Logrus 中,Hook 是一种可以在日志输出时执行自定义逻辑的机制。通过使用 Hook,可以在日志消息输出到指定目标之前或之后执行额外的操作,如发送日志到远程服务器、记录日志到数据库等。Logrus支持多种类型的 Hooks,可以根据需要选择适合的类型。
4.1 Hook接口
创建自定义的Hook时需要实现该接口,该接口的方法如下:
// 在执行自定义操作,比如发送日志到远程服务器、记录日志到数据库等
func Fire(entry *logrus.Entry) error
// 返回需要被调用的日志级别
func Levels() []logrus.Level
4.2 自定义Hook案例
假设我有这样一个需求,每隔一段时间(例如每天0点),清空日志(输出为文件的情况),以下为一个简易的实现:
type DailyResetHook struct {
logFile *os.File
resetHour int
}
func NewDailyResetHook(logFile *os.File, resetHour int) *DailyResetHook {
return &DailyResetHook{
logFile: logFile,
resetHour: resetHour,
}
}
func (hook *DailyResetHook) Fire(entry *logrus.Entry) error {
now := time.Now()
if now.Hour() == hook.resetHour {
// 关闭之前的日志文件
hook.logFile.Close()
// 打开新的日志文件
newLogFile, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
hook.logFile = newLogFile
// 更新 Logrus 的输出
entry.Logger.Out = newLogFile
}
return nil
}
func (hook *DailyResetHook) Levels() []logrus.Level {
return logrus.AllLevels
}
以上代码创建了一个名为 DailyResetHook 的 Hook,它会在每天指定的时间点检查是否需要重新记录日志。如果当前时间的小时数等于设定的 resetHour,它会关闭当前的日志文件,创建一个新的日志文件,并将 Logrus 的输出设置为新的日志文件。
4.3 预定义Hook
Logrus 还提供了一些预定义的 Hook 类型,可以直接使用。例如,logrus.SyslogHook 可以将日志输出到系统日志。以下为一些常用的预定义Hook 在 Logrus 中,有一些预定义的 Hook 可供使用,它们可以帮助你将日志输出到不同的目标或系统。以下是一些常见的预定义 Hook 类型:
- Syslog Hook(系统日志):可以将日志消息输出到系统日志。这在 Unix-like 系统中特别有用,可以与系统日志工具集成,如 syslog。
log := logrus.New()
log.SetLevel(logrus.InfoLevel)
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil {
log.AddHook(hook)
}
- Logstash Hook:可以将日志消息输出到 Logstash,从而集成到 ELK(Elasticsearch、Logstash 和 Kibana)堆栈中。
log := logrus.New()
log.SetLevel(logrus.InfoLevel)
hook, err := logrustash.NewAsyncHook("tcp", "localhost:5044", "myApp")
if err == nil {
log.AddHook(hook)
}
- Airbrake Hook:可以将日志消息发送到 Airbrake 服务,用于收集和监控应用程序的错误和异常信息。
log := logrus.New()
log.SetLevel(logrus.InfoLevel)
apiKey := "your_airbrake_api_key"
projectID := 12345
environment := "production"
hook, err := logrusbrake.NewHook(apiKey, projectID, environment)
if err == nil {
log.AddHook(hook)
}