Logrus是Go的结构化日志框架且与标准库API兼容

Logrus


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

**Logrus 处于维护模式。**我们不会引入新功能。这实在太难了,很难做到不破坏许多人的项目,而这恰恰是日志库最不希望看到的(再次强调……)。

这并不意味着 Logrus 已经停产。Logrus 将继续维护,以提升安全性、(向后兼容的)错误修复和性能(目前我们受接口限制)。

我认为 Logrus 最大的贡献在于促进了如今Golang 结构化日志记录的广泛使用。由于优秀的 Go 社区已经独立构建了这些版本,因此似乎没有必要对 Logrus V2 进行重大的、突破性的迭代。许多优秀的替代方案已经涌现。如果 Logrus 按照我们目前对 Go 结构化日志记录的了解进行重新设计,它看起来应该就是这些版本。例如,Zerolog, Zap, and Apex

遇到奇怪的大小写敏感问题? 过去,可以同时导入大小写形式的 Logrus。由于 Go 软件包环境的原因,这在社区中引发了问题,因此我们需要一个标准。某些环境在使用大写形式时遇到了问题,因此我们决定使用小写形式。所有使用 logrus 的软件包都需要使用小写形式:github.com/sirupsen/logrus。任何未使用小写形式的软件包都应进行更改。

要修复 Glide 问题,请参阅这些评论。有关大小写问题的深入解释,请参阅此评论

开发过程中颜色编码清晰(当连接 TTY 时,否则为纯文本):

Colored

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

json 复制代码
{"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 格式兼容:

text 复制代码
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!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true

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

go 复制代码
logrus.SetFormatter(&logrus.TextFormatter{
    DisableColors: true,
    FullTimestamp: true,
})

日志方法名称

如果您希望将调用方法添加为字段,请通过以下方式指示日志记录器:

go 复制代码
logrus.SetReportCaller(true)

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

json 复制代码
{"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"}
text 复制代码
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% 之间。您可以通过基准测试在您的环境中验证这一点:

bash 复制代码
go test -bench=.*CallerTracing

区分大小写

组织名称已更改为小写,并且不会更改回来。如果您由于区分大小写而遇到导入冲突,请使用小写导入:github.com/sirupsen/logrus

示例

使用 Logrus 最简单的方法是使用包级导出的记录器:

go 复制代码
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 的灵活性。您可以根据需要进行任何自定义:

go 复制代码
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 的实例:

go 复制代码
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"),不如记录更易于发现的内容:

go 复制代码
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 来传递:

go 复制代码
requestLogger := logger.WithFields(logrus.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") // 将记录 request_id 和 user_ip
requestLogger.Warn("something not great happened")

钩子

您可以为日志级别添加钩子。例如,在出现“Error”、“Fatal”和“Panic”时,将错误发送到异常跟踪服务,将信息发送到 StatsD,或同时记录到多个位置,例如 syslog。

Logrus 附带内置钩子。请在 init 中添加这些钩子或您的自定义钩子:

go 复制代码
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)。

go 复制代码
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”上设置日志级别,这样它只会记录 该严重级别或更高级别的条目:

go 复制代码
// 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 README

条目

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

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

环境

Logrus 没有环境的概念。

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

go 复制代码
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。如果 stdout 是 tty,则以彩色格式记录事件,否则
    不带彩色。
  • 注意: 要强制在没有 TTY 时输出彩色,请将 ForceColors字段设置为 true。要强制即使有 TTY 也不输出彩色,请将DisableColors 字段设置为 true。对于 Windows,请参阅github.com/mattn/go-colorable
  • 启用颜色后,级别默认会被截断为 4 个字符。要禁用截断,请将 DisableLevelTruncation 字段设置为 true
  • 输出到 TTY 时,通常需要直观地向下扫描所有层级宽度相同的列。将 PadLevelText 字段设置为 true 即可启用此行为,即向层级文本添加填充。
  • 所有选项均列在 生成的文档 中。
  • logrus.JSONFormatter。将字段记录为 JSON 格式。
  • 所有选项均列在 生成的文档 中。

第三方日志格式化程序:

您可以通过实现 Formatter 接口来定义格式化程序,并需要一个 Format 方法。Format 接受一个 *Entry 参数。entry.Data 是一个Fields 类型(map[string]interface{}),包含所有您的字段以及默认字段(参见上文的 Entries 部分):

go 复制代码
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。该写入器是 io.Pipe 的末端,您需要负责关闭它。

go 复制代码
w := logger.Writer()
defer w.Close()

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

Each line written to that writer will be printed the usual way, using formatters
and hooks. The level for those entries is info.

This means that we can override the standard library logger easily:

go 复制代码
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 内置了一个用于断言日志消息存在的功能。该功能通过 test 钩子实现,并提供:

  • 现有日志记录器(test.NewLocaltest.NewGlobal)的装饰器,基本上只需添加 test 钩子即可
  • 一个测试日志记录器(test.NewNullLogger),它只记录日志消息(不输出任何消息):
go 复制代码
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 可以注册一个或多个函数,当记录任何“致命”级别的消息时,这些函数将被调用。注册的处理程序将在logrus 执行 os.Exit(1) 之前执行。如果调用者需要优雅地关闭进程,此行为可能会有所帮助。与可以使用延迟 recover 拦截的 panic("Something went wrong...") 调用不同,os.Exit(1) 调用无法被拦截。

go 复制代码
// ...
handler := func() {
  // gracefully shut down something...
}
logrus.RegisterExitHandler(handler)
// ...

线程安全

默认情况下,Logger 受互斥锁保护,用于并发写入。调用钩子并写入日志时,该互斥锁会被保留。如果您确定不需要这种锁定,可以调用 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/)

关于项目

Logrus 是一款基于 Go (golang) 的结构化日志框架,其 API 与标准库日志框架完全兼容。
MIT
Golang
25,585
2281
302
2013-10-17
2025-10-23

增长趋势 - stars