chore: bump reva to latest main

This commit is contained in:
Ralf Haferkamp
2026-04-08 11:45:37 +02:00
committed by Ralf Haferkamp
parent 4c86d2a289
commit b8c4f581fb
139 changed files with 6620 additions and 3045 deletions
+2
View File
@@ -23,3 +23,5 @@ _testmain.go
*.exe
*.test
*.prof
coverage.out
+175 -130
View File
@@ -18,17 +18,18 @@ Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog)
## Features
* [Blazing fast](#benchmarks)
* [Low to zero allocation](#benchmarks)
* [Leveled logging](#leveled-logging)
* [Sampling](#log-sampling)
* [Hooks](#hooks)
* [Contextual fields](#contextual-logging)
* [`context.Context` integration](#contextcontext-integration)
* [Integration with `net/http`](#integration-with-nethttp)
* [JSON and CBOR encoding formats](#binary-encoding)
* [Pretty logging for development](#pretty-logging)
* [Error Logging (with optional Stacktrace)](#error-logging)
- [Blazing fast](#benchmarks)
- [Low to zero allocation](#benchmarks)
- [Leveled logging](#leveled-logging)
- [Sampling](#log-sampling)
- [Hooks](#hooks)
- [Contextual fields](#contextual-logging)
- [`context.Context` integration](#contextcontext-integration)
- [Integration with `net/http`](#integration-with-nethttp)
- [JSON and CBOR encoding formats](#binary-encoding)
- [Pretty logging for development](#pretty-logging)
- [Error Logging (with optional Stacktrace)](#error-logging)
- [`log/slog` integration](#integration-with-logslog)
## Installation
@@ -59,8 +60,9 @@ func main() {
// Output: {"time":1516134303,"level":"debug","message":"hello world"}
```
> Note: By default log writes to `os.Stderr`
> Note: The default log level for `log.Print` is *trace*
> Note: The default log level for `log.Print` is _trace_
### Contextual Logging
@@ -81,7 +83,7 @@ func main() {
Str("Scale", "833 cents").
Float64("Interval", 833.09).
Msg("Fibonacci is everywhere")
log.Debug().
Str("Name", "Tom").
Send()
@@ -118,15 +120,15 @@ func main() {
**zerolog** allows for logging at the following levels (from highest to lowest):
* 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)
- 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)
You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant.
You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant.
#### Setting Global Log Level
@@ -212,17 +214,17 @@ You can log errors using the `Err` method
package main
import (
"errors"
"errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
err := errors.New("seems we have an error here")
log.Error().Err(err).Msg("")
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}
@@ -232,45 +234,45 @@ func main() {
#### Error Logging with Stacktrace
Using `github.com/pkg/errors`, you can add a formatted stacktrace to your errors.
Using `github.com/pkg/errors`, you can add a formatted stacktrace to your errors.
```go
package main
import (
"github.com/pkg/errors"
"github.com/rs/zerolog/pkgerrors"
"github.com/pkg/errors"
"github.com/rs/zerolog/pkgerrors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
err := outer()
log.Error().Stack().Err(err).Msg("")
err := outer()
log.Error().Stack().Err(err).Msg("")
}
func inner() error {
return errors.New("seems we have an error here")
return errors.New("seems we have an error here")
}
func middle() error {
err := inner()
if err != nil {
return err
}
return nil
err := inner()
if err != nil {
return err
}
return nil
}
func outer() error {
err := middle()
if err != nil {
return err
}
return nil
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}
@@ -308,7 +310,6 @@ func main() {
> NOTE: Using `Msgf` generates one allocation even when the logger is disabled.
### Create logger instance to manage different outputs
```go
@@ -393,7 +394,7 @@ log.Info().Str("foo", "bar").
Str("one", "test_one").
Str("three", "test_three").
Msg("Hello World")
// Output: INFO TEST_ONE test_two (test_three) Hello World foo:bar
```
@@ -457,8 +458,8 @@ If your writer might be slow or not thread-safe and you need your log producers
```go
wr := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) {
fmt.Printf("Logger Dropped %d messages", missed)
})
fmt.Printf("Logger Dropped %d messages", missed)
})
log := zerolog.New(wr)
log.Print("test")
```
@@ -537,7 +538,7 @@ stdlog.Print("hello world")
### context.Context integration
Go contexts are commonly passed throughout Go code, and this can help you pass
your Logger into places it might otherwise be hard to inject. The `Logger`
your Logger into places it might otherwise be hard to inject. The `Logger`
instance may be attached to Go context (`context.Context`) using
`Logger.WithContext(ctx)` and extracted from it using `zerolog.Ctx(ctx)`.
For example:
@@ -562,7 +563,7 @@ func someFunc(ctx context.Context) {
```
A second form of `context.Context` integration allows you to pass the current
context.Context into the logged event, and retrieve it from hooks. This can be
`context.Context` into the logged event, and retrieve it from hooks. This can be
useful to log trace and span IDs or other information stored in the go context,
and facilitates the unification of logging and tracing in some systems:
@@ -640,17 +641,17 @@ if err := http.ListenAndServe(":8080", nil); err != nil {
```
## Multiple Log Output
`zerolog.MultiLevelWriter` may be used to send the log message to multiple outputs.
In this example, we send the log message to both `os.Stdout` and the in-built ConsoleWriter.
`zerolog.MultiLevelWriter` may be used to send the log message to multiple outputs.
In this example, we send the log message to both `os.Stdout` and the in-built `ConsoleWriter`.
```go
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!")
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)
@@ -662,43 +663,45 @@ func main() {
Some settings can be changed and will be applied to all loggers:
* `log.Logger`: You can set this value to customize the global logger (the one used by package level methods).
* `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Call this with `zerolog.Disabled` to disable logging altogether (quiet mode).
* `zerolog.DisableSampling`: If argument is `true`, all sampled loggers will stop sampling and issue 100% of their log events.
* `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name.
* `zerolog.LevelFieldName`: Can be set to customize level field name.
* `zerolog.MessageFieldName`: Can be set to customize message field name.
* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name.
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formatted as UNIX timestamp.
* `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`).
* `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`).
* `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
* `zerolog.FloatingPointPrecision`: If set to a value other than -1, controls the number
of digits when formatting float numbers in JSON. See
[strconv.FormatFloat](https://pkg.go.dev/strconv#FormatFloat)
for more details.
- `log.Logger`: You can set this value to customize the global logger (the one used by package level methods).
- `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Call this with `zerolog.Disabled` to disable logging altogether (quiet mode).
- `zerolog.DisableSampling`: If argument is `true`, all sampled loggers will stop sampling and issue 100% of their log events.
- `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name.
- `zerolog.LevelFieldName`: Can be set to customize level field name.
- `zerolog.MessageFieldName`: Can be set to customize message field name.
- `zerolog.ErrorFieldName`: Can be set to customize `Err` field name.
- `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formatted as UNIX timestamp.
- `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`).
- `zerolog.DurationFieldFormat`: Can be set to `DurationFormatFloat`, `DurationFormatInt`, or `DurationFormatString` (default: `DurationFormatFloat`) to append the `Duration` as a `Float64`, `Int64`, or by calling `String()` (respectively).
- `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`). Deprecated: Use `zerolog.DurationFieldFormat = DurationFormatInt` instead.
- `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking.
- `zerolog.FloatingPointPrecision`: If set to a value other than -1, controls the number of digits when formatting float numbers in JSON. See [strconv.FormatFloat](https://pkg.go.dev/strconv#FormatFloat)
for more details.
## Field Types
### Standard Types
* `Str`
* `Bool`
* `Int`, `Int8`, `Int16`, `Int32`, `Int64`
* `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`
* `Float32`, `Float64`
- `Str`
- `Bool`
- `Int`, `Int8`, `Int16`, `Int32`, `Int64`
- `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`
- `Float32`, `Float64`
### Advanced Fields
* `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name.
* `Func`: Run a `func` only if the level is enabled.
* `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`.
* `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`.
* `Dur`: Adds a field with `time.Duration`.
* `Dict`: Adds a sub-key/value as a field of the event.
* `RawJSON`: Adds a field with an already encoded JSON (`[]byte`)
* `Hex`: Adds a field with value formatted as a hexadecimal string (`[]byte`)
* `Interface`: Uses reflection to marshal the type.
- `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name.
- `Func`: Run a `func` only if the level is enabled.
- `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`.
- `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`.
- `Dur`: Adds a field with `time.Duration`.
- `Dict`: Adds a sub-key/value as a field of the event.
- `RawJSON`: Adds a field with an already encoded JSON (`[]byte`)
- `Hex`: Adds a field with value formatted as a hexadecimal string (`[]byte`)
- `Interface`: Uses reflection to marshal the type.
- `IPAddr`: Adds a field with `net.IP`.
- `IPPrefix`: Adds a field with `net.IPNet`.
- `MACAddr`: Adds a field with `net.HardwareAddr`
Most fields are also available in the slice format (`Strs` for `[]string`, `Errs` for `[]error` etc.)
@@ -710,20 +713,48 @@ In addition to the default JSON encoding, `zerolog` can produce binary logs usin
go build -tags binary_log .
```
To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work
To decode binary encoded log files you can use any CBOR decoder. One has been tested to work
with zerolog library is [CSD](https://github.com/toravir/csd/).
## Integration with `log/slog`
zerolog provides a `slog.Handler` implementation that routes `log/slog` records through a zerolog logger. This lets you use the standard library's `slog` API while keeping zerolog's performance and encoding:
```go
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"}
```
The handler supports all `slog` features including `WithAttrs`, `WithGroup`, nested groups, and `LogValuer` resolution. slog levels are mapped to zerolog levels (e.g. `slog.LevelDebug` to `zerolog.DebugLevel`).
## Related Projects
* [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog`
* [overlog](https://github.com/Trendyol/overlog): Implementation of `Mapped Diagnostic Context` interface using `zerolog`
* [zerologr](https://github.com/go-logr/zerologr): Implementation of `logr.LogSink` interface using `zerolog`
- [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog`
- [overlog](https://github.com/Trendyol/overlog): Implementation of `Mapped Diagnostic Context` interface using `zerolog`
- [zerologr](https://github.com/go-logr/zerologr): Implementation of `logr.LogSink` interface using `zerolog`
- [logze](https://github.com/maxbolgarin/logze): Implementation of `log/slog` interface using `zerolog`
## Benchmarks
See [logbench](http://bench.zerolog.io/) for more comprehensive and up-to-date benchmarks.
All operations are allocation free (those numbers *include* JSON encoding):
All operations are allocation free (those numbers _include_ JSON encoding):
```text
BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op
@@ -735,50 +766,50 @@ BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op
There are a few Go logging benchmarks and comparisons that include zerolog.
* [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench)
* [uber-common/zap](https://github.com/uber-go/zap#performance)
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench)
- [uber-common/zap](https://github.com/uber-go/zap#performance)
Using Uber's zap comparison benchmark:
Log a message and 10 fields:
| 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: zap (sugared) | 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 |
| 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: zap (sugared) | 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 |
Log a message with a logger that already has 10 fields of context:
| 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 |
| 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 |
Log a static string, without any context or `printf`-style templating:
| 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 |
| 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 |
## Caveats
@@ -798,7 +829,7 @@ In this case, many consumers will take the last value, but this is not guarantee
### Concurrency safety
Be careful when calling UpdateContext. It is not concurrency safe. Use the With method to create a child logger:
Be careful when calling `UpdateContext`. It is not concurrency safe. Use the `With()` method to create a child logger:
```go
func handler(w http.ResponseWriter, r *http.Request) {
@@ -811,3 +842,17 @@ func handler(w http.ResponseWriter, r *http.Request) {
})
}
```
The `Event` object returned from the `Logger` level-specific message functions (e.g. `Log()`, `Trace()`, `Debug()`, etc.)
is allocated in `sync.Pool` memory that will be returned to the pool as soon as the `Msg()`, `Msgf()`, `Send()`,
or `MsgFunc()` writes the message and **must not** be accessed afterwards.
**Do not** hold a reference to the `*Event` while in callback functions or your own code. This is especially important in
`Hook.Run()` and `HookFunc` functions or `MarshalZerologObject(e *Event)` callback (e.g. `LogObjectMarshaler` implementations).
Any `Array` objects returned from `Context.CreateArray()` or `Event.CreateArray()` are from a `sync.Pool` so **do not** hold
references to them from within any `MarshalZerologArray(a *Array)` callback (e.g. `LogArrayMarshaler` implementations) or your
own code as they will be cleared and returned to the pool after being buffered by a call to `Context.Array()` or `Event.Array()`.
Any _dictionary_ `Event` returned from `Context.CreateDict()` or `Event.CreateDict()` **must not** be referenced after being
buffered by a call to `Array.Dict()`, `Context.Dict()`, or `Event.Dict()` as they will be cleared and returned to the pool.
+57 -21
View File
@@ -1,6 +1,7 @@
package zerolog
import (
"context"
"net"
"sync"
"time"
@@ -17,10 +18,19 @@ var arrayPool = &sync.Pool{
// Array is used to prepopulate an array of items
// which can be re-used to add to log messages.
type Array struct {
buf []byte
buf []byte
stack bool // enable error stack trace
ctx context.Context // Optional Go context
ch []Hook // hooks
}
func putArray(a *Array) {
// prevent any subsequent use of the Array contextual state and truncate the buffer
a.stack = false
a.ctx = nil
a.ch = nil
a.buf = a.buf[:0]
// Proper usage of a sync.Pool requires each entry to have approximately
// the same memory cost. To obtain this property when the stored type
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
@@ -28,22 +38,28 @@ func putArray(a *Array) {
//
// See https://golang.org/issue/23199
const maxSize = 1 << 16 // 64KiB
if cap(a.buf) > maxSize {
return
if cap(a.buf) <= maxSize {
arrayPool.Put(a)
}
arrayPool.Put(a)
}
// Arr creates an array to be added to an Event or Context.
// WARNING: This function is deprecated because it does not preserve
// the stack, hooks, and context from the parent event.
// Deprecated: Use Event.CreateArray or Context.CreateArray instead.
func Arr() *Array {
a := arrayPool.Get().(*Array)
a.buf = a.buf[:0]
a.stack = false
a.ctx = nil
a.ch = nil
return a
}
// MarshalZerologArray method here is no-op - since data is
// already in the needed format.
func (*Array) MarshalZerologArray(*Array) {
// untestable: there's no code to be covered
}
func (a *Array) write(dst []byte) []byte {
@@ -59,11 +75,7 @@ func (a *Array) write(dst []byte) []byte {
// Object marshals an object that implement the LogObjectMarshaler
// interface and appends it to the array.
func (a *Array) Object(obj LogObjectMarshaler) *Array {
e := Dict()
obj.MarshalZerologObject(e)
e.buf = enc.AppendEndMarker(e.buf)
a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...)
putEvent(e)
a.buf = appendObject(enc.AppendArrayDelim(a.buf), obj, a.stack, a.ctx, a.ch)
return a
}
@@ -94,16 +106,12 @@ func (a *Array) RawJSON(val []byte) *Array {
// Err serializes and appends the err to the array.
func (a *Array) Err(err error) *Array {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf))
case LogObjectMarshaler:
e := newEvent(nil, 0)
e.buf = e.buf[:0]
e.appendObject(m)
a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...)
putEvent(e)
a = a.Object(m)
case error:
if m == nil || isNilValue(m) {
a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf))
} else {
if !isNilValue(m) {
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error())
}
case string:
@@ -115,6 +123,27 @@ func (a *Array) Err(err error) *Array {
return a
}
// Errs serializes and appends errors to the array.
func (a *Array) Errs(errs []error) *Array {
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
a = a.Interface(nil)
case LogObjectMarshaler:
a = a.Object(m)
case error:
if !isNilValue(m) {
a = a.Str(m.Error())
}
case string:
a = a.Str(m)
default:
a = a.Interface(m)
}
}
return a
}
// Bool appends the val as a bool to the array.
func (a *Array) Bool(b bool) *Array {
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b)
@@ -201,7 +230,7 @@ func (a *Array) Time(t time.Time) *Array {
// Dur appends d to the array.
func (a *Array) Dur(d time.Duration) *Array {
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return a
}
@@ -214,19 +243,19 @@ func (a *Array) Interface(i interface{}) *Array {
return a
}
// IPAddr adds IPv4 or IPv6 address to the array
// IPAddr adds a net.IP IPv4 or IPv6 address to the array
func (a *Array) IPAddr(ip net.IP) *Array {
a.buf = enc.AppendIPAddr(enc.AppendArrayDelim(a.buf), ip)
return a
}
// IPPrefix adds IPv4 or IPv6 Prefix (IP + mask) to the array
// IPPrefix adds a net.IPNet IPv4 or IPv6 Prefix (IP + mask) to the array
func (a *Array) IPPrefix(pfx net.IPNet) *Array {
a.buf = enc.AppendIPPrefix(enc.AppendArrayDelim(a.buf), pfx)
return a
}
// MACAddr adds a MAC (Ethernet) address to the array
// MACAddr adds a net.HardwareAddr MAC (Ethernet) address to the array
func (a *Array) MACAddr(ha net.HardwareAddr) *Array {
a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha)
return a
@@ -236,5 +265,12 @@ func (a *Array) MACAddr(ha net.HardwareAddr) *Array {
func (a *Array) Dict(dict *Event) *Array {
dict.buf = enc.AppendEndMarker(dict.buf)
a.buf = append(enc.AppendArrayDelim(a.buf), dict.buf...)
putEvent(dict)
return a
}
// Type adds the val's type using reflection to the array.
func (a *Array) Type(val interface{}) *Array {
a.buf = enc.AppendType(enc.AppendArrayDelim(a.buf), val)
return a
}
+3 -3
View File
@@ -101,9 +101,9 @@ type ConsoleWriter struct {
// NewConsoleWriter creates and initializes a new ConsoleWriter.
func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
w := ConsoleWriter{
Out: os.Stdout,
TimeFormat: consoleDefaultTimeFormat,
PartsOrder: consoleDefaultPartsOrder(),
Out: os.Stdout,
TimeFormat: consoleDefaultTimeFormat,
PartsOrder: consoleDefaultPartsOrder(),
}
for _, opt := range options {
+98 -43
View File
@@ -3,7 +3,6 @@ package zerolog
import (
"context"
"fmt"
"io"
"math"
"net"
"time"
@@ -23,7 +22,7 @@ func (c Context) Logger() Logger {
// Only map[string]interface{} and []interface{} are accepted. []interface{} must
// alternate string keys and arbitrary values, and extraneous ones are ignored.
func (c Context) Fields(fields interface{}) Context {
c.l.context = appendFields(c.l.context, fields, c.l.stack)
c.l.context = appendFields(c.l.context, fields, c.l.stack, c.l.ctx, c.l.hooks)
return c
}
@@ -35,8 +34,28 @@ func (c Context) Dict(key string, dict *Event) Context {
return c
}
// CreateDict creates an Event to be used with the Context.Dict method.
// It preserves the stack, hooks, and context from the logger.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the Context.Dict method.
func (c Context) CreateDict() *Event {
return newEvent(nil, DebugLevel, c.l.stack, c.l.ctx, c.l.hooks)
}
// CreateArray creates an Array to be used with the Context.Array method.
// It preserves the stack, hooks, and context from the logger.
// Call usual field methods like Str, Int etc to add elements to this
// array and give it as argument the Context.Array method.
func (c Context) CreateArray() *Array {
a := Arr()
a.stack = c.l.stack
a.ctx = c.l.ctx
a.ch = c.l.hooks
return a
}
// Array adds the field key with an array to the event context.
// Use zerolog.Arr() to create the array or pass a type that
// Use c.CreateArray() to create the array or pass a type that
// implement the LogArrayMarshaler interface.
func (c Context) Array(key string, arr LogArrayMarshaler) Context {
c.l.context = enc.AppendKey(c.l.context, key)
@@ -44,29 +63,44 @@ func (c Context) Array(key string, arr LogArrayMarshaler) Context {
c.l.context = arr.write(c.l.context)
return c
}
var a *Array
if aa, ok := arr.(*Array); ok {
a = aa
} else {
a = Arr()
arr.MarshalZerologArray(a)
}
a := c.CreateArray()
arr.MarshalZerologArray(a)
c.l.context = a.write(c.l.context)
return c
}
// Object marshals an object that implement the LogObjectMarshaler interface.
func (c Context) Object(key string, obj LogObjectMarshaler) Context {
e := newEvent(LevelWriterAdapter{io.Discard}, 0)
e := c.l.scratchEvent()
e.Object(key, obj)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
return c
}
// Objects adds the field key with objs to the logger context as an array of
// objects that implement the LogObjectMarshaler interface.
//
// This is the array version that accepts a slice of LogObjectMarshaler objects.
func (c Context) Objects(key string, objs []LogObjectMarshaler) Context {
e := c.l.scratchEvent()
e.Objects(key, objs)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
return c
}
// ObjectsV adds the field key with objs to the logger context as an array of
// objects that implement the LogObjectMarshaler interface.
//
// This is a variadic version that accepts a list of individual LogObjectMarshaler objects.
func (c Context) ObjectsV(key string, objs ...LogObjectMarshaler) Context {
return c.Objects(key, objs)
}
// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface.
func (c Context) EmbedObject(obj LogObjectMarshaler) Context {
e := newEvent(LevelWriterAdapter{io.Discard}, 0)
e := c.l.scratchEvent()
e.EmbedObject(obj)
c.l.context = enc.AppendObjectData(c.l.context, e.buf)
putEvent(e)
@@ -80,11 +114,20 @@ func (c Context) Str(key, val string) Context {
}
// Strs adds the field key with val as a string to the logger context.
//
// This is the array version that accepts a slice of string values.
func (c Context) Strs(key string, vals []string) Context {
c.l.context = enc.AppendStrings(enc.AppendKey(c.l.context, key), vals)
return c
}
// StrsV adds the field key with vals as a []string to the logger context.
//
// This is a variadic version that accepts a list of individual strings.
func (c Context) StrsV(key string, vals ...string) Context {
return c.Strs(key, vals)
}
// Stringer adds the field key with val.String() (or null if val is nil) to the logger context.
func (c Context) Stringer(key string, val fmt.Stringer) Context {
if val != nil {
@@ -96,6 +139,24 @@ func (c Context) Stringer(key string, val fmt.Stringer) Context {
return c
}
// Stringers adds the field key with vals to the logger context where each
// individual val is added by calling val.String().
//
// This is the array version that accepts a slice of fmt.Stringer values.
func (c Context) Stringers(key string, vals []fmt.Stringer) Context {
c.l.context = enc.AppendStringers(enc.AppendKey(c.l.context, key), vals)
return c
}
// StringersV adds the field key with vals to the logger context where each
// individual val is added by calling val.String().
//
// This is a variadic version that accepts a list of individual
// fmt.Stringer values.
func (c Context) StringersV(key string, vals ...fmt.Stringer) Context {
return c.Stringers(key, vals)
}
// Bytes adds the field key with val as a []byte to the logger context.
func (c Context) Bytes(key string, val []byte) Context {
c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val)
@@ -118,6 +179,7 @@ func (c Context) RawJSON(key string, b []byte) Context {
}
// AnErr adds the field key with serialized err to the logger context.
// If err is nil, no field is added.
func (c Context) AnErr(key string, err error) Context {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
@@ -125,11 +187,10 @@ func (c Context) AnErr(key string, err error) Context {
case LogObjectMarshaler:
return c.Object(key, m)
case error:
if m == nil || isNilValue(m) {
if isNilValue(m) {
return c
} else {
return c.Str(key, m.Error())
}
return c.Str(key, m.Error())
case string:
return c.Str(key, m)
default:
@@ -140,24 +201,7 @@ func (c Context) AnErr(key string, err error) Context {
// Errs adds the field key with errs as an array of serialized errors to the
// logger context.
func (c Context) Errs(key string, errs []error) Context {
arr := Arr()
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case LogObjectMarshaler:
arr = arr.Object(m)
case error:
if m == nil || isNilValue(m) {
arr = arr.Interface(nil)
} else {
arr = arr.Str(m.Error())
}
case string:
arr = arr.Str(m)
default:
arr = arr.Interface(m)
}
}
arr := c.CreateArray().Errs(errs)
return c.Array(key, arr)
}
@@ -166,12 +210,11 @@ func (c Context) Err(err error) Context {
if c.l.stack && ErrorStackMarshaler != nil {
switch m := ErrorStackMarshaler(err).(type) {
case nil:
return c // do nothing with nil errors
case LogObjectMarshaler:
c = c.Object(ErrorStackFieldName, m)
case error:
if m != nil && !isNilValue(m) {
c = c.Str(ErrorStackFieldName, m.Error())
}
c = c.Str(ErrorStackFieldName, m.Error())
case string:
c = c.Str(ErrorStackFieldName, m)
default:
@@ -377,15 +420,15 @@ func (c Context) Times(key string, t []time.Time) Context {
return c
}
// Dur adds the fields key with d divided by unit and stored as a float.
// Dur adds the field key with d divided by unit and stored as a float.
func (c Context) Dur(key string, d time.Duration) Context {
c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return c
}
// Durs adds the fields key with d divided by unit and stored as a float.
// Durs adds the field key with d divided by unit and stored as a float.
func (c Context) Durs(key string, d []time.Duration) Context {
c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return c
}
@@ -461,19 +504,31 @@ func (c Context) Stack() Context {
return c
}
// IPAddr adds IPv4 or IPv6 Address to the context
// IPAddr adds adds the field key with ip as a net.IP IPv4 or IPv6 Address to the context
func (c Context) IPAddr(key string, ip net.IP) Context {
c.l.context = enc.AppendIPAddr(enc.AppendKey(c.l.context, key), ip)
return c
}
// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the context
// IPAddrs adds the field key with ip as a []net.IP array of IPv4 or IPv6 Address to the context
func (c Context) IPAddrs(key string, ip []net.IP) Context {
c.l.context = enc.AppendIPAddrs(enc.AppendKey(c.l.context, key), ip)
return c
}
// IPPrefix adds adds the field key with pfx as a []net.IPNet IPv4 or IPv6 Prefix (address and mask) to the context
func (c Context) IPPrefix(key string, pfx net.IPNet) Context {
c.l.context = enc.AppendIPPrefix(enc.AppendKey(c.l.context, key), pfx)
return c
}
// MACAddr adds MAC address to the context
// IPPrefix adds adds the field key with pfx as a []net.IPNet array of IPv4 or IPv6 Prefix (address and mask) to the context
func (c Context) IPPrefixes(key string, pfx []net.IPNet) Context {
c.l.context = enc.AppendIPPrefixes(enc.AppendKey(c.l.context, key), pfx)
return c
}
// MACAddr adds adds the field key with ha as a net.HardwareAddr MAC address to the context
func (c Context) MACAddr(key string, ha net.HardwareAddr) Context {
c.l.context = enc.AppendMACAddr(enc.AppendKey(c.l.context, key), ha)
return c
+5 -6
View File
@@ -25,12 +25,11 @@ type ctxKey struct{}
// replacing it in a new Context), use UpdateContext with the following
// notation:
//
// ctx := r.Context()
// l := zerolog.Ctx(ctx)
// l.UpdateContext(func(c Context) Context {
// return c.Str("bar", "baz")
// })
//
// ctx := r.Context()
// l := zerolog.Ctx(ctx)
// l.UpdateContext(func(c Context) Context {
// return c.Str("bar", "baz")
// })
func (l Logger) WithContext(ctx context.Context) context.Context {
if _, ok := ctx.Value(ctxKey{}).(*Logger); !ok && l.level == Disabled {
// Do not store disabled logger.
+2 -2
View File
@@ -13,8 +13,8 @@ type encoder interface {
AppendBool(dst []byte, val bool) []byte
AppendBools(dst []byte, vals []bool) []byte
AppendBytes(dst, s []byte) []byte
AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte
AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte
AppendDuration(dst []byte, d time.Duration, unit time.Duration, format string, useInt bool, precision int) []byte
AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, format string, useInt bool, precision int) []byte
AppendEndMarker(dst []byte) []byte
AppendFloat32(dst []byte, val float32, precision int) []byte
AppendFloat64(dst []byte, val float64, precision int) []byte
+143 -55
View File
@@ -32,6 +32,15 @@ type Event struct {
}
func putEvent(e *Event) {
// prevent any subsequent use of the Event contextual state and truncate the buffer
e.w = nil
e.done = nil
e.stack = false
e.ch = nil
e.skipFrame = 0
e.ctx = nil
e.buf = e.buf[:0]
// Proper usage of a sync.Pool requires each entry to have approximately
// the same memory cost. To obtain this property when the stored type
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
@@ -39,10 +48,9 @@ func putEvent(e *Event) {
//
// See https://golang.org/issue/23199
const maxSize = 1 << 16 // 64KiB
if cap(e.buf) > maxSize {
return
if cap(e.buf) <= maxSize {
eventPool.Put(e)
}
eventPool.Put(e)
}
// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface
@@ -57,14 +65,15 @@ type LogArrayMarshaler interface {
MarshalZerologArray(a *Array)
}
func newEvent(w LevelWriter, level Level) *Event {
func newEvent(w LevelWriter, level Level, stack bool, ctx context.Context, hooks []Hook) *Event {
e := eventPool.Get().(*Event)
e.buf = e.buf[:0]
e.ch = nil
e.stack = stack
e.ctx = ctx
e.ch = hooks
e.buf = enc.AppendBeginMarker(e.buf)
e.w = w
e.level = level
e.stack = false
e.skipFrame = 0
return e
}
@@ -164,31 +173,58 @@ func (e *Event) Fields(fields interface{}) *Event {
if e == nil {
return e
}
e.buf = appendFields(e.buf, fields, e.stack)
e.buf = appendFields(e.buf, fields, e.stack, e.ctx, e.ch)
return e
}
// Dict adds the field key with a dict to the event context.
// Use zerolog.Dict() to create the dictionary.
// Use e.CreateDict() to create the dictionary.
func (e *Event) Dict(key string, dict *Event) *Event {
if e == nil {
return e
if e != nil {
dict.buf = enc.AppendEndMarker(dict.buf)
e.buf = append(enc.AppendKey(e.buf, key), dict.buf...)
}
dict.buf = enc.AppendEndMarker(dict.buf)
e.buf = append(enc.AppendKey(e.buf, key), dict.buf...)
putEvent(dict)
return e
}
// CreateDict creates an Event to be used with the *Event.Dict method.
// It preserves the stack, hooks, and context from the parent event.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the *Event.Dict method.
func (e *Event) CreateDict() *Event {
if e == nil {
return newEvent(nil, DebugLevel, false, nil, nil)
}
return newEvent(nil, DebugLevel, e.stack, e.ctx, e.ch)
}
// Dict creates an Event to be used with the *Event.Dict method.
// Call usual field methods like Str, Int etc to add fields to this
// event and give it as argument the *Event.Dict method.
// NOTE: This function is deprecated because it does not preserve
// the stack, hooks, and context from the parent event.
// Deprecated: Use Event.CreateDict instead.
func Dict() *Event {
return newEvent(nil, 0)
return newEvent(nil, DebugLevel, false, nil, nil)
}
// CreateArray creates an Array to be used with the *Event.Array method.
// It preserves the stack, hooks, and context from the parent event.
// Call usual field methods like Str, Int etc to add elements to this
// array and give it as argument the *Event.Array method.
func (e *Event) CreateArray() *Array {
a := Arr()
if e != nil {
a.stack = e.stack
a.ctx = e.ctx
a.ch = e.ch
}
return a
}
// Array adds the field key with an array to the event context.
// Use zerolog.Arr() to create the array or pass a type that
// Use e.CreateArray() to create the array or pass a type that
// implement the LogArrayMarshaler interface.
func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
if e == nil {
@@ -199,7 +235,7 @@ func (e *Event) Array(key string, arr LogArrayMarshaler) *Event {
if aa, ok := arr.(*Array); ok {
a = aa
} else {
a = Arr()
a = e.CreateArray()
arr.MarshalZerologArray(a)
}
e.buf = a.write(e.buf)
@@ -228,6 +264,33 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
return e
}
// Objects adds the field key with objs as an array of objects that
// implement the LogObjectMarshaler interface to the event.
//
// This is the array version that accepts a slice of LogObjectMarshaler objects.
func (e *Event) Objects(key string, objs []LogObjectMarshaler) *Event {
if e == nil {
return e
}
e.buf = enc.AppendArrayStart(enc.AppendKey(e.buf, key))
for i, obj := range objs {
e.buf = appendObject(e.buf, obj, e.stack, e.ctx, e.ch)
if i < (len(objs) - 1) {
e.buf = enc.AppendArrayDelim(e.buf)
}
}
e.buf = enc.AppendArrayEnd(e.buf)
return e
}
// ObjectsV adds the field key with objs as an array of objects that
// implement the LogObjectMarshaler interface to the event.
//
// This is a variadic version that accepts a list of individual LogObjectMarshaler objects.
func (e *Event) ObjectsV(key string, objs ...LogObjectMarshaler) *Event {
return e.Objects(key, objs)
}
// Func allows an anonymous func to run only if the event is enabled.
func (e *Event) Func(f func(e *Event)) *Event {
if e != nil && e.Enabled() {
@@ -258,6 +321,8 @@ func (e *Event) Str(key, val string) *Event {
}
// Strs adds the field key with vals as a []string to the *Event context.
//
// This is the array version that accepts a slice of string values.
func (e *Event) Strs(key string, vals []string) *Event {
if e == nil {
return e
@@ -266,8 +331,16 @@ func (e *Event) Strs(key string, vals []string) *Event {
return e
}
// Stringer adds the field key with val.String() (or null if val is nil)
// to the *Event context.
// StrsV adds the field key with vals as a []string to the *Event context.
//
// This is a variadic version that accepts a list of individual strings.
func (e *Event) StrsV(key string, vals ...string) *Event {
return e.Strs(key, vals)
}
// Stringer adds the field key and a val to the *Event context.
// If val is not nil, it is added by calling val.String().
// If val is nil, it is encoded as null without calling String().
func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
if e == nil {
return e
@@ -276,9 +349,11 @@ func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
return e
}
// Stringers adds the field key with vals where each individual val
// is used as val.String() (or null if val is empty) to the *Event
// context.
// Stringers adds the field key with vals to the *Event context.
// If a val is not nil, it is added by calling val.String().
// If a val is nil, it is encoded as null without calling String().
//
// This is the array version that accepts a slice of fmt.Stringer values.
func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
if e == nil {
return e
@@ -287,6 +362,16 @@ func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
return e
}
// StringersV adds the field key with vals to the *Event context.
// If a val is not nil, it is added by calling val.String().
// If a val is nil, it is encoded as null without calling String().
//
// This is a variadic version that accepts a list of individual
// fmt.Stringer values.
func (e *Event) StringersV(key string, vals ...fmt.Stringer) *Event {
return e.Stringers(key, vals)
}
// Bytes adds the field key with val as a string to the *Event context.
//
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
@@ -344,11 +429,10 @@ func (e *Event) AnErr(key string, err error) *Event {
case LogObjectMarshaler:
return e.Object(key, m)
case error:
if m == nil || isNilValue(m) {
if isNilValue(m) {
return e
} else {
return e.Str(key, m.Error())
}
return e.Str(key, m.Error())
case string:
return e.Str(key, m)
default:
@@ -362,20 +446,7 @@ func (e *Event) Errs(key string, errs []error) *Event {
if e == nil {
return e
}
arr := Arr()
for _, err := range errs {
switch m := ErrorMarshalFunc(err).(type) {
case LogObjectMarshaler:
arr = arr.Object(m)
case error:
arr = arr.Err(m)
case string:
arr = arr.Str(m)
default:
arr = arr.Interface(m)
}
}
arr := e.CreateArray().Errs(errs)
return e.Array(key, arr)
}
@@ -391,21 +462,22 @@ func (e *Event) Err(err error) *Event {
if e == nil {
return e
}
if e.stack && ErrorStackMarshaler != nil {
switch m := ErrorStackMarshaler(err).(type) {
case nil:
return e
case LogObjectMarshaler:
e.Object(ErrorStackFieldName, m)
e = e.Object(ErrorStackFieldName, m)
case error:
if m != nil && !isNilValue(m) {
e.Str(ErrorStackFieldName, m.Error())
}
e = e.Str(ErrorStackFieldName, m.Error())
case string:
e.Str(ErrorStackFieldName, m)
e = e.Str(ErrorStackFieldName, m)
default:
e.Interface(ErrorStackFieldName, m)
e = e.Interface(ErrorStackFieldName, m)
}
}
return e.AnErr(ErrorFieldName, err)
}
@@ -431,8 +503,8 @@ func (e *Event) Ctx(ctx context.Context) *Event {
}
// GetCtx retrieves the Go context.Context which is optionally stored in the
// Event. This allows Hooks and functions passed to Func() to retrieve values
// which are stored in the context.Context. This can be useful in tracing,
// Event. This allows Hooks and functions passed to Func() to retrieve values
// which are stored in the context.Context. This can be useful in tracing,
// where span information is commonly propagated in the context.Context.
func (e *Event) GetCtx() context.Context {
if e == nil || e.ctx == nil {
@@ -713,7 +785,7 @@ func (e *Event) Dur(key string, d time.Duration) *Event {
if e == nil {
return e
}
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return e
}
@@ -724,7 +796,7 @@ func (e *Event) Durs(key string, d []time.Duration) *Event {
if e == nil {
return e
}
e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return e
}
@@ -739,7 +811,7 @@ func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event {
if t.After(start) {
d = t.Sub(start)
}
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
return e
}
@@ -794,15 +866,13 @@ func (e *Event) caller(skip int) *Event {
if e == nil {
return e
}
pc, file, line, ok := runtime.Caller(skip + e.skipFrame)
if !ok {
return e
if pc, file, line, ok := runtime.Caller(skip + e.skipFrame); ok {
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(pc, file, line))
}
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(pc, file, line))
return e
}
// IPAddr adds IPv4 or IPv6 Address to the event
// IPAddr adds the field key with ip as a net.IP IPv4 or IPv6 Address to the event
func (e *Event) IPAddr(key string, ip net.IP) *Event {
if e == nil {
return e
@@ -811,7 +881,16 @@ func (e *Event) IPAddr(key string, ip net.IP) *Event {
return e
}
// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the event
// IPAddrs adds the field key with ip as a net.IP array of IPv4 or IPv6 Address to the event
func (e *Event) IPAddrs(key string, ip []net.IP) *Event {
if e == nil {
return e
}
e.buf = enc.AppendIPAddrs(enc.AppendKey(e.buf, key), ip)
return e
}
// IPPrefix adds the field key with pfx as a net.IPNet IPv4 or IPv6 Prefix (address and mask) to the event
func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event {
if e == nil {
return e
@@ -820,7 +899,16 @@ func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event {
return e
}
// MACAddr adds MAC address to the event
// IPPrefixes the field key with pfx as a net.IPNet array of IPv4 or IPv6 Prefixes (address and mask) to the event
func (e *Event) IPPrefixes(key string, pfx []net.IPNet) *Event {
if e == nil {
return e
}
e.buf = enc.AppendIPPrefixes(enc.AppendKey(e.buf, key), pfx)
return e
}
// MACAddr the field key with ha as a net.HardwareAddr MAC address to the event
func (e *Event) MACAddr(key string, ha net.HardwareAddr) *Event {
if e == nil {
return e
+62 -42
View File
@@ -1,24 +1,31 @@
package zerolog
import (
"context"
"encoding/json"
"io"
"net"
"reflect"
"sort"
"time"
"unsafe"
)
func isNilValue(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
func isNilValue(e error) bool {
switch reflect.TypeOf(e).Kind() {
case reflect.Ptr:
return reflect.ValueOf(e).IsNil()
default:
return false
}
}
func appendFields(dst []byte, fields interface{}, stack bool) []byte {
func appendFields(dst []byte, fields interface{}, stack bool, ctx context.Context, hooks []Hook) []byte {
switch fields := fields.(type) {
case []interface{}:
if n := len(fields); n&0x1 == 1 { // odd number
fields = fields[:n-1]
}
dst = appendFieldList(dst, fields, stack)
dst = appendFieldList(dst, fields, stack, ctx, hooks)
case map[string]interface{}:
keys := make([]string, 0, len(fields))
for key := range fields {
@@ -28,13 +35,22 @@ func appendFields(dst []byte, fields interface{}, stack bool) []byte {
kv := make([]interface{}, 2)
for _, key := range keys {
kv[0], kv[1] = key, fields[key]
dst = appendFieldList(dst, kv, stack)
dst = appendFieldList(dst, kv, stack, ctx, hooks)
}
}
return dst
}
func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
func appendObject(dst []byte, obj LogObjectMarshaler, stack bool, ctx context.Context, hooks []Hook) []byte {
e := newEvent(LevelWriterAdapter{io.Discard}, DebugLevel, stack, ctx, hooks)
e.buf = e.buf[:0] // discard the beginning marker added by newEvent
e.appendObject(obj)
dst = append(dst, e.buf...)
putEvent(e)
return dst
}
func appendFieldList(dst []byte, kvList []interface{}, stack bool, ctx context.Context, hooks []Hook) []byte {
for i, n := 0, len(kvList); i < n; i += 2 {
key, val := kvList[i], kvList[i+1]
if key, ok := key.(string); ok {
@@ -42,14 +58,6 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
} else {
continue
}
if val, ok := val.(LogObjectMarshaler); ok {
e := newEvent(nil, 0)
e.buf = e.buf[:0]
e.appendObject(val)
dst = append(dst, e.buf...)
putEvent(e)
continue
}
switch val := val.(type) {
case string:
dst = enc.AppendString(dst, val)
@@ -57,16 +65,12 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
dst = enc.AppendBytes(dst, val)
case error:
switch m := ErrorMarshalFunc(val).(type) {
case nil:
dst = enc.AppendNil(dst)
case LogObjectMarshaler:
e := newEvent(nil, 0)
e.buf = e.buf[:0]
e.appendObject(m)
dst = append(dst, e.buf...)
putEvent(e)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m == nil || isNilValue(m) {
dst = enc.AppendNil(dst)
} else {
if !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
case string:
@@ -76,16 +80,20 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
}
if stack && ErrorStackMarshaler != nil {
dst = enc.AppendKey(dst, ErrorStackFieldName)
switch m := ErrorStackMarshaler(val).(type) {
case nil:
return dst // do nothing with nil errors
case LogObjectMarshaler:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m != nil && !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendString(dst, m.Error())
case string:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendString(dst, m)
default:
dst = enc.AppendKey(dst, ErrorStackFieldName)
dst = enc.AppendInterface(dst, m)
}
}
@@ -93,16 +101,12 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
dst = enc.AppendArrayStart(dst)
for i, err := range val {
switch m := ErrorMarshalFunc(err).(type) {
case nil:
dst = enc.AppendNil(dst)
case LogObjectMarshaler:
e := newEvent(nil, 0)
e.buf = e.buf[:0]
e.appendObject(m)
dst = append(dst, e.buf...)
putEvent(e)
dst = appendObject(dst, m, stack, ctx, hooks)
case error:
if m == nil || isNilValue(m) {
dst = enc.AppendNil(dst)
} else {
if !isNilValue(m) {
dst = enc.AppendString(dst, m.Error())
}
case string:
@@ -112,7 +116,16 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
}
if i < (len(val) - 1) {
enc.AppendArrayDelim(dst)
dst = enc.AppendArrayDelim(dst)
}
}
dst = enc.AppendArrayEnd(dst)
case []LogObjectMarshaler:
dst = enc.AppendArrayStart(dst)
for i, obj := range val {
dst = appendObject(dst, obj, stack, ctx, hooks)
if i < (len(val) - 1) {
dst = enc.AppendArrayDelim(dst)
}
}
dst = enc.AppendArrayEnd(dst)
@@ -145,7 +158,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case time.Time:
dst = enc.AppendTime(dst, val, TimeFieldFormat)
case time.Duration:
dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
case *string:
if val != nil {
dst = enc.AppendString(dst, *val)
@@ -238,7 +251,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
}
case *time.Duration:
if val != nil {
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
} else {
dst = enc.AppendNil(dst)
}
@@ -258,8 +271,7 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
dst = enc.AppendInts64(dst, val)
case []uint:
dst = enc.AppendUints(dst, val)
// case []uint8:
// dst = enc.AppendUints8(dst, val)
// case []uint8: is handled as []byte above
case []uint16:
dst = enc.AppendUints16(dst, val)
case []uint32:
@@ -273,19 +285,27 @@ func appendFieldList(dst []byte, kvList []interface{}, stack bool) []byte {
case []time.Time:
dst = enc.AppendTimes(dst, val, TimeFieldFormat)
case []time.Duration:
dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger, FloatingPointPrecision)
dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldFormat, DurationFieldInteger, FloatingPointPrecision)
case nil:
dst = enc.AppendNil(dst)
case net.IP:
dst = enc.AppendIPAddr(dst, val)
case []net.IP:
dst = enc.AppendIPAddrs(dst, val)
case net.IPNet:
dst = enc.AppendIPPrefix(dst, val)
case []net.IPNet:
dst = enc.AppendIPPrefixes(dst, val)
case net.HardwareAddr:
dst = enc.AppendMACAddr(dst, val)
case json.RawMessage:
dst = appendJSON(dst, val)
default:
dst = enc.AppendInterface(dst, val)
if lom, ok := val.(LogObjectMarshaler); ok {
dst = appendObject(dst, lom, stack, ctx, hooks)
} else {
dst = enc.AppendInterface(dst, val)
}
}
}
return dst
+18
View File
@@ -24,6 +24,16 @@ const (
// TimeFormatUnixNano defines a time format that makes time fields to be
// serialized as Unix timestamp integers in nanoseconds.
TimeFormatUnixNano = "UNIXNANO"
// DurationFormatFloat defines a format for Duration fields that makes duration fields to be
// serialized as floating point numbers.
DurationFormatFloat = "float"
// DurationFormatInt defines a format for Duration fields that makes duration fields to be
// serialized as integers.
DurationFormatInt = "int"
// DurationFormatString defines a format for Duration fields that makes duration fields to be
// serialized as string.
DurationFormatString = "string"
)
var (
@@ -107,12 +117,16 @@ var (
// TimestampFunc defines the function called to generate a timestamp.
TimestampFunc = time.Now
// DurationFieldFormat defines the format of the Duration field type.
DurationFieldFormat = DurationFormatFloat
// DurationFieldUnit defines the unit for time.Duration type fields added
// using the Dur method.
DurationFieldUnit = time.Millisecond
// DurationFieldInteger renders Dur fields as integer instead of float if
// set to true.
// Deprecated: use DurationFieldFormat with DurationFormatInt instead.
DurationFieldInteger = false
// ErrorHandler is called whenever zerolog fails to write an event on its
@@ -120,6 +134,10 @@ var (
// be thread safe and non-blocking.
ErrorHandler func(err error)
// FatalExitFunc is called by log.Fatal() instead of os.Exit(1). If not set,
// os.Exit(1) is called.
FatalExitFunc func()
// DefaultContextLogger is returned from Ctx() if there is no logger associated
// with the context.
DefaultContextLogger *Logger
+30
View File
@@ -0,0 +1,30 @@
//go:build go1.18
// +build go1.18
package zerolog
import (
"fmt"
)
func AsLogObjectMarshalers[T LogObjectMarshaler](objs []T) []LogObjectMarshaler {
if objs == nil {
return nil
}
s := make([]LogObjectMarshaler, len(objs))
for i, v := range objs {
s[i] = v
}
return s
}
func AsStringers[T fmt.Stringer](objs []T) []fmt.Stringer {
if objs == nil {
return nil
}
s := make([]fmt.Stringer, len(objs))
for i, v := range objs {
s[i] = v
}
return s
}
+7 -7
View File
@@ -55,12 +55,12 @@ const (
)
const (
float32Nan = "\xfa\x7f\xc0\x00\x00"
float32PosInfinity = "\xfa\x7f\x80\x00\x00"
float32NegInfinity = "\xfa\xff\x80\x00\x00"
float64Nan = "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"
float64PosInfinity = "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00"
float64NegInfinity = "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00"
float32Nan = "\x7f\xc0\x00\x00"
float32PosInfinity = "\x7f\x80\x00\x00"
float32NegInfinity = "\xff\x80\x00\x00"
float64Nan = "\x7f\xf8\x00\x00\x00\x00\x00\x00"
float64PosInfinity = "\x7f\xf0\x00\x00\x00\x00\x00\x00"
float64NegInfinity = "\xff\xf0\x00\x00\x00\x00\x00\x00"
)
// IntegerTimeFieldFormat indicates the format of timestamp decoded
@@ -72,7 +72,7 @@ var IntegerTimeFieldFormat = time.RFC3339
var NanoTimeFieldFormat = time.RFC3339Nano
func appendCborTypePrefix(dst []byte, major byte, number uint64) []byte {
byteCount := 8
var byteCount int
var minor byte
switch {
case number < 256:
+1 -1
View File
@@ -490,7 +490,7 @@ func decodeTimeStamp(src *bufio.Reader) []byte {
tsb = append(tsb, '"')
return tsb
}
panic(fmt.Errorf("TS format is neigther int nor float: %d", tsMajor))
panic(fmt.Errorf("TS format is neither int nor float: %d", tsMajor))
}
func decodeSimpleFloat(src *bufio.Reader) []byte {
+1 -1
View File
@@ -35,7 +35,7 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
// AppendStringers encodes and adds an array of Stringer values
// to the dst byte array.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
if vals == nil || len(vals) == 0 {
return e.AppendArrayEnd(e.AppendArrayStart(dst))
}
dst = e.AppendArrayStart(dst)
+23 -5
View File
@@ -4,6 +4,17 @@ import (
"time"
)
const (
// Import from zerolog/global.go
timeFormatUnix = ""
timeFormatUnixMs = "UNIXMS"
timeFormatUnixMicro = "UNIXMICRO"
timeFormatUnixNano = "UNIXNANO"
durationFormatFloat = "float"
durationFormatInt = "int"
durationFormatString = "string"
)
func appendIntegerTimestamp(dst []byte, t time.Time) []byte {
major := majorTypeTags
minor := additionalTypeTimestamp
@@ -27,8 +38,7 @@ func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte {
dst = append(dst, major|minor)
secs := t.Unix()
nanos := t.Nanosecond()
var val float64
val = float64(secs)*1.0 + float64(nanos)*1e-9
val := float64(secs)*1.0 + float64(nanos)*1e-9
return e.AppendFloat64(dst, val, -1)
}
@@ -64,17 +74,25 @@ func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte
// AppendDuration encodes and adds a duration to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float).
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, unused int) []byte {
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, format string, useInt bool, unused int) []byte {
if useInt {
return e.AppendInt64(dst, int64(d/unit))
}
switch format {
case durationFormatFloat:
return e.AppendFloat64(dst, float64(d)/float64(unit), unused)
case durationFormatInt:
return e.AppendInt64(dst, int64(d/unit))
case durationFormatString:
return e.AppendString(dst, d.String())
}
return e.AppendFloat64(dst, float64(d)/float64(unit), unused)
}
// AppendDurations encodes and adds an array of durations to the dst byte array.
// useInt field indicates whether to store the duration as seconds (integer) or
// as seconds+nanoseconds (float).
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, unused int) []byte {
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, format string, useInt bool, unused int) []byte {
major := majorTypeArray
l := len(vals)
if l == 0 {
@@ -87,7 +105,7 @@ func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Dur
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, d := range vals {
dst = e.AppendDuration(dst, d, unit, useInt, unused)
dst = e.AppendDuration(dst, d, unit, format, useInt, unused)
}
return dst
}
+40 -2
View File
@@ -447,7 +447,7 @@ func (e Encoder) AppendType(dst []byte, i interface{}) []byte {
return e.AppendString(dst, reflect.TypeOf(i).String())
}
// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6).
// AppendIPAddr adds a net.IP IPv4 or IPv6 address into the dst byte array.
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
dst = append(dst, byte(additionalTypeTagNetworkAddr>>8))
@@ -455,7 +455,26 @@ func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
return e.AppendBytes(dst, ip)
}
// AppendIPPrefix encodes and inserts an IP Address Prefix (Address + Mask Length).
// AppendIPAddrs adds a []net.IP array of IPv4 or IPv6 address into the dst byte array.
func (e Encoder) AppendIPAddrs(dst []byte, ips []net.IP) []byte {
major := majorTypeArray
l := len(ips)
if l == 0 {
return e.AppendArrayEnd(e.AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, major|lb)
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range ips {
dst = e.AppendIPAddr(dst, v)
}
return dst
}
// AppendIPPrefix adds a net.IPNet IPv4 or IPv6 Prefix (address & mask) into the dst byte array.
func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte {
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
dst = append(dst, byte(additionalTypeTagNetworkPrefix>>8))
@@ -469,6 +488,25 @@ func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte {
return e.AppendUint8(dst, uint8(maskLen))
}
// AppendIPPrefixes adds a []net.IPNet array of IPv4 or IPv6 Prefix (address & mask) into the dst byte array.
func (e Encoder) AppendIPPrefixes(dst []byte, pfxs []net.IPNet) []byte {
major := majorTypeArray
l := len(pfxs)
if l == 0 {
return e.AppendArrayEnd(e.AppendArrayStart(dst))
}
if l <= additionalMax {
lb := byte(l)
dst = append(dst, major|lb)
} else {
dst = appendCborTypePrefix(dst, major, uint64(l))
}
for _, v := range pfxs {
dst = e.AppendIPPrefix(dst, v)
}
return dst
}
// AppendMACAddr encodes and inserts a Hardware (MAC) address.
func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte {
dst = append(dst, majorTypeTags|additionalTypeIntUint16)
+2 -2
View File
@@ -23,7 +23,7 @@ func (Encoder) AppendBytes(dst, s []byte) []byte {
func (Encoder) AppendHex(dst, s []byte) []byte {
dst = append(dst, '"')
for _, v := range s {
dst = append(dst, hex[v>>4], hex[v&0x0f])
dst = append(dst, hexCharacters[v>>4], hexCharacters[v&0x0f])
}
return append(dst, '"')
}
@@ -73,7 +73,7 @@ func appendBytesComplex(dst, s []byte, i int) []byte {
case '\t':
dst = append(dst, '\\', 't')
default:
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
dst = append(dst, '\\', 'u', '0', '0', hexCharacters[b>>4], hexCharacters[b&0xF])
}
i++
start = i
+4 -4
View File
@@ -5,7 +5,7 @@ import (
"unicode/utf8"
)
const hex = "0123456789abcdef"
const hexCharacters = "0123456789abcdef"
var noEscapeTable = [256]bool{}
@@ -66,7 +66,7 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
// AppendStringers encodes the provided Stringer list to json and
// appends the encoded Stringer list to the input byte slice.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
if vals == nil || len(vals) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
@@ -88,7 +88,7 @@ func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte {
return e.AppendString(dst, val.String())
}
//// appendStringComplex is used by appendString to take over an in
// appendStringComplex is used by appendString to take over an in
// progress JSON string encoding that encountered a character that needs
// to be encoded.
func appendStringComplex(dst []byte, s string, i int) []byte {
@@ -137,7 +137,7 @@ func appendStringComplex(dst []byte, s string, i int) []byte {
case '\t':
dst = append(dst, '\\', 't')
default:
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF])
dst = append(dst, '\\', 'u', '0', '0', hexCharacters[b>>4], hexCharacters[b&0xF])
}
i++
start = i
+19 -8
View File
@@ -7,10 +7,13 @@ import (
const (
// Import from zerolog/global.go
timeFormatUnix = ""
timeFormatUnixMs = "UNIXMS"
timeFormatUnixMicro = "UNIXMICRO"
timeFormatUnixNano = "UNIXNANO"
timeFormatUnix = ""
timeFormatUnixMs = "UNIXMS"
timeFormatUnixMicro = "UNIXMICRO"
timeFormatUnixNano = "UNIXNANO"
durationFormatFloat = "float"
durationFormatInt = "int"
durationFormatString = "string"
)
// AppendTime formats the input time with the given format
@@ -88,24 +91,32 @@ func appendUnixNanoTimes(dst []byte, vals []time.Time, div int64) []byte {
// AppendDuration formats the input duration with the given unit & format
// and appends the encoded string to the input byte slice.
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool, precision int) []byte {
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, format string, useInt bool, precision int) []byte {
if useInt {
return strconv.AppendInt(dst, int64(d/unit), 10)
}
switch format {
case durationFormatFloat:
return e.AppendFloat64(dst, float64(d)/float64(unit), precision)
case durationFormatInt:
return e.AppendInt64(dst, int64(d/unit))
case durationFormatString:
return e.AppendString(dst, d.String())
}
return e.AppendFloat64(dst, float64(d)/float64(unit), precision)
}
// AppendDurations formats the input durations with the given unit & format
// and appends the encoded string list to the input byte slice.
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool, precision int) []byte {
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, format string, useInt bool, precision int) []byte {
if len(vals) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
dst = e.AppendDuration(dst, vals[0], unit, useInt, precision)
dst = e.AppendDuration(dst, vals[0], unit, format, useInt, precision)
if len(vals) > 1 {
for _, d := range vals[1:] {
dst = e.AppendDuration(append(dst, ','), d, unit, useInt, precision)
dst = e.AppendDuration(append(dst, ','), d, unit, format, useInt, precision)
}
}
dst = append(dst, ']')
+37 -6
View File
@@ -418,18 +418,49 @@ func (Encoder) AppendObjectData(dst []byte, o []byte) []byte {
return append(dst, o...)
}
// AppendIPAddr adds IPv4 or IPv6 address to dst.
// AppendIPAddr adds a net.IP IPv4 or IPv6 address to dst.
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte {
return e.AppendString(dst, ip.String())
}
// AppendIPPrefix adds IPv4 or IPv6 Prefix (address & mask) to dst.
func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte {
return e.AppendString(dst, pfx.String())
// AppendIPAddrs adds a []net.IP array of IPv4 or IPv6 address to dst.
func (e Encoder) AppendIPAddrs(dst []byte, ips []net.IP) []byte {
if len(ips) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
dst = e.AppendString(dst, ips[0].String())
if len(ips) > 1 {
for _, ip := range ips[1:] {
dst = e.AppendString(append(dst, ','), ip.String())
}
}
dst = append(dst, ']')
return dst
}
// AppendMACAddr adds MAC address to dst.
// AppendIPPrefix adds a net.IPNet IPv4 or IPv6 Prefix (address & mask) to dst.
func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte {
return e.AppendString(dst, pfx.String())
}
// AppendIPPrefixes adds a []net.IPNet array of IPv4 or IPv6 Prefix (address & mask) to dst.
func (e Encoder) AppendIPPrefixes(dst []byte, pfxs []net.IPNet) []byte {
if len(pfxs) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
dst = e.AppendString(dst, pfxs[0].String())
if len(pfxs) > 1 {
for _, pfx := range pfxs[1:] {
dst = e.AppendString(append(dst, ','), pfx.String())
}
}
dst = append(dst, ']')
return dst
}
// AppendMACAddr adds a net.HardwareAddr MAC address to dst.
func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte {
return e.AppendString(dst, ha.String())
}
+79 -69
View File
@@ -2,85 +2,85 @@
//
// A global Logger can be use for simple logging:
//
// import "github.com/rs/zerolog/log"
// import "github.com/rs/zerolog/log"
//
// log.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
// log.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
//
// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log".
//
// Fields can be added to log messages:
//
// log.Info().Str("foo", "bar").Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
// log.Info().Str("foo", "bar").Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
//
// Create logger instance to manage different outputs:
//
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Str("foo", "bar").
// Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Str("foo", "bar").
// Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
//
// Sub-loggers let you chain loggers with additional context:
//
// sublogger := log.With().Str("component", "foo").Logger()
// sublogger.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
// sublogger := log.With().Str("component", "foo").Logger()
// sublogger.Info().Msg("hello world")
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
//
// Level logging
//
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
//
// log.Debug().Msg("filtered out message")
// log.Info().Msg("routed message")
// log.Debug().Msg("filtered out message")
// log.Info().Msg("routed message")
//
// if e := log.Debug(); e.Enabled() {
// // Compute log output only if enabled.
// value := compute()
// e.Str("foo": value).Msg("some debug message")
// }
// // Output: {"level":"info","time":1494567715,"routed message"}
// if e := log.Debug(); e.Enabled() {
// // Compute log output only if enabled.
// value := compute()
// e.Str("foo": value).Msg("some debug message")
// }
// // Output: {"level":"info","time":1494567715,"routed message"}
//
// Customize automatic field names:
//
// log.TimestampFieldName = "t"
// log.LevelFieldName = "p"
// log.MessageFieldName = "m"
// log.TimestampFieldName = "t"
// log.LevelFieldName = "p"
// log.MessageFieldName = "m"
//
// log.Info().Msg("hello world")
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
// log.Info().Msg("hello world")
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
//
// Log with no level and message:
//
// log.Log().Str("foo","bar").Msg("")
// // Output: {"time":1494567715,"foo":"bar"}
// log.Log().Str("foo","bar").Msg("")
// // Output: {"time":1494567715,"foo":"bar"}
//
// Add contextual fields to global Logger:
//
// log.Logger = log.With().Str("foo", "bar").Logger()
// log.Logger = log.With().Str("foo", "bar").Logger()
//
// Sample logs:
//
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
// sampled.Info().Msg("will be logged every 10 messages")
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
// sampled.Info().Msg("will be logged every 10 messages")
//
// Log with contextual hooks:
//
// // Create the hook:
// type SeverityHook struct{}
// // Create the 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())
// }
// }
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
// if level != zerolog.NoLevel {
// e.Str("severity", level.String())
// }
// }
//
// // And use it:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Hook(h)
// log.Warn().Msg("")
// // Output: {"level":"warn","severity":"warn"}
// // And use it:
// var h SeverityHook
// log := zerolog.New(os.Stdout).Hook(h)
// log.Warn().Msg("")
// // Output: {"level":"warn","severity":"warn"}
//
// # Caveats
//
@@ -89,11 +89,11 @@
// There is no fields deduplication out-of-the-box.
// Using the same key multiple times creates new key in final JSON each time.
//
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Timestamp().
// Msg("dup")
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// logger.Info().
// Timestamp().
// Msg("dup")
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
//
// In this case, many consumers will take the last value,
// but this is not guaranteed; check yours if in doubt.
@@ -102,15 +102,15 @@
//
// Be careful when calling UpdateContext. It is not concurrency safe. Use the With method to create a child logger:
//
// func handler(w http.ResponseWriter, r *http.Request) {
// // Create a child logger for concurrency safety
// logger := log.Logger.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 {
// ...
// })
// }
// // Add context fields, for example User-Agent from HTTP headers
// logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
// ...
// })
// }
package zerolog
import (
@@ -294,7 +294,7 @@ func (l Logger) With() Context {
// Caution: This method is not concurrency safe.
// Use the With method to create a child logger before modifying the context from concurrent goroutines.
func (l *Logger) UpdateContext(update func(c Context) Context) {
if l == disabledLogger {
if l.disabled() {
return
}
if cap(l.context) == 0 {
@@ -382,18 +382,24 @@ func (l *Logger) Err(err error) *Event {
return l.Info()
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method, which terminates the program immediately.
// Fatal starts a new message with fatal level. The FatalExitFunc interceptor function
// is called by the Msg method, which by default terminates the program immediately
// using os.Exit(1), any desired behavior can be implemented by setting FatalExitFunc.
//
// You must call Msg on the returned event in order to send the event.
func (l *Logger) Fatal() *Event {
return l.newEvent(FatalLevel, func(msg string) {
if closer, ok := l.w.(io.Closer); ok {
// Close the writer to flush any buffered message. Otherwise the message
// will be lost as os.Exit() terminates the program immediately.
// could be lost if FatalExitFunc() terminates the program immediately or
// os.Exit(1) is called if not FatalExitFunc isn't set (default).
closer.Close()
}
os.Exit(1)
if FatalExitFunc != nil {
FatalExitFunc()
} else {
os.Exit(1) // untestable: terminates the program, cannot be covered
}
})
}
@@ -487,25 +493,29 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
}
return nil
}
e := newEvent(l.w, level)
e := newEvent(l.w, level, l.stack, l.ctx, l.hooks)
e.done = done
e.ch = l.hooks
e.ctx = l.ctx
if level != NoLevel && LevelFieldName != "" {
e.Str(LevelFieldName, LevelFieldMarshalFunc(level))
}
if len(l.context) > 1 {
e.buf = enc.AppendObjectData(e.buf, l.context)
}
if l.stack {
e.Stack()
}
return e
}
func (l *Logger) scratchEvent() *Event {
return newEvent(LevelWriterAdapter{io.Discard}, DebugLevel, l.stack, l.ctx, l.hooks)
}
// disabled returns true if the logger is a disabled or nop logger.
func (l *Logger) disabled() bool {
return l.w == nil || l.level == Disabled
}
// should returns true if the log event should be logged.
func (l *Logger) should(lvl Level) bool {
if l.w == nil {
if l.disabled() {
return false
}
if lvl < l.level || lvl < GlobalLevel() {
+247
View File
@@ -0,0 +1,247 @@
package zerolog
import (
"context"
"log/slog"
"time"
)
// SlogHandler implements the slog.Handler interface using a zerolog.Logger
// as the underlying log backend. This allows code that uses the standard
// library's slog package to route log output through zerolog.
type SlogHandler struct {
logger Logger
prefix string // group prefix for nested groups
attrs []slog.Attr
}
// NewSlogHandler creates a new slog.Handler that writes log records to the
// given zerolog.Logger. The handler maps slog levels to zerolog levels and
// converts slog attributes to zerolog fields.
func NewSlogHandler(logger Logger) *SlogHandler {
return &SlogHandler{logger: logger}
}
// Enabled reports whether the handler handles records at the given level.
// It mirrors Logger.should's level and writer checks (without sampling).
func (h *SlogHandler) Enabled(_ context.Context, level slog.Level) bool {
if h.logger.w == nil {
return false
}
zl := slogToZerologLevel(level)
if zl < GlobalLevel() {
return false
}
return zl >= h.logger.level
}
// Handle handles the Record. It converts the slog.Record into a zerolog event
// and writes it using the underlying zerolog.Logger.
func (h *SlogHandler) Handle(ctx context.Context, record slog.Record) error {
zlevel := slogToZerologLevel(record.Level)
event := h.logger.WithLevel(zlevel)
if event == nil {
return nil
}
// Propagate slog context to the zerolog event so that hooks
// relying on Event.GetCtx() (e.g. tracing) can access it.
if ctx != nil {
event = event.Ctx(ctx)
}
// Add pre-attached attrs from WithAttrs
for _, a := range h.attrs {
event = appendSlogAttr(event, a, h.prefix)
}
// Add attrs from the record itself
record.Attrs(func(a slog.Attr) bool {
event = appendSlogAttr(event, a, h.prefix)
return true
})
// Add timestamp from the slog record, but only if the logger doesn't
// already have a timestampHook (added via .With().Timestamp()) to
// avoid duplicate timestamp keys in the output.
if !record.Time.IsZero() && !h.hasTimestampHook() {
event.Time(TimestampFieldName, record.Time)
}
event.Msg(record.Message)
return nil
}
// hasTimestampHook reports whether the logger has a timestampHook installed,
// which would cause duplicate timestamp fields if we also emit record.Time.
func (h *SlogHandler) hasTimestampHook() bool {
for _, hook := range h.logger.hooks {
if _, ok := hook.(timestampHook); ok {
return true
}
}
return false
}
// WithAttrs returns a new Handler with the given attributes pre-attached.
// These attributes will be included in every subsequent log record.
func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
h2 := h.clone()
h2.attrs = append(h2.attrs, attrs...)
return h2
}
// WithGroup returns a new Handler with the given group name. All subsequent
// attributes will be nested under this group name in the output.
func (h *SlogHandler) WithGroup(name string) slog.Handler {
if name == "" {
return h
}
h2 := h.clone()
if h2.prefix != "" {
h2.prefix = h2.prefix + "." + name
} else {
h2.prefix = name
}
return h2
}
func (h *SlogHandler) clone() *SlogHandler {
h2 := &SlogHandler{
logger: h.logger,
prefix: h.prefix,
}
if len(h.attrs) > 0 {
h2.attrs = make([]slog.Attr, len(h.attrs))
copy(h2.attrs, h.attrs)
}
return h2
}
// slogToZerologLevel maps slog levels to zerolog levels.
//
// slog levels: Debug=-4, Info=0, Warn=4, Error=8
// zerolog levels: Trace=-1, Debug=0, Info=1, Warn=2, Error=3, Fatal=4, Panic=5
func slogToZerologLevel(level slog.Level) Level {
switch {
case level < slog.LevelDebug:
return TraceLevel
case level < slog.LevelInfo:
return DebugLevel
case level < slog.LevelWarn:
return InfoLevel
case level < slog.LevelError:
return WarnLevel
default:
return ErrorLevel
}
}
// zerologToSlogLevel maps zerolog levels to slog levels.
func zerologToSlogLevel(level Level) slog.Level {
switch level {
case TraceLevel:
return slog.LevelDebug - 4
case DebugLevel:
return slog.LevelDebug
case InfoLevel:
return slog.LevelInfo
case WarnLevel:
return slog.LevelWarn
case ErrorLevel:
return slog.LevelError
case FatalLevel:
return slog.LevelError + 4
case PanicLevel:
return slog.LevelError + 8
default:
return slog.LevelInfo
}
}
// joinPrefix concatenates a prefix and key with a dot separator.
// It avoids allocations when either prefix or key is empty.
func joinPrefix(prefix, key string) string {
if prefix == "" {
return key
}
if key == "" {
return prefix
}
return prefix + "." + key
}
// appendSlogAttr appends a single slog.Attr to the zerolog event, handling
// type-specific encoding to avoid reflection where possible.
func appendSlogAttr(event *Event, attr slog.Attr, prefix string) *Event {
if event == nil {
return event
}
// Resolve the attribute to handle LogValuer types.
// This handles slog.KindLogValuer implicitly by unwrapping
// any values that implement slog.LogValuer to their resolved form.
attr.Value = attr.Value.Resolve()
// For group kinds, handle grouping before key concatenation
if attr.Value.Kind() == slog.KindGroup {
attrs := attr.Value.Group()
if len(attrs) == 0 {
return event
}
groupPrefix := joinPrefix(prefix, attr.Key)
for _, ga := range attrs {
event = appendSlogAttr(event, ga, groupPrefix)
}
return event
}
// Skip empty keys for non-group attributes
if attr.Key == "" {
return event
}
key := joinPrefix(prefix, attr.Key)
val := attr.Value
switch val.Kind() {
case slog.KindString:
event = event.Str(key, val.String())
case slog.KindInt64:
event = event.Int64(key, val.Int64())
case slog.KindUint64:
event = event.Uint64(key, val.Uint64())
case slog.KindFloat64:
event = event.Float64(key, val.Float64())
case slog.KindBool:
event = event.Bool(key, val.Bool())
case slog.KindDuration:
event = event.Dur(key, val.Duration())
case slog.KindTime:
event = event.Time(key, val.Time())
case slog.KindAny:
v := val.Any()
switch cv := v.(type) {
case error:
event = event.AnErr(key, cv)
case time.Duration:
event = event.Dur(key, cv)
case time.Time:
event = event.Time(key, cv)
case []byte:
event = event.Bytes(key, cv)
default:
event = event.Interface(key, v)
}
default:
event = event.Interface(key, val.Any())
}
return event
}
// Verify at compile time that SlogHandler satisfies the slog.Handler interface.
var _ slog.Handler = (*SlogHandler)(nil)