sirupsen/logrus
GitHub: sirupsen/logrus
Go 语言的结构化日志库,提供可插拔的格式化器和 Hook 机制,与标准库 log API 完全兼容,目前处于维护模式。
Stars: 25749 | Forks: 2285
# Logrus
[](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [](https://pkg.go.dev/github.com/sirupsen/logrus)
Logrus 是一个用于 Go (golang) 的结构化 logger,与标准库 logger 完全 API 兼容。
**Logrus 处于维护模式。** 该项目专注于安全性、bug 修复和性能改进。除了为提供与其他日志生态系统(例如 Go 的 [log/slog](https://pkg.go.dev/log/slog))的互操作性所需的更改外,不计划推出新功能。
我相信 Logrus 最大的贡献在于,它在如今 Golang 中结构化日志的广泛使用中发挥了一定作用。由于出色的 Go 社区已经独立构建了这些功能,似乎没有理由对 Logrus V2 进行重大的、破坏性的迭代。已经涌现出了许多出色的替代方案。如果根据我们今天对 Go 中结构化日志的了解来重新设计 Logrus,它看起来就会像那些替代方案一样。例如,请查看 [Zerolog][zerolog]、[Zap][zap] 和 [Apex][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](https://pkg.go.dev/github.com/kr/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!" 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 也能保持此行为,请按如下方式设置你的 formatter:
```
logrus.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
```
#### 记录方法名
如果你希望将调用方法作为 field 添加,请通过以下方式指示 logger:
```
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
```
#### 大小写敏感
该组织的名称已[更改为小写][1]。如果你由于大小写敏感而遇到 import 冲突,请使用小写的 import:`github.com/sirupsen/logrus`。
#### 示例
使用 Logrus 最简单的方法是直接使用包级别导出的 logger:
```
package main
import "github.com/sirupsen/logrus"
func main() {
logrus.WithFields(logrus.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
```
请注意,它与 stdlib logger 完全 API 兼容,因此你可以在所有地方将 `log` import 替换为 `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 通过记录 fields 而不是冗长、难以解析的错误消息,鼓励谨慎、结构化的日志记录。例如,不要使用:`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 迫使你以一种能产生更有用日志消息的方式来思考日志记录。我们遇到过无数次这样的情况:只需在已有的日志语句中添加一个 field,就能为我们节省数小时的时间。`WithFields` 调用是可选的。
通常,在 Logrus 中使用任何 `printf` 系列函数都应该被视为你需要添加一个 field 的提示,但是,你仍然可以在 Logrus 中使用 `printf` 系列函数。
#### 默认 Fields
通常,将 fields _始终_ 附加到应用程序或其部分组件中的日志语句会很有帮助。例如,你可能希望在请求的上下文中始终记录 `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")
```
#### Hooks
你可以为日志级别添加 hooks。例如,在 `Error`、`Fatal` 和 `Panic` 时将错误发送到异常跟踪服务,将 info 发送到 StatsD,或者同时记录到多个地方,例如 syslog。
Logrus 自带[内置 hooks](hooks/)。在 `init` 中添加它们或你的自定义 hook:
```
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)
}
}
```
目前已知的 service hooks 列表可以在 wiki [页面](https://github.com/sirupsen/logrus/wiki/Hooks)中找到
#### 级别日志记录
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 README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging)。
#### Entries
除了通过 `WithField` 或 `WithFields` 添加的 fields 之外,一些 fields 会自动添加到所有日志事件中:
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 等工具进行日志聚合时才有用。
#### Formatters
内置的日志 formatters 有:
* `logrus.TextFormatter`。如果 stdout 是 tty,则以彩色记录事件,否则没有颜色。
* *注意:* 要在没有 TTY 时强制彩色输出,请将 `ForceColors` field 设置为 `true`。要强制没有彩色输出,即使存在 TTY,也要将 `DisableColors` field 设置为 `true`。对于 Windows,请查看 [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable)。
* 启用颜色后,默认情况下级别会被截断为 4 个字符。要禁用截断,请将 `DisableLevelTruncation` field 设置为 `true`。
* 输出到 TTY 时,在所有级别宽度相同的列中向下进行视觉扫描通常很有帮助。将 `PadLevelText` field 设置为 `true` 可启用此行为,方法是为级别文本添加填充。
* 所有选项都在[生成的文档](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter)中列出。
* `logrus.JSONFormatter`。将 fields 记录为 JSON。
* 所有选项都在[生成的文档](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter)中列出。
第三方的日志 formatters:
* [`FluentdFormatter`](https://github.com/joonix/log)。格式化可由 Kubernetes 和 Google Container Engine 解析的条目。
* [`GELF`](https://github.com/fabienm/go-logrus-formatters)。格式化条目使其符合 Graylog 的 [GELF 1.1 规范](http://docs.graylog.org/en/2.4/pages/gelf.html)。
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook)。将 fields 记录为 [Logstash](http://logstash.net) Events。
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter)。显示日志条目来源以及替代布局。
* [`zalgo`](https://github.com/aybabtme/logzalgo)。召唤 Zalgo 的力量。
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter)。将 logrus fields 转换为嵌套结构。
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool)。打印日志时获取 fileName、日志的行号以及最新的函数名;将日志保存到文件。
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter)。带有添加了人类可读标题的 logrus 消息 JSON formatter。
* [`easy-logrus-formatter`](https://github.com/WeiZhixiong/easy-logrus-formatter)。为 logrus 提供一个用户友好的 formatter。
* [`redactrus`](https://github.com/ibreakthecloud/redactrus)。从日志中隐藏(Redact)如 password、apikeys、email 等敏感信息。
你可以通过实现 `Formatter` 接口来定义你的 formatter,该接口需要一个 `Format` 方法。`Format` 接收一个 `*Entry`。`entry.Data` 是一个 `Fields` 类型(`map[string]any`),包含你所有的 fields 以及默认 fields(参见上面的 Entries 部分):
```
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 的每一行都将使用 formatters 和 hooks 以通常的方式打印。这些条目的级别是 `info`。
这意味着我们可以轻松覆盖标准库 logger:
```
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())
```
#### Rotation
Logrus 不提供日志轮转。日志轮转应该由外部程序(如 `logrotate(8)`)完成,该程序可以压缩和删除旧的日志条目。它不应该是应用程序级别 logger 的功能。
#### 工具
| 工具 | 描述 |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate 是一个用于管理 logrus logger 的工具,你可以通过配置文件初始化 logger 的级别、hook 和 formatter,该 logger 将在不同环境中使用不同的配置生成。|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|一个围绕 Logrus 的 Helper,封装了 spf13/Viper 以加载带有 fangs 的配置!并使用 [Logrus Mate](https://github.com/gogap/logrus_mate) 的某些行为来简化 Logrus 配置。[示例](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
#### 测试
Logrus 具有用于断言日志消息是否存在的内置工具。这是通过 `test` hook 实现的,并提供:
* 用于现有 logger 的装饰器(`test.NewLocal` 和 `test.NewGlobal`),它们基本上只是添加了 `test` hook
* 一个测试 logger(`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())
}
```
#### Fatal handlers
Logrus 可以注册一个或多个函数,这些函数将在记录任何 `fatal` 级别的消息时被调用。已注册的 handlers 将在 logrus 执行 `os.Exit(1)` 之前运行。如果调用者需要优雅关闭,这种行为可能会很有用。与可以通过 deferred `recover` 拦截的 `panic("Something went wrong...")` 调用不同,对 `os.Exit(1)` 的调用无法被拦截。
```
// ...
handler := func() {
// gracefully shut down something...
}
logrus.RegisterExitHandler(handler)
// ...
```
#### 线程安全
默认情况下,Logger 由 mutex 保护以进行并发写入。在调用 hooks 和写入日志时会持有该 mutex。
如果你确定不需要此类锁定,则可以调用 logger.SetNoLock() 来禁用锁定。
不需要锁定的情况包括:
* 你没有注册任何 hooks,或者 hooks 的调用已经是线程安全的。
* 写入 logger.Out 已经是线程安全的,例如:
1. logger.Out 受锁保护。
2. logger.Out 是一个使用 `O_APPEND` 标志打开的 os.File handler,并且每次写入都小于 4k。(这允许多线程/多进程写入)
(参考 )
标签:EVTX分析, 日志审计