Structured, pluggable logging for Go.
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 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
![]()
Logrus 是一款适用于 Go(golang)的结构化日志记录器,与标准库日志器完全 API 兼容。
Logrus 目前处于维护模式。 该项目专注于安全性、错误修复和性能改进。除为实现与其他日志生态系统(例如 Go 的 log/slog)的互操作性而必需的更改外,暂无新增功能计划。
我认为 Logrus 最大的贡献在于,它在如今 Go 语言中结构化日志的广泛应用方面发挥了一定作用。似乎没有必要对 Logrus 进行重大的、不兼容的 V2 版本迭代,因为出色的 Go 社区已经独立开发了这些功能。许多优秀的替代方案纷纷涌现。如果 Logrus 是根据我们今天对 Go 中结构化日志的了解来重新设计的,它也会与这些方案相似。例如,可以了解一下 Zerolog、Zap 和 Apex。
在开发环境中支持美观的彩色输出(当连接 TTY 时,否则为纯文本):

使用 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_id 和 user_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")
钩子
您可以为日志级别添加钩子。例如,在 Error、Fatal 和 Panic 级别时将错误发送到异常跟踪服务,将信息发送到 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 自述文件。
日志条目
除了通过 WithField 或 WithFields 添加的字段外,一些字段会自动添加到所有日志事件中:
time:创建日志条目的时间戳。msg:在调用AddFields之后,传递给{Info,Warn,Error,Fatal,Panic}的日志消息。例如:Failed to send event.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可通过为级别文本添加填充来实现此行为。
- 若要在没有 TTY 时强制彩色输出,请将
logrus.JSONFormatter将字段以 JSON 格式记录。
第三方日志格式化器:
FluentdFormatter。格式化可由 Kubernetes 和 Google Container Engine 解析的日志条目。GELF。格式化日志条目以符合 Graylog 的 GELF 1.1 规范。logstash。将字段作为 Logstash 事件记录。prefixed。显示日志条目的来源以及替代布局。zalgo。调用 Zalgo 的力量。nested-logrus-formatter。将 logrus 字段转换为嵌套结构。powerful-logrus-formatter。打印日志时获取文件名、日志行号和最新的函数名;将日志保存到文件。caption-json-formatter。logrus 的消息 JSON 格式化器,添加了人类可读的标题。easy-logrus-formatter。为 logrus 提供用户友好的格式化器。redactrus。从日志中屏蔽敏感信息,如密码、API 密钥、电子邮件等。
您可以通过实现 Formatter 接口来定义自己的格式化器,该接口需要一个 Format 方法。Format 方法接收一个 *Entry。entry.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.NewLocal和test.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 的写入操作本身已经是线程安全的,例如:
-
logger.Out 受到锁的保护。
-
logger.Out 是使用
O_APPEND标志打开的 os.File 句柄,并且每次写入的大小都小于 4k。(这允许多线程/多进程写入)(参考 http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
-