logrus:基于 Go 语言的结构化日志库项目

Structured, pluggable logging for Go.

分支8Tags63
文件最后提交记录最后更新时间
2 个月前
16 天前
1 个月前
6 年前
19 天前
16 天前
12 年前
17 天前
3 个月前
2 个月前
1 年前
3 个月前
5 个月前
17 天前
19 天前
17 天前
5 个月前
6 年前
6 年前
5 个月前
5 个月前
5 个月前
4 个月前
16 天前
19 天前
12 天前
12 天前
5 个月前
1 年前
16 天前
5 个月前
16 天前
7 年前
17 天前
2 个月前
17 天前
2 个月前
19 天前
5 个月前
4 个月前
3 个月前
4 个月前
4 个月前
4 个月前
5 个月前
16 天前
17 天前
5 个月前
6 个月前

Logrus

:walrus:

Build Status Go Reference

Logrus 是一款适用于 Go(golang)的结构化日志记录器,与标准库日志器完全 API 兼容。

Logrus 目前处于维护模式。 该项目专注于安全性、错误修复和性能改进。除为实现与其他日志生态系统(例如 Go 的 log/slog)的互操作性而必需的更改外,暂无新增功能计划。

我认为 Logrus 最大的贡献在于,它在如今 Go 语言中结构化日志的广泛应用方面发挥了一定作用。似乎没有必要对 Logrus 进行重大的、不兼容的 V2 版本迭代,因为出色的 Go 社区已经独立开发了这些功能。许多优秀的替代方案纷纷涌现。如果 Logrus 是根据我们今天对 Go 中结构化日志的了解来重新设计的,它也会与这些方案相似。例如,可以了解一下 ZerologZapApex

在开发环境中支持美观的彩色输出(当连接 TTY 时,否则为纯文本):

Colored

使用 logrus.SetFormatter(&logrus.JSONFormatter{}),便于 logstash 或 Splunk 进行解析:

{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!","size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.","size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}

当未连接 TTY 时,使用默认的 logrus.SetFormatter(&logrus.TextFormatter{}),输出将与 logfmt 格式兼容:

time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" animal=orca err="It's over 9000!" number=100 omg=true size=9009

为确保即使连接了 TTY 也能实现此行为,请按以下方式设置格式化程序:

logrus.SetFormatter(&logrus.TextFormatter{
    DisableColors: true,
    FullTimestamp: true,
})

记录方法名称

如果希望将调用方法添加为字段,请通过以下方式告知记录器:

logrus.SetReportCaller(true)

这会将调用方添加为“method”,如下所示:

{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by","time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin

请注意,这确实会带来可衡量的性能开销——具体成本取决于 Go 的版本,但在最近使用 1.6 和 1.7 版本的测试中,开销在 20% 到 40% 之间。你可以通过基准测试在自己的环境中验证这一点:

go test -bench=ReportCaller

区分大小写

该组织名称已更改为小写。如果您因大小写敏感问题遇到导入冲突,请使用小写导入路径:github.com/sirupsen/logrus

示例

使用 Logrus 最简单的方式就是直接使用包级导出的日志记录器:

package main

import "github.com/sirupsen/logrus"

func main() {
  logrus.WithFields(logrus.Fields{
    "animal": "walrus",
  }).Info("A walrus appears")
}

请注意,它与标准库日志记录器完全兼容 API,因此您可以将所有地方的 log 导入替换为 log "github.com/sirupsen/logrus",这样您就拥有了 Logrus 的灵活性。您可以随心所欲地对其进行自定义:

package main

import (
  "os"

  log "github.com/sirupsen/logrus"
)

func init() {
  // Log as JSON instead of the default ASCII formatter.
  log.SetFormatter(&log.JSONFormatter{})

  // Output to stdout instead of the default stderr
  // Can be any io.Writer, see below for File example
  log.SetOutput(os.Stdout)

  // Only log the warning severity or above.
  log.SetLevel(log.WarnLevel)
}

func main() {
  log.WithFields(log.Fields{
    "animal": "walrus",
    "size":   10,
  }).Info("A group of walrus emerges from the ocean")

  log.WithFields(log.Fields{
    "omg":    true,
    "number": 122,
  }).Warn("The group's number increased tremendously!")

  log.WithFields(log.Fields{
    "omg":    true,
    "number": 100,
  }).Fatal("The ice breaks!")

  // A common pattern is to re-use fields between logging statements by re-using
  // the logrus.Entry returned from WithFields()
  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")
  contextLogger.Info("Me too")
}

对于更高级的用法,例如从同一应用程序记录到多个位置,您还可以创建一个 logrus Logger 实例:

package main

import (
  "os"

  "github.com/sirupsen/logrus"
)

// Create a new instance of the logger. You can have any number of instances.
var logger = logrus.New()

func main() {
  // The API for setting attributes is a little different than the package level
  // exported logger. See Godoc. 
  logger.Out = os.Stdout

  // You could set this to any `io.Writer` such as a file
  // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
  // if err == nil {
  //  logger.Out = file
  // } else {
  //  logger.Info("Failed to log to file, using default stderr")
  // }

  logger.WithFields(logrus.Fields{
    "animal": "walrus",
    "size":   10,
  }).Info("A group of walrus emerges from the ocean")
}

字段

Logrus 鼓励通过日志字段进行细致、结构化的日志记录,而非使用冗长且难以解析的错误消息。例如,不建议使用 logrus.Fatalf("Failed to send event %s to topic %s with key %d"),而应记录更易于检索的内容:

logrus.WithFields(logrus.Fields{
  "event": event,
  "topic": topic,
  "key": key,
}).Fatal("Failed to send event")

我们发现,这种 API 会促使你以一种能生成更有用日志消息的方式来思考日志记录。在很多情况下,只要在已有的日志语句中多添加一个字段,就能为我们节省数小时的时间。WithFields 调用是可选的。

通常,在 Logrus 中使用任何 printf 系列函数都应该被视为一个提示,提示你应该添加一个字段,不过,你仍然可以在 Logrus 中使用 printf 系列函数。

默认字段

在应用程序或其部分中,让某些字段始终附加到日志语句中通常很有帮助。例如,在请求上下文中,你可能希望始终记录 request_iduser_ip。无需在每一行都编写 logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip}),你可以创建一个 logrus.Entry 来传递:

