rs/zerolog
GitHub: rs/zerolog
Go 语言的高性能零分配结构化 JSON 日志库,通过链式 API 和避免反射实现极致的日志写入性能。
Stars: 12465 | Forks: 628
# 零分配 JSON Logger
[](https://godoc.org/github.com/rs/zerolog) [](https://raw.githubusercontent.com/rs/zerolog/master/LICENSE) [](https://github.com/rs/zerolog/actions/workflows/test.yml) [](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),可以在控制台上实现美化日志输出。

## 谁在使用 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, 开发工具, 日志审计, 日志库, 结构化日志, 零内存分配