# Zero Allocation JSON LoggerZerolog https://github.com/rs/zerolog
Zerolog 包提供了一个专用于 JSON 输出的快速且简单的 Logger 记录器。
Zerolog 的 API 旨在提供出色的开发人员体验和令人惊叹的性能。其独特的链接 API 允许 Zerolog 通过避免分配和反射来写入 JSON(或 CBOR)日志事件。
Uber 的 zap 库率先采用了这种方法。 Zerolog 通过更简单的 API 和更好的性能将这一概念提升到了一个新的水平。
为了保持代码库和 API 简单,zerolog 仅专注于高效的结构化日志记录。使用提供的(但效率低下) zerolog.ConsoleWriter
可以在 console 上进行漂亮的日志记录。
# 安装go get -u github.com/rs/zerolog/log
# 日志# 简单示例package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix log. Print ( "hello world" ) }
注意:默认情况下日志写入 os.Stderr
注意: log.Print
的默认日志级别是 debug
# 键值对日志Zerolog 允许以键值对的形式将数据添加到日志消息中。
package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix log. Debug ( ) . Str ( "Scale" , "833 cents" ) . Float64 ( "Interval" , 833.09 ) . Msg ( "Fibonacci is everywhere" ) log. Debug ( ) . Str ( "Name" , "Tom" ) . Send ( ) }
# 键值对里加键值对日志log. Info ( ) . Str ( "foo" , "bar" ) . Dict ( "dict" , zerolog. Dict ( ) . Str ( "bar" , "baz" ) . Int ( "n" , 1 ) , ) . Msg ( "hello world" )
# 日志分级package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix log. Info ( ) . Msg ( "hello world" ) }
需要注意的是,当使用 Zerolog 链接 API 时,如上所示 ( log.Info().Msg("hello world"
),必须具有 Msg
或 Msgf
方法调用。如果忘记添加其中任何一个,则不会出现日志,也不会出现编译时错误。
Zerolog 允许在以下级别进行日志记录(从最高到最低)
panic ( zerolog.PanicLevel
, 5) fatal ( zerolog.FatalLevel
, 4) error ( zerolog.ErrorLevel
, 3) warn ( zerolog.WarnLevel
, 2) info ( zerolog.InfoLevel
, 1) debug ( zerolog.DebugLevel
, 0) trace ( zerolog.TraceLevel
, -1) # 没有级别或消息的日志package mainimport ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix log. Log ( ) . Str ( "foo" , "bar" ) . Msg ( "" ) }
# 创建 Logger 实例logger := zerolog. New ( os. Stderr) . With ( ) . Timestamp ( ) . Logger ( ) logger. Info ( ) . Str ( "foo" , "bar" ) . Msg ( "hello world" )
# 子 Loggersublogger := log. With ( ) . Str ( "component" , "foo" ) . Logger ( ) sublogger. Info ( ) . Msg ( "hello world" )
# 美化日志使用 zerolog.ConsoleWriter
log. Logger = log. Output ( zerolog. ConsoleWriter{ Out: os. Stderr} ) log. Info ( ) . Str ( "foo" , "bar" ) . Msg ( "Hello world" )
自定义配置和格式:
output := zerolog. ConsoleWriter{ Out: os. Stdout, TimeFormat: time. RFC3339} output. FormatLevel = func ( i interface { } ) string { return strings. ToUpper ( fmt. Sprintf ( "| %-6s|" , i) ) } output. FormatMessage = func ( i interface { } ) string { return fmt. Sprintf ( "***%s****" , i) } output. FormatFieldName = func ( i interface { } ) string { return fmt. Sprintf ( "%s:" , i) } output. FormatFieldValue = func ( i interface { } ) string { return strings. ToUpper ( fmt. Sprintf ( "%s" , i) ) } log := zerolog. New ( output) . With ( ) . Timestamp ( ) . Logger ( ) log. Info ( ) . Str ( "foo" , "bar" ) . Msg ( "Hello World" )
# 线程安全、无锁、非阻塞 writer
如果 writer
很慢,或者 writer
不是线程安全的。并且不想因为 writer
写入过慢减少日志的写入速度。试看看 diode.Writer
,如下所示:
wr := diode. NewWriter ( os. Stdout, 1000 , 10 * time. Millisecond, func ( missed int ) { fmt. Printf ( "Logger Dropped %d messages" , missed) } ) log := zerolog. New ( wr) log. Print ( "test" )
需要安装 code.cloudfoundry.org/go-diodes
才能使用此功能。
# 日志采样sampled := log. Sample ( & zerolog. BasicSampler{ N: 10 } ) sampled. Info ( ) . Msg ( "will be logged every 10 messages" )
采样进阶:
sampled := log. Sample ( zerolog. LevelSampler{ DebugSampler: & zerolog. BurstSampler{ Burst: 5 , Period: 1 * time. Second, NextSampler: & zerolog. BasicSampler{ N: 100 } , } , } ) sampled. Debug ( ) . Msg ( "hello world" )
# Hookstype SeverityHook struct { } func ( h SeverityHook) Run ( e * zerolog. Event, level zerolog. Level, msg string ) { if level != zerolog. NoLevel { e. Str ( "severity" , level. String ( ) ) } } hooked := log. Hook ( SeverityHook{ } ) hooked. Warn ( ) . Msg ( "" )
# 通过 context 传递日志ctx := log. With ( ) . Str ( "component" , "module" ) . Logger ( ) . WithContext ( ctx) log. Ctx ( ctx) . Info ( ) . Msg ( "hello world" )
# 设置 Stdout 输出log := zerolog. New ( os. Stdout) . With ( ) . Str ( "foo" , "bar" ) . Logger ( ) stdlog. SetFlags ( 0 ) stdlog. SetOutput ( log) stdlog. Print ( "hello world" )
# Context 集成Go context 在 Go 的代码中到处被使用,如果将 logger 嵌入到 context 中,Logger 就更方便传递到难以注入的地方。Logger
实例可以使用 Logger.WithContext(ctx)
附加到 Go context.Context
,并使用 zerolog.Ctx(ctx)
从中提取。例如:
func f ( ) { logger := zerolog. New ( os. Stdout) ctx := context. Background ( ) ctx = logger. WithContext ( ctx) someFunc ( ctx) } func someFunc ( ctx context. Context) { logger := zerolog. Ctx ( ctx) logger. Info ( ) . Msg ( "Hello" ) }
第二种集成方式:
将当前 context.Context
传递到日志的事件中,并从 hooks
中检索它。 这对追踪存储在 go Context 中的信息 或者 logID 很有帮助。
type TracingHook struct { } func ( h TracingHook) Run ( e * zerolog. Event, level zerolog. Level, msg string ) { ctx := e. Ctx ( ) spanId := getSpanIdFromContext ( ctx) e. Str ( "span-id" , spanId) } func f ( ) { logger := zerolog. New ( os. Stdout) logger = logger. Hook ( TracingHook{ } ) ctx := context. Background ( ) logger. Info ( ) . Ctx ( ctx) . Msg ( "Hello" ) }
# 多日志输出zerolog.MultiLevelWriter
可用于将日志消息发送到多个输出。在此示例中,我们将日志消息发送到 os.Stdout
和内置的 ConsoleWriter
。
func main ( ) { consoleWriter := zerolog. ConsoleWriter{ Out: os. Stdout} multi := zerolog. MultiLevelWriter ( consoleWriter, os. Stdout) logger := zerolog. New ( multi) . With ( ) . Timestamp ( ) . Logger ( ) logger. Info ( ) . Msg ( "Hello World!" ) }
# 全局设置某些设置可以更改并将应用于所有 logger:
log.Logger
:设置此值来自定义全局 Logger(包级别方法使用的 Logger)。zerolog.SetGlobalLevel
:提高所有 Logger 的最低级别。使用 zerolog.Disabled
调用此命令可完全禁用日志记录(安静模式)。zerolog.DisableSampling
:如果参数为 true
,所有采样的 Logger 将停止采样并输出 100% 的日志事件。zerolog.TimestampFieldName
:设置自定义 Timestamp
字段名称。zerolog.LevelFieldName
:设置自定义级别字段名称。zerolog.MessageFieldName
:设置自定义消息字段名称。zerolog.ErrorFieldName
:设置自定义 Err
字段名称。zerolog.TimeFieldFormat
:设置自定义 Time
字段值格式。如果使用 zerolog.TimeFormatUnix
、 zerolog.TimeFormatUnixMs
或 zerolog.TimeFormatUnixMicro
设置,时间将被格式化为 UNIX 时间戳。zerolog.DurationFieldUnit
:设置自定义时间单位。 Dur
添加的持续时间类型字段(默认: time.Millisecond
)。zerolog.DurationFieldInteger
:设置为 true
,则 Dur
字段将格式化为整数而不是浮点数(默认值: false
)。zerolog.ErrorHandler
:当 Zerolog 无法在其输出上写入事件时调用。如果未设置,则会在 stderr 上打印错误。该处理程序必须是线程安全且非阻塞的。# 设置全局日志级别package mainimport ( "flag" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix debug := flag. Bool ( "debug" , false , "sets log level to debug" ) flag. Parse ( ) zerolog. SetGlobalLevel ( zerolog. InfoLevel) if * debug { zerolog. SetGlobalLevel ( zerolog. DebugLevel) } log. Debug ( ) . Msg ( "This message appears only when log level set to Debug" ) log. Info ( ) . Msg ( "This message appears when log level set to Debug or Info" ) if e := log. Debug ( ) ; e. Enabled ( ) { value := "bar" e. Str ( "foo" , value) . Msg ( "some debug message" ) } }
$ ./logLevelExample { "time" :1516387492,"level" : "info" ,"message" : "This message appears when log level set to Debug or Info" }
$ ./logLevelExample -debug { "time" :1516387573,"level" : "debug" ,"message" : "This message appears only when log level set to Debug" } { "time" :1516387573,"level" : "info" ,"message" : "This message appears when log level set to Debug or Info" } { "time" :1516387573,"level" : "debug" ,"foo" : "bar" ,"message" : "some debug message" }
# 设定全局初始化字段名称zerolog. TimestampFieldName = "t" zerolog. LevelFieldName = "l" zerolog. MessageFieldName = "m" log. Info ( ) . Msg ( "hello world" )
# 设定全局键值对日志log. Logger = log. With ( ) . Str ( "service" , "gin" ) . Logger ( )
# 设定全局文件和行号log. Logger = log. With ( ) . Caller ( ) . Logger ( ) log. Info ( ) . Msg ( "hello world" )
相当于 Lshortfile
:
zerolog. CallerMarshalFunc = func ( pc uintptr , file string , line int ) string { short := file for i := len ( file) - 1 ; i > 0 ; i-- { if file[ i] == '/' { short = file[ i+ 1 : ] break } } file = short return file + ":" + strconv. Itoa ( line) } log. Logger = log. With ( ) . Caller ( ) . Logger ( ) log. Info ( ) . Msg ( "hello world" )
# 错误日志# Error logpackage mainimport ( "errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix err := errors. New ( "seems we have an error here" ) log. Error ( ) . Err ( err) . Msg ( "" ) }
# 使用 Stacktrace 记录错误使用 github.com/pkg/errors
,可以用格式化的 Stacktrace 追踪错误信息
package mainimport ( "github.com/pkg/errors" "github.com/rs/zerolog/pkgerrors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { zerolog. TimeFieldFormat = zerolog. TimeFormatUnix zerolog. ErrorStackMarshaler = pkgerrors. MarshalStack err := outer ( ) log. Error ( ) . Stack ( ) . Err ( err) . Msg ( "" ) } func inner ( ) error { return errors. New ( "seems we have an error here" ) } func middle ( ) error { err := inner ( ) if err != nil { return err } return nil } func outer ( ) error { err := middle ( ) if err != nil { return err } return nil }
必须设置 Zerolog.ErrorStackMarshaler
才能使堆栈输出任何内容。
# Fatal logpackage mainimport ( "errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main ( ) { err := errors. New ( "A repo man spends his life getting into tense situations" ) service := "myservice" zerolog. TimeFieldFormat = zerolog. TimeFormatUnix log. Fatal ( ) . Err ( err) . Str ( "service" , service) . Msgf ( "Cannot start %s" , service) }
# 注意事项# 字段重复zerolog 不会对重复字段进行删除。多次使用相同的字段会在最终 JSON 一起出现
一般情况下,程序会取最后一个字段的值,但是最好还是检查你的代码。
logger := zerolog. New ( os. Stderr) . With ( ) . Timestamp ( ) . Logger ( ) logger. Info ( ) . Timestamp ( ) . Msg ( "dup" )
# 并发安全调用 UpdateContext 时要小心。它不是并发安全的。使用 With 方法创建子 logger:
func handler ( w http. ResponseWriter, r * http. Request) { logger := log. Logger. With ( ) . Logger ( ) logger. UpdateContext ( func ( c zerolog. Context) zerolog. Context { ... } ) }