rs/zerolog

GitHub: rs/zerolog

Go 语言的高性能零分配结构化 JSON 日志库,通过链式 API 和避免反射实现极致的日志写入性能。

Stars: 12465 | Forks: 628

# 零分配 JSON Logger [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/zerolog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zerolog/master/LICENSE) [![Build Status](https://static.pigsec.cn/wp-content/uploads/repos/cas/ce/ce733292a922c08274cf5a2096f8fa4cf01023bfa51a36ef6beecaaef371a9d9.svg)](https://github.com/rs/zerolog/actions/workflows/test.yml) [![Go Coverage](https://static.pigsec.cn/wp-content/uploads/repos/cas/9c/9c28aca095b7592ce0f3a5de813c6f968e7e86ec989f5c5f03d9619603e19906.svg)](https://raw.githack.com/wiki/rs/zerolog/coverage.html) zerolog 包提供了一个专用于 JSON 输出的快速且简单的 logger。 Zerolog 的 API 旨在提供出色的开发者体验和惊人的[性能](#benchmarks)。其独特的链式 API 让 zerolog 能够通过避免内存分配和反射来写入 JSON(或 CBOR)日志事件。 Uber 的 [zap](https://godoc.org/go.uber.org/zap) 库开创了这种方法。Zerolog 通过更简单的 API 和更出色的性能,将这一概念提升到了一个新的高度。 为了保持代码库和 API 的简单性,zerolog 仅专注于高效的结构化日志记录。通过使用提供(但效率较低)的 [`zerolog.ConsoleWriter`](#pretty-logging),可以在控制台上实现美化日志输出。 ![美化日志图片](https://static.pigsec.cn/wp-content/uploads/repos/cas/ab/ab55f01a6bf2912687852c5df6347e3df31761324d8ce01839311e9b995e584f.png) ## 谁在使用 zerolog 查看[谁在使用 zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog),并将您的公司/项目添加到列表中。 ## 功能特性 - [极快](#benchmarks) - [低至零分配](#benchmarks) - [分级日志](#leveled-logging) - [采样](#log-sampling) - [Hook](#hooks) - [上下文字段](#contextual-logging) - [`context.Context` 集成](#contextcontext-integration) - [与 `net/http` 集成](#integration-with-nethttp) - [JSON 和 CBOR 编码格式](#binary-encoding) - [用于开发的美化日志](#pretty-logging) - [错误日志(可选 Stacktrace)](#error-logging) - [`log/slog` 集成](#integration-with-logslog) ## 安装说明 ``` go get -u github.com/rs/zerolog/log ``` ## 快速开始 ### 简单日志记录示例 对于简单的日志记录,请导入全局 logger 包 **github.com/rs/zerolog/log** ``` package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { // UNIX Time is faster and smaller than most timestamps zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Print("hello world") } // Output: {"time":1516134303,"level":"debug","message":"hello world"} ``` ### 上下文日志 **zerolog** 允许以 key:value 键值对的形式向日志消息中添加数据。添加到消息中的数据增加了关于日志事件的“上下文”,这对于调试以及无数其他目的来说可能至关重要。以下是一个示例: ``` package main import ( "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() } // Output: {"level":"debug","Scale":"833 cents","Interval":833.09,"time":1562212768,"message":"Fibonacci is everywhere"} // Output: {"level":"debug","Name":"Tom","time":1562212768} ``` ### 分级日志 #### 简单的分级日志示例 ``` package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Info().Msg("hello world") } // Output: {"time":1516134303,"level":"info","message":"hello world"} ``` **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) 您可以使用 zerolog 包中的 `SetGlobalLevel` 函数将全局日志级别设置为这些选项中的任何一个,并传入上述给定的常量之一,例如 `zerolog.InfoLevel` 将是 "info" 级别。无论选择哪个级别,所有大于或等于该级别的日志都将被写入。要完全关闭日志记录,请传入 `zerolog.Disabled` 常量。 #### 设置全局日志级别 此示例使用命令行标志来演示根据所选日志级别的不同输出。 ``` package main import ( "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() // Default level for this example is info, unless debug flag is present 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() { // Compute log output only if enabled. value := "bar" e.Str("foo", value).Msg("some debug message") } } ``` Info 输出(无标志) ``` $ ./logLevelExample {"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"} ``` Debug 输出(设置了 debug 标志) ``` $ ./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"} ``` #### 不带级别或消息的日志记录 您可以选择使用 `Log` 方法在不指定特定级别的情况下记录日志。您也可以通过在 `Msg` 方法的 `msg string` 参数中设置空字符串,从而在不带消息的情况下写入。以下示例演示了这两种用法。 ``` package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Log(). Str("foo", "bar"). Msg("") } // Output: {"time":1494567715,"foo":"bar"} ``` ### 错误日志 您可以使用 `Err` 方法记录错误 ``` package main import ( "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("") } // Output: {"level":"error","error":"seems we have an error here","time":1609085256} ``` #### 带 Stacktrace 的错误日志 使用 `github.com/pkg/errors`,您可以为错误添加格式化的 stacktrace。 ``` package main import ( "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 } // Output: {"level":"error","stack":[{"func":"inner","line":"20","source":"errors.go"},{"func":"middle","line":"24","source":"errors.go"},{"func":"outer","line":"32","source":"errors.go"},{"func":"main","line":"15","source":"errors.go"},{"func":"main","line":"204","source":"proc.go"},{"func":"goexit","line":"1374","source":"asm_amd64.s"}],"error":"seems we have an error here","time":1609086683} ``` #### 记录 Fatal 消息 ``` package main import ( "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) } // Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} // exit status 1 ``` ### 创建 logger 实例以管理不同的输出 ``` logger := zerolog.New(os.Stderr).With().Timestamp().Logger() logger.Info().Str("foo", "bar").Msg("hello world") // Output: {"level":"info","time":1494567715,"message":"hello world","foo":"bar"} ``` ### 子 logger 允许您链式添加具有额外上下文的 logger ``` sublogger := log.With(). Str("component", "foo"). Logger() sublogger.Info().Msg("hello world") // Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"} ``` ### 美化日志 要记录对人类友好、带颜色的输出,请使用 `zerolog.ConsoleWriter`: ``` log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Info().Str("foo", "bar").Msg("Hello world") // Output: 3:04PM INF Hello World foo=bar ``` 自定义配置和格式: ``` 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") // Output: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World**** foo:BAR ``` 使用自定义高级格式: ``` output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: true, PartsOrder: []string{"level", "one", "two", "three", "message"}, FieldsExclude: []string{"one", "two", "three"}} output.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("%-6s", i)) } output.FormatFieldName = func(i interface{}) string { return fmt.Sprintf("%s:", i) } output.FormatPartValueByName = func(i interface{}, s string) string { var ret string switch s { case "one": ret = strings.ToUpper(fmt.Sprintf("%s", i)) case "two": ret = strings.ToLower(fmt.Sprintf("%s", i)) case "three": ret = strings.ToLower(fmt.Sprintf("(%s)", i)) } return ret } log := zerolog.New(output) log.Info().Str("foo", "bar"). Str("two", "TEST_TWO"). Str("one", "test_one"). Str("three", "test_three"). Msg("Hello World") // Output: INFO TEST_ONE test_two (test_three) Hello World foo:bar ``` ### 子字典 ``` log.Info(). Str("foo", "bar"). Dict("dict", zerolog.Dict(). Str("bar", "baz"). Int("n", 1), ).Msg("hello world") // Output: {"level":"info","time":1494567715,"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} ``` ### 自定义自动字段名 ``` zerolog.TimestampFieldName = "t" zerolog.LevelFieldName = "l" zerolog.MessageFieldName = "m" log.Info().Msg("hello world") // Output: {"l":"info","t":1494567715,"m":"hello world"} ``` ### 向全局 logger 添加上下文字段 ``` log.Logger = log.With().Str("foo", "bar").Logger() ``` ### 向日志中添加文件和行号 等同于 `Llongfile`: ``` log.Logger = log.With().Caller().Logger() log.Info().Msg("hello world") // Output: {"level": "info", "message": "hello world", "caller": "/go/src/your_project/some_file:21"} ``` 等同于 `Lshortfile`: ``` zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { return filepath.Base(file) + ":" + strconv.Itoa(line) } log.Logger = log.With().Caller().Logger() log.Info().Msg("hello world") // Output: {"level": "info", "message": "hello world", "caller": "some_file:21"} ``` ### 线程安全、无锁、非阻塞的 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") // Output: {"time":1494567715,"level":"info","message":"will be logged every 10 messages"} ``` 更高级的采样: ``` // Will let 5 debug messages per period of 1 second. // Over 5 debug message, 1 every 100 debug messages are logged. // Other levels are not sampled. sampled := log.Sample(zerolog.LevelSampler{ DebugSampler: &zerolog.BurstSampler{ Burst: 5, Period: 1*time.Second, NextSampler: &zerolog.BasicSampler{N: 100}, }, }) sampled.Debug().Msg("hello world") // Output: {"time":1494567715,"level":"debug","message":"hello world"} ``` ### Hook ``` type 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("") // Output: {"level":"warn","severity":"warn"} ``` ### 通过 context 传递子 logger ``` ctx := log.With().Str("component", "module").Logger().WithContext(ctx) log.Ctx(ctx).Info().Msg("hello world") // Output: {"component":"module","level":"info","message":"hello world"} ``` ### 设置为标准 logger 输出 ``` log := zerolog.New(os.Stdout).With(). Str("foo", "bar"). Logger() stdlog.SetFlags(0) stdlog.SetOutput(log) stdlog.Print("hello world") // Output: {"foo":"bar","message":"hello world"} ``` ### context.Context 集成 Go context 通常会在 Go 代码中传递,这可以帮助您将 Logger 传递到原本可能难以注入的地方。`Logger` 实例可以通过 `Logger.WithContext(ctx)` 附加到 Go context (`context.Context`) 上, 并通过 `zerolog.Ctx(ctx)` 从中提取。 例如: ``` func f() { logger := zerolog.New(os.Stdout) ctx := context.Background() // Attach the Logger to the context.Context ctx = logger.WithContext(ctx) someFunc(ctx) } func someFunc(ctx context.Context) { // Get Logger from the go Context. if it's nil, then // `zerolog.DefaultContextLogger` is returned, if // `DefaultContextLogger` is nil, then a disabled logger is returned. logger := zerolog.Ctx(ctx) logger.Info().Msg("Hello") } ``` 另一种 `context.Context` 集成形式允许您将当前的 `context.Context` 传入日志事件中,并从 hook 中检索它。这对于 记录 trace 和 span ID 或其他存储在 go context 中的信息非常有用, 并有助于在某些系统中统一日志记录和追踪: ``` type TracingHook struct{} func (h TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { ctx := e.GetCtx() spanId := getSpanIdFromContext(ctx) // as per your tracing framework e.Str("span-id", spanId) } func f() { // Setup the logger logger := zerolog.New(os.Stdout) logger = logger.Hook(TracingHook{}) ctx := context.Background() // Use the Ctx function to make the context available to the hook logger.Info().Ctx(ctx).Msg("Hello") } ``` ### 与 `net/http` 集成 `github.com/rs/zerolog/hlog` 包提供了一些 helper,可将 zerolog 与 `http.Handler` 集成。 在此示例中,我们使用 [alice](https://github.com/justinas/alice) 安装 logger 以提高可读性。 ``` log := zerolog.New(os.Stdout).With(). Timestamp(). Str("role", "my-service"). Str("host", host). Logger() c := alice.New() // Install the logger handler with default output on the console c = c.Append(hlog.NewHandler(log)) // Install some provided extra handler to set some request's context fields. // Thanks to that handler, all our logs will come with some prepopulated fields. c = c.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { hlog.FromRequest(r).Info(). Str("method", r.Method). Stringer("url", r.URL). Int("status", status). Int("size", size). Dur("duration", duration). Msg("") })) c = c.Append(hlog.RemoteAddrHandler("ip")) c = c.Append(hlog.UserAgentHandler("user_agent")) c = c.Append(hlog.RefererHandler("referer")) c = c.Append(hlog.RequestIDHandler("req_id", "Request-Id")) // Here is your final handler h := c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get the logger from the request's context. You can safely assume it // will be always there: if the handler is removed, hlog.FromRequest // will return a no-op logger. hlog.FromRequest(r).Info(). Str("user", "current user"). Str("status", "ok"). Msg("Something happened") // Output: {"level":"info","time":"2001-02-03T04:05:06Z","role":"my-service","host":"local-hostname","req_id":"b4g0l5t6tfid6dtrapu0","user":"current user","status":"ok","message":"Something happened"} })) http.Handle("/", h) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal().Err(err).Msg("Startup failed") } ``` ## 多重日志输出 `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!") } // Output (Line 1: Console; Line 2: Stdout) // 12:36PM INF Hello World! // {"level":"info","time":"2019-11-07T12:36:38+03:00","message":"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.Duration` 类型字段的单位(默认:`time.Millisecond`)。 - `zerolog.DurationFieldFormat`:可以设置为 `DurationFormatFloat`、`DurationFormatInt` 或 `DurationFormatString`(默认:`DurationFormatFloat`),分别通过追加为 `Float64`、`Int64` 或调用 `String()` 来格式化 `Duration`。 - `zerolog.DurationFieldInteger`:如果设置为 `true`,`Dur` 字段将被格式化为整数而不是浮点数(默认:`false`)。已弃用:请改用 `zerolog.DurationFieldFormat = DurationFormatInt`。 - `zerolog.ErrorHandler`:每当 zerolog 无法在其输出上写入事件时调用。如果未设置,则会在 stderr 上打印错误。此 handler 必须是线程安全且非阻塞的。 - `zerolog.FloatingPointPrecision`:如果设置为 -1 以外的值,则控制 JSON 中格式化浮点数时的位数。有关更多详细信息,请参见 [strconv.FormatFloat](https://pkg.go.dev/strconv#FormatFloat)。 ## 字段类型 ### 标准类型 - `Str` - `Bool` - `Int`, `Int8`, `Int16`, `Int32`, `Int64` - `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64` - `Float32`, `Float64` ### 高级字段 - `Err`:接受一个 `error` 并使用 `zerolog.ErrorFieldName` 字段名将其渲染为字符串。 - `Func`:仅在级别启用时才运行 `func`。 - `Timestamp`:插入一个具有 `zerolog.TimestampFieldName` 字段名的时间戳字段,并使用 `zerolog.TimeFieldFormat` 进行格式化。 - `Time`:添加一个使用 `zerolog.TimeFieldFormat` 格式化的时间字段。 - `Dur`:添加一个具有 `time.Duration` 的字段。 - `Dict`:将子键/值作为事件的一个字段添加。 - `RawJSON`:添加一个包含已编码 JSON (`[]byte`) 的字段 - `Hex`:添加一个值被格式化为十六进制字符串 (`[]byte`) 的字段 - `Interface`:使用反射来序列化该类型。 - `IPAddr`:添加一个包含 `net.IP` 的字段。 - `IPPrefix`:添加一个包含 `net.IPNet` 的字段。 - `MACAddr`:添加一个包含 `net.HardwareAddr` 的字段 大多数字段也有切片格式可用(例如用于 `[]string` 的 `Strs`、用于 `[]error` 的 `Errs` 等)。 ## 二进制编码 除了默认的 JSON 编码外,`zerolog` 还可以使用 [CBOR](https://cbor.io) 编码生成二进制日志。可以在编译时使用构建标签 `binary_log` 来决定编码选择,如下所示: ``` go build -tags binary_log . ``` 要解码二进制编码的日志文件,您可以使用任何 CBOR 解码器。其中已被测试可与 zerolog 库一起使用的是 [CSD](https://github.com/toravir/csd/)。 ## 与 `log/slog` 集成 zerolog 提供了一个 `slog.Handler` 实现,通过 zerolog logger 路由 `log/slog` 记录。这允许您使用标准库的 `slog` API,同时保留 zerolog 的性能和编码: ``` package main import ( "log/slog" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) func main() { zl := log.Logger handler := zerolog.NewSlogHandler(zl) logger := slog.New(handler) logger.Info("user logged in", "user", "alice", "role", "admin") } // Output: {"level":"info","user":"alice","role":"admin","time":"...","message":"user logged in"} ``` 该 handler 支持所有 `slog` 特性,包括 `WithAttrs`、`WithGroup`、嵌套组和 `LogValuer` 解析。slog 级别映射到 zerolog 级别(例如 `slog.LevelDebug` 映射到 `zerolog.DebugLevel`)。 ## 相关项目 - [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog):使用 `zerolog` 实现 `grpclog.LoggerV2` 接口 - [overlog](https://github.com/Trendyol/overlog):使用 `zerolog` 实现 `Mapped Diagnostic Context` 接口 - [zerologr](https://github.com/go-logr/zerologr):使用 `zerolog` 实现 `logr.LogSink` 接口 - [logze](https://github.com/maxbolgarin/logze):使用 `zerolog` 实现 `log/slog` 接口 ## 性能基准测试 有关更全面和最新的基准测试,请参见 [logbench](http://bench.zerolog.io/)。 所有操作都是零分配的(这些数字_包含_ JSON 编码): ``` BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op ``` 有一些包含 zerolog 的 Go 日志基准测试和比较。 - [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) - [uber-common/zap](https://github.com/uber-go/zap#performance) 使用 Uber 的 zap 比较基准测试: 记录一条消息和 10 个字段: | Library | Time | Bytes Allocated | Objects Allocated | | :------------------ | :---------: | :-------------: | :---------------: | | zerolog | 767 ns/op | 552 B/op | 6 allocs/op | | :zap: zap | 848 ns/op | 704 B/op | 2 allocs/op | | :zap: zapsugared) | 1363 ns/op | 1610 B/op | 20 allocs/op | | go-kit | 3614 ns/op | 2895 B/op | 66 allocs/op | | lion | 5392 ns/op | 5807 B/op | 63 allocs/op | | logrus | 5661 ns/op | 6092 B/op | 78 allocs/op | | apex/log | 15332 ns/op | 3832 B/op | 65 allocs/op | | log15 | 20657 ns/op | 5632 B/op | 93 allocs/op | 使用一个已经具有 10 个上下文字段的 logger 记录一条消息: | Library | Time | Bytes Allocated | Objects Allocated | | :------------------ | :---------: | :-------------: | :---------------: | | zerolog | 52 ns/op | 0 B/op | 0 allocs/op | | :zap: zap | 283 ns/op | 0 B/op | 0 allocs/op | | :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | | lion | 2702 ns/op | 4074 B/op | 38 allocs/op | | go-kit | 3378 ns/op | 3046 B/op | 52 allocs/op | | logrus | 4309 ns/op | 4564 B/op | 63 allocs/op | | apex/log | 13456 ns/op | 2898 B/op | 51 allocs/op | | log15 | 14179 ns/op | 2642 B/op | 44 allocs/op | 记录一个静态字符串,不带任何上下文或 `printf` 风格的模板: | Library | Time | Bytes Allocated | Objects Allocated | | :------------------ | :--------: | :-------------: | :---------------: | | zerolog | 50 ns/op | 0 B/op | 0 allocs/op | | :zap: zap | 236 ns/op | 0 B/op | 0 allocs/op | | standard library | 453 ns/op | 80 B/op | 2 allocs/op | | :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | | go-kit | 508 ns/op | 656 B/op | 13 allocs/op | | lion | 771 ns/op | 1224 B/op | 10 allocs/op | | logrus | 1244 ns/op | 1505 B/op | 27 allocs/op | | apex/log | 2751 ns/op | 584 B/op | 11 allocs/op | | log15 | 5181 ns/op | 1592 B/op | 26 allocs/op | ## 注意事项 ### 字段重复 请注意,zerolog 不会对字段进行去重。多次使用相同的键会在最终的 JSON 中创建多个键: ``` logger := zerolog.New(os.Stderr).With().Timestamp().Logger() logger.Info(). Timestamp(). Msg("dup") // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} ``` 在这种情况下,许多消费者会取最后一个值,但这并不能保证;如果有疑问,请检查您的消费者逻辑。 ### 并发安全 在调用 `UpdateContext` 时要小心。它不是并发安全的。请使用 `With()` 方法创建一个子 logger: ``` func handler(w http.ResponseWriter, r *http.Request) { // Create a child logger for concurrency safety logger := log.Logger.With().Logger() // Add context fields, for example User-Agent from HTTP headers logger.UpdateContext(func(c zerolog.Context) zerolog.Context { ... }) } ``` 从 `Logger` 特定级别消息函数(例如 `Log()`、`Trace()`、`Debug()` 等)返回的 `Event` 对象 分配在 `sync.Pool` 内存中,一旦 `Msg()`、`Msgf()`、`Send()` 或 `MsgFunc()` 写入消息后,该内存将立即被回收到池中,**绝不能**在之后被访问。 **不要**在回调函数或您自己的代码中持有对 `*Event` 的引用。这在 `Hook.Run()` 和 `HookFunc` 函数或 `MarshalZerologObject(e *Event)` 回调(例如 `LogObjectMarshaler` 实现)中尤其重要。 任何从 `Context.CreateArray()` 或 `Event.CreateArray()` 返回的 `Array` 对象都来自 `sync.Pool`,因此**不要**在 任何 `MarshalZerologArray(a *Array)` 回调(例如 `LogArrayMarshaler` 实现)或您自己的代码中持有对它们的引用,因为它们在通过调用 `Context.Array()` 或 `Event.Array()` 缓冲后,将被清空并回收到池中。 任何从 `Context.CreateDict()` 或 `Event.CreateDict()` 返回的_字典_ `Event` 在通过调用 `Array.Dict()`、`Context.Dict()` 或 `Event.Dict()` 缓冲后,**绝不能**再被引用,因为它们将被清空并回收到池中。
标签:EVTX分析, Go, Homebrew安装, JSON, Ruby工具, SOC Prime, 开发工具, 日志审计, 日志库, 结构化日志, 零内存分配