Logrus 是 Go (golang) 的结构化记录器,与标准库记录器 API 完全兼容。
Logrus 处于维护模式。我们不会引入新功能。以一种不会破坏很多人的项目的方式来做到这一点太难了,这是您最不想从日志库中得到的东西(再次......)。
这并不意味着 Logrus 已经死了。 Logrus 将继续维护安全性、(向后兼容)错误修复和性能(我们受到界面的限制)。
我相信 Logrus 最大的贡献是在当今 Golang 结构化日志记录的广泛使用中发挥了作用。似乎没有理由对 Logrus V2 进行重大、突破性的迭代,因为出色的 Go 社区已经独立构建了这些迭代。许多奇妙的替代方案如雨后春笋般涌现。如果使用我们今天对 Go 中结构化日志的了解重新设计,Logrus 看起来就会像那些。例如,查看 Zerolog、Zap 和 Apex。
看到奇怪的区分大小写的问题吗?过去可以以大写和小写形式导入 Logrus。由于Go包环境的原因,这在社区中引起了问题,我们需要一个标准。某些环境遇到了大写变体的问题,因此决定使用小写变体。使用logrus
的所有内容都需要使用小写: github.com/sirupsen/logrus
。任何不是的包都应该更改。
要修复 Glide,请参阅这些评论。有关外壳问题的深入解释,请参阅此评论。
在开发中很好地进行了颜色编码(当附加 TTY 时,否则只是纯文本):
使用log.SetFormatter(&log.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 时,使用默认的log.SetFormatter(&log.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!" 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 也能实现此行为,请按如下方式设置格式化程序:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
如果您希望将调用方法添加为字段,请通过以下方式指示记录器:
log . SetReportCaller ( true )
这会将调用者添加为“方法”,如下所示:
{ "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=.*CallerTracing
该组织的名称已更改为小写字母,并且不会改回来。如果您因区分大小写而遇到导入冲突,请使用小写导入: github.com/sirupsen/logrus
。
使用 Logrus 最简单的方法就是包级导出记录器:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
请注意,它与 stdlib 记录器完全 api 兼容,因此您可以用log "github.com/sirupsen/logrus"
替换任何地方的log
导入,现在您将拥有 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 log = logrus . New ()
func main () {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log . 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 {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log . WithFields (logrus. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
}
Logrus 鼓励通过日志字段进行仔细、结构化的日志记录,而不是冗长的、无法解析的错误消息。例如,您应该记录更容易发现的内容,而不是: log.Fatalf("Failed to send event %s to topic %s with key %d")
:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
我们发现这个 API 迫使您考虑以产生更有用的日志消息的方式进行日志记录。我们遇到过无数的情况,只要在已有的日志语句中添加一个字段就可以节省我们的时间。 WithFields
调用是可选的。
一般来说,Logrus 使用任何printf
系列函数都应该被视为您应该添加字段的提示,但是,您仍然可以将printf
系列函数与 Logrus 一起使用。
通常,将字段始终附加到应用程序或应用程序的一部分中的日志语句会很有帮助。例如,您可能希望始终在请求的上下文中记录request_id
和user_ip
。您可以创建一个logrus.Entry
来传递,而不是在每一行上编写log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger := log . WithFields (log. 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
中添加这些或您的自定义挂钩:
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/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.
log . AddHook ( airbrake . NewHook ( 123 , "xyz" , "production" ))
hook , err := logrus_syslog . NewSyslogHook ( "udp" , "localhost:514" , syslog . LOG_INFO , "" )
if err != nil {
log . Error ( "Unable to connect to local syslog daemon" )
} else {
log . AddHook ( hook )
}
}
注意:Syslog hook 还支持连接到本地 syslog(例如“/dev/log”或“/var/run/syslog”或“/var/run/log”)。有关详细信息,请查看 syslog 挂钩 README。
当前已知的服务挂钩列表可以在此 wiki 页面中找到
Logrus 有七个日志记录级别:Trace、Debug、Info、Warning、Error、Fatal 和 Panic。
log . Trace ( "Something very low level." )
log . Debug ( "Useful debugging information." )
log . Info ( "Something noteworthy happened!" )
log . Warn ( "You should probably take a look at this." )
log . Error ( "Something failed but I'm not quitting." )
// Calls os.Exit(1) after logging
log . Fatal ( "Bye." )
// Calls panic() after logging
log . Panic ( "I'm bailing." )
您可以在Logger
上设置日志记录级别,然后它只会记录具有该严重性或高于其严重性的条目:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
如果您的应用程序具有调试或详细环境,则在调试或详细环境中设置log.Level = logrus.DebugLevel
可能会很有用。
注意:如果您希望全局 ( log.SetLevel(...)
) 和 syslog 日志记录使用不同的日志级别,请检查 syslog 挂钩自述文件。
除了使用WithField
或WithFields
添加的字段之外,一些字段会自动添加到所有日志记录事件中:
time
。创建条目时的时间戳。msg
。 AddFields
调用后,日志消息传递到{Info,Warn,Error,Fatal,Panic}
。例如, Failed to send event.
level
。日志记录级别。例如info
。 Logrus 没有环境的概念。
如果您希望挂钩和格式化程序仅在特定环境中使用,您应该自己处理。例如,如果您的应用程序有一个全局变量Environment
,它是环境的字符串表示形式,您可以执行以下操作:
import (
log "github.com/sirupsen/logrus"
)
func init () {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log . SetFormatter ( & log. JSONFormatter {})
} else {
// The TextFormatter is default, you don't actually have to do this.
log . SetFormatter ( & log. TextFormatter {})
}
}
此配置是logrus
预期使用方式,但生产中的 JSON 大多数情况下只有在使用 Splunk 或 Logstash 等工具进行日志聚合时才有用。
内置的日志格式化程序是:
logrus.TextFormatter
。如果 stdout 是 tty,则以颜色记录事件,否则不记录颜色。ForceColors
字段设置为true
。要强制没有彩色输出,即使存在 TTY,也将DisableColors
字段设置为true
。对于 Windows,请参阅 github.com/mattn/go-colorable。DisableLevelTruncation
字段设置为true
。PadLevelText
字段设置为true
可通过向级别文本添加填充来启用此行为。logrus.JSONFormatter
。将字段记录为 JSON。第三方日志格式化程序:
FluentdFormatter
。格式化可由 Kubernetes 和 Google Container Engine 解析的条目。GELF
。设置条目格式,使其符合 Graylog 的 GELF 1.1 规范。logstash
。将字段记录为 Logstash 事件。prefixed
.显示日志条目源以及替代布局。zalgo
。调用扎尔戈的力量。nested-logrus-formatter
。将 logrus 字段转换为嵌套结构。powerful-logrus-formatter
。打印日志时获取文件名、日志行号以及最新函数名;将日志保存到文件。caption-json-formatter
。 logrus 的消息 json 格式化程序,添加了人类可读的标题。您可以通过实现Formatter
接口来定义格式化程序,这需要一个Format
方法。 Format
采用*Entry
。 entry.Data
是一个Fields
类型( map[string]interface{}
),包含所有字段以及默认字段(请参阅上面的“条目”部分):
type MyJSONFormatter struct {
}
log . 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
}
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 ),
}
写入该写入器的每一行都将使用格式化程序和挂钩以通常的方式打印。这些条目的级别是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管理记录器的工具,您可以通过配置文件初始记录器的级别、挂钩和格式化程序,在不同的环境下会生成不同的配置的记录器。 |
Logrus 毒蛇助手 | Logrus 周围的助手,可与 spf13/Viper 一起包装,以使用尖牙加载配置!为了简化 Logrus 配置,请使用 Logrus Mate 的一些行为。样本 |
Logrus 有一个内置工具用于断言日志消息的存在。这是通过test
挂钩实现的,并提供:
test.NewLocal
和test.NewGlobal
)基本上只是添加test
钩子test.NewNullLogger
),仅记录日志消息(并且不输出任何内容): import (
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
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 shutdown something...
}
logrus.RegisterExitHandler(handler)
...
默认情况下,Logger 受到并发写入互斥锁的保护。调用钩子和写入日志时会保留互斥体。如果您确定不需要这样的锁定,您可以调用 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/)