requestLogger := logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") // will log request_id and user_ip
requestLogger.Warn("something not great happened")

钩子

您可以为日志级别添加钩子。例如,在 ErrorFatalPanic 级别时将错误发送到异常跟踪服务,将信息发送到 StatsD,或者同时记录到多个位置(如 syslog)。

Logrus 附带了内置钩子。您可以在 init 函数中添加这些钩子或您的自定义钩子:

package main

import (
  "log/syslog"

  "github.com/sirupsen/logrus"
  airbrake "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
  logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
)

func init() {

  // Use the Airbrake hook to report errors that have Error severity or above to
  // an exception tracker. You can create custom hooks, see the Hooks section.
  logrus.AddHook(airbrake.NewHook(123, "xyz", "production"))

  hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
  if err != nil {
    logrus.Error("Unable to connect to local syslog daemon")
  } else {
    logrus.AddHook(hook)
  }
}

注意:Syslog 钩子还支持连接到本地 syslog(例如 "/dev/log"、"/var/run/syslog" 或 "/var/run/log")。有关详细信息,请查看 syslog 钩子 README

当前已知的服务钩子列表可在本 wiki 的 页面 中找到。

级别日志

Logrus 有七个日志级别:Trace、Debug、Info、Warning、Error、Fatal 和 Panic。

logrus.Trace("Something very low level.")
logrus.Debug("Useful debugging information.")
logrus.Info("Something noteworthy happened!")
logrus.Warn("You should probably take a look at this.")
logrus.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
logrus.Fatal("Bye.")
// Calls panic() after logging
logrus.Panic("I'm bailing.")

你可以在 Logger 上设置日志级别,之后它将只记录具有该级别或更高级别的日志条目:

// Will log anything that is info or above (warn, error, fatal, panic). Default.
logrus.SetLevel(logrus.InfoLevel)

如果你的应用程序处于调试或详细模式环境中,将 logrus.Level 设置为 logrus.DebugLevel 可能会很有用。

注意:如果你希望全局日志(logrus.SetLevel(...))和 syslog 日志使用不同的日志级别,请查看 syslog hook 自述文件

日志条目

除了通过 WithFieldWithFields 添加的字段外,一些字段会自动添加到所有日志事件中:

  1. time:创建日志条目的时间戳。
  2. msg:在调用 AddFields 之后,传递给 {Info,Warn,Error,Fatal,Panic} 的日志消息。例如:Failed to send event.
  3. level:日志级别。例如:info

环境

Logrus 没有环境的概念。

如果你希望钩子(hooks)和格式化器(formatters)仅在特定环境中使用,则需要自行处理。例如,如果你的应用程序有一个全局变量 Environment,它是环境的字符串表示,你可以这样做:

import (
  "github.com/sirupsen/logrus"
)

func init() {
  // do something here to set environment depending on an environment variable
  // or command-line flag
  if Environment == "production" {
    logrus.SetFormatter(&logrus.JSONFormatter{})
  } else {
    // The TextFormatter is default, you don't actually have to do this.
    logrus.SetFormatter(&logrus.TextFormatter{})
  }
}

此配置是 logrus 的预期使用方式,但 JSON 格式在生产环境中大多仅在使用 Splunk 或 Logstash 等工具进行日志聚合时才有用。

格式化器

