761 字
4 分钟
logrus库同时将日志输出到文件和stderr,并设置不同的formatter

背景#

在Web项目开发中,日志记录是维护和调试程序的重要手段。logrus 是一个流行的Go语言日志库,提供了丰富的日志级别和格式定制能力。logrus它支持 JSONFormatterTextFormatter 两种格式化器,允许开发人员根据不同的场景,选择最合适的日志输出格式。

JSONFormatter#

JSONFormatter 将日志信息输出为JSON格式,这被称为”结构化日志”。结构化日志易于程序解析,非常适合于传输到日志分析系统,如ELK Stack(Elasticsearch、Logstash、Kibana),以便进行集中管理和分析。

JSONFormatter的输出示例:

{"level":"info","msg":"Server started.","time":"2024-04-07T16:14:36+08:00"}

TextFormatter#

与JSON相对,TextFormatter 输出的日志更适合人类阅读。同时还能支持彩色输出。

INFO[2024-04-07T16:28:39+08:00] Server started.

在调试或本地开发过程中,直观的文本输出可以更快地帮助开发者定位问题。

在实际应用中,我们可能希望日志同时满足人和机器的阅读需求:将日志以JSON格式输出到日志文件,方便系统的日志分析工具处理;同时,为了开发者的方便,也需要将日志以文本格式输出到标准错误流(stderr)。

那么,如何同时实现两种格式的日志输出?

解决方案#

这里不得不吐槽,golang的生态并不成熟,关于这个问题的实现方法网上很少有相关资料

Hook#

logrus 提供Hook功能 ,它允许在日志事件发生时,执行特定操作。我们可以使用 Hook 来实现我们的需求。

// A hook to be fired when logging on the logging levels returned from
// `Levels()` on your implementation of the interface. Note that this is not
// fired in a goroutine or a channel with workers, you should handle such
// functionality yourself if your call is non-blocking and you don't wish for
// the logging calls for levels returned from `Levels()` to block.
type Hook interface {
Levels() []Level
Fire(*Entry) error
}

Hook 接口定义了两个方法

  • Levels() []Level:返回一个日志级别数组,当这些级别的日志事件发生时,将调用 Fire 方法。
  • Fire(*Entry) error:当符合 Levels() 返回的日志级别的日志事件发生时,将调用此方法。

实现#

于是可以写出以下代码

package logger
import (
"io"
"log"
"os"
"github.com/sirupsen/logrus"
)
var Logger *logrus.Logger
type Hook struct {
Writer io.Writer
Formatter logrus.Formatter
Level []logrus.Level
}
func (h *Hook) Fire(entry *logrus.Entry) error {
line, err := h.Formatter.Format(entry)
if err != nil {
return err
}
h.Writer.Write(line)
return nil
}
func (h *Hook) Levels() []logrus.Level {
return h.Level
}
func newHook(writer io.Writer, formatter logrus.Formatter, level logrus.Level) *Hook {
var levels []logrus.Level
for _, l := range logrus.AllLevels {
if l <= level {
levels = append(levels, l)
}
}
return &Hook{
Writer: writer,
Formatter: formatter,
Level: levels,
}
}
func Init(logFilePath string, logLevelStr string) {
logLevel, err := logrus.ParseLevel(logLevelStr)
if err != nil {
logLevel = logrus.InfoLevel
log.Printf("Invalid log level: %s. Defaulting to info", logLevelStr)
}
Logger = logrus.New()
Logger.SetOutput(io.Discard)
logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Printf("Failed to open log file: %s", logFilePath)
panic(err)
}
Logger.AddHook(newHook(
logFile,
&logrus.JSONFormatter{},
logLevel,
))
Logger.AddHook(newHook(
os.Stderr,
&logrus.TextFormatter{
FullTimestamp: true,
ForceColors: true,
},
logLevel,
))
}

效果#

在指定的日志文件中,会输出JSON格式的日志,同时在标准错误流(stderr)中,会输出文本格式的日志。

logrus multiple formatter

logrus库同时将日志输出到文件和stderr,并设置不同的formatter
https://cyrus28214.github.io/posts/logrus-multiple-formatter/
作者
Cyrus
发布于
2024-04-07
许可协议
CC BY-NC-SA 4.0