chore: bump reva to latest main
This commit is contained in:
committed by
Ralf Haferkamp
parent
4c86d2a289
commit
b8c4f581fb
Generated
+2
@@ -23,3 +23,5 @@ _testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
coverage.out
|
||||
|
||||
+175
-130
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
Reference in New Issue
Block a user