内置的日志格式化器包括:

  • logrus.TextFormatter 如果日志输出目标是 TTY,则会以彩色方式记录事件,否则不使用彩色。
    • 若要在没有 TTY 时强制彩色输出,请将 ForceColors 字段设置为 true。若要即使存在 TTY 也强制不使用彩色输出,请将 DisableColors 字段设置为 true
    • 在支持 ANSI(虚拟终端)的现代 Windows 终端上,TextFormatter 会自动启用彩色输出。
    • 如果您的环境不支持 ANSI 转义序列,请使用 github.com/mattn/go-colorable 包装日志输出,并设置 ForceColors(或 CLICOLOR_FORCE=1)以通过包装器启用彩色。
    • 启用彩色时,日志级别默认会被截断为 4 个字符。要禁用截断,请将 DisableLevelTruncation 字段设置为 true
    • 当输出到 TTY 时,将所有日志级别文本调整为相同宽度,以便在列中直观扫描,通常会很有帮助。将 PadLevelText 字段设置为 true 可通过为级别文本添加填充来实现此行为。
  • logrus.JSONFormatter 将字段以 JSON 格式记录。

第三方日志格式化器:

您可以通过实现 Formatter 接口来定义自己的格式化器,该接口需要一个 Format 方法。Format 方法接收一个 *Entryentry.Data 是一个 Fields 类型(map[string]any),包含您的所有字段以及默认字段(参见上文的条目部分):

type MyJSONFormatter struct{}

logrus.SetFormatter(new(MyJSONFormatter))

func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
  // Note this doesn't include Time, Level and Message which are available on
  // the Entry. Consult `godoc` on information about those fields or read the
  // source of the official loggers.
  serialized, err := json.Marshal(entry.Data)
    if err != nil {
      return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
    }
  return append(serialized, '\n'), nil
}

将 Logger 作为 io.Writer

Logrus 可以转换为 io.Writer。该 writer 是 io.Pipe 的末端,关闭它是您的责任。

w := logger.Writer()
defer w.Close()

srv := http.Server{
    // create a stdlib log.Logger that writes to
    // logrus.Logger.
    ErrorLog: log.New(w, "", 0),
}

写入该 writer 的每一行都将按常规方式打印,会使用格式化器和钩子。这些日志条目的级别为 info

这意味着我们可以轻松覆盖标准库日志记录器:

logger := logrus.New()
logger.Formatter = &logrus.JSONFormatter{}

// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log.SetOutput(logger.Writer())

日志轮转

Logrus 本身不提供日志轮转功能。日志轮转应由外部程序(如 logrotate(8))来完成,这些程序可以压缩并删除旧的日志条目。日志轮转不应作为应用级日志记录器的功能。

工具

工具 描述
Logrus Mate Logrus Mate 是一个用于 Logrus 的日志记录器管理工具,您可以通过配置文件初始化日志记录器的级别、钩子和格式化器,以便在不同环境中使用不同的配置生成日志记录器。
Logrus Viper Helper 一个围绕 Logrus 的辅助工具,通过包装 spf13/Viper 来加载配置,功能强大!它借鉴了 Logrus Mate 的部分特性,以简化 Logrus 的配置。示例

测试

Logrus 内置了用于断言日志消息存在的工具。这是通过 test 钩子实现的,提供以下功能:

  • 现有日志记录器的装饰器(test.NewLocaltest.NewGlobal),其主要作用是添加 test 钩子
  • 测试日志记录器(test.NewNullLogger),仅记录日志消息(不输出任何内容):
import(
  "testing"

  "github.com/sirupsen/logrus"
  "github.com/sirupsen/logrus/hooks/test"
  "github.com/stretchr/testify/assert"
)

func TestSomething(t*testing.T){
  logger, hook := test.NewNullLogger()
  logger.Error("Helloerror")

  assert.Equal(t, 1, len(hook.Entries))
  assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
  assert.Equal(t, "Helloerror", hook.LastEntry().Message)

  hook.Reset()
  assert.Nil(t, hook.LastEntry())
}

致命错误处理器

Logrus 可以注册一个或多个函数,当记录任何 fatal 级别消息时,这些函数将被调用。已注册的处理器会在 logrus 执行 os.Exit(1) 之前运行。如果调用者需要优雅地关闭程序,此行为可能会有所帮助。与可通过延迟执行的 recover 拦截的 panic("Something went wrong...") 调用不同,os.Exit(1) 调用无法被拦截。

// ...
handler := func() {
  // gracefully shut down something...
}
logrus.RegisterExitHandler(handler)
// ...

线程安全

默认情况下,Logger 通过互斥锁(mutex)来保护并发写入操作。在调用钩子(hooks)和写入日志时,会持有该互斥锁。 如果确定不需要此类锁定,可以调用 logger.SetNoLock() 来禁用锁定功能。

不需要锁定的情况包括:

  • 未注册任何钩子,或者钩子的调用本身已经是线程安全的。

  • 对 logger.Out 的写入操作本身已经是线程安全的,例如:

    1. logger.Out 受到锁的保护。

    2. logger.Out 是使用 O_APPEND 标志打开的 os.File 句柄,并且每次写入的大小都小于 4k。(这允许多线程/多进程写入)

      (参考 http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/

项目介绍

Structured, pluggable logging for Go.

定制我的领域
29725.75 K2.29 K访问 GitHub