sirupsen/logrus

GitHub: sirupsen/logrus

Go 语言的结构化日志库,提供可插拔的格式化器和 Hook 机制,与标准库 log API 完全兼容,目前处于维护模式。

Stars: 25749 | Forks: 2285

# Logrus :walrus: [![Build Status](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [![Go Reference](https://pkg.go.dev/badge/github.com/sirupsen/logrus.svg)](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 时,否则只是纯文本): ![Colored](http://i.imgur.com/PY7qMwd.png) 使用 `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分析, 日志审计