diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..0672ca9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,57 @@
+name: Continuous Integration
+
+on:
+ push:
+ tags:
+ - v*
+ branches:
+ - master
+ - main
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ linters:
+ name: Run linters
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.21'
+ cache-dependency-path: |
+ go.sum
+ test/go.sum
+ - name: Lint .
+ uses: golangci/golangci-lint-action@v3
+ with:
+ version: v1.56
+ # Disable caching as a workaround for https://github.com/golangci/golangci-lint-action/issues/135.
+ # The line can be removed once the golangci-lint issue is resolved.
+ skip-pkg-cache: true
+ - name: Lint other modules
+ run: go list -m -f '{{.Dir}}/...' | golangci-lint run
+
+ unit-tests:
+ name: Run unit tests
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ go: ['1.21']
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version: ${{ matrix.go }}
+ - name: Test
+ run: go list -m -f '{{.Dir}}/...' | xargs go test -race -coverprofile=cover.out -coverpkg=./...
+
+ - name: Collect coverage
+ run: go tool cover -html=cover.out -o cover.html
+
+ - name: Upload coverage to codecov.io
+ uses: codecov/codecov-action@v4
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..3f6a773
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,79 @@
+linters-settings:
+ dupl:
+ threshold: 150
+ exhaustive:
+ default-signifies-exhaustive: false
+ funlen:
+ lines: 100
+ statements: 50
+ goconst:
+ min-len: 2
+ min-occurrences: 2
+ gocyclo:
+ min-complexity: 32
+ goimports:
+ local-prefixes: github.com/pamburus
+ govet:
+ check-shadowing: false
+ lll:
+ line-length: 140
+ maligned:
+ suggest-new: true
+ nolintlint:
+ allow-unused: false # report any unused nolint directives
+ require-explanation: true # require an explanation for nolint directives
+ require-specific: false # don't require nolint directives to be specific about which linter is being skipped
+
+linters:
+ # Please, do not use `enable-all`: it's deprecated and will be removed soon.
+ # Inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint.
+ disable-all: true
+ enable:
+ - asciicheck
+ - dupl
+ - errcheck
+ - exportloopref
+ - gocritic
+ - gocyclo
+ - godot
+ - gofmt
+ - goimports
+ - gosec
+ - gosimple
+ - govet
+ - ineffassign
+ - misspell
+ - nakedret
+ - nlreturn
+ - prealloc
+ - revive
+ - staticcheck
+ - stylecheck
+ - typecheck
+ - unconvert
+ - unparam
+ - unused
+ - vet
+ - vetshadow
+ - whitespace
+
+issues:
+ # Disable this option because it causes golint to hide almost all issues.
+ exclude-use-default: false
+ # Excluding configuration per-path, per-linter, per-text and per-source.
+ exclude-rules:
+ # The dot-imports linter is disabled for the test files because it is convenient to dot-import third-party testing frameworks.
+ - linters: [revive]
+ text: '^dot-imports: '
+ paths:
+ - '*_test.go'
+ # The staticcheck linter reports that `nil` context is passed, but it is intentionally done for the sake of the tests.
+ - linters: [staticcheck]
+ text: '^SA1012:'
+ paths:
+ - '*_test.go'
+ # The duplicate lines in the benchmark_test.go file are expected because any attempt to deduplicate them would affect the performance of the benchmark.
+ - linters: [dupl]
+ text: 'lines are duplicate'
+ paths:
+ - 'benchmark_test.go'
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..0ccf1fd
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,23 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Go: Benchmark LogAfterWith",
+ "type": "go",
+ "request": "launch",
+ "mode": "test",
+ "program": "${workspaceFolder}",
+ "args": [
+ "-test.run=^$",
+ "-test.bench=^BenchmarkLogger/slogx/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs$",
+ "-test.benchtime=1x"
+ ],
+ "env": {
+ "GOFLAGS": "-count=1"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 6e2338d..8e47418 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,26 @@
-# slogx
-Extensions and helpers for log/slog package.
+# slogx [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
+
+Module [slogx](https://pkg.go.dev/github.com/pamburus/slogx) provides extensions and helpers for the [log/slog](https://pkg.go.dev/log/slog) package.
+
+## Packages
+* [slogx](./README.md)
+* [slogc](slogc/README.md)
+
+
+### Package slogx
+
+Package [slogx](https://pkg.go.dev/github.com/pamburus/slogx) provides [Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) as an alternative to [slog.Logger](https://pkg.go.dev/log/slog#Logger), which focuses on performance and makes several design changes to improve it:
+* It does not provide convenience methods for attributes that may affect performace. All methods accept attributes only as [slog.Attr](https://pkg.go.dev/log/slog#Attr).
+* It provides an [option](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithSource) to disable the inclusion of [source](https://pkg.go.dev/log/slog#Source) information into the log [Record](https://pkg.go.dev/log/slog#Record). This can improve performance up to 100% in cases where the source information is not included by the [Handler](https://pkg.go.dev/log/slog#Handler) anyway.
+* Its [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) method does not immediately call the [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method of the handler, instead it buffers up to 4 attributes which are then will be added to each log [Record](https://pkg.go.dev/log/slog#Record). This improves performance if you need to define a temporary set of attributes in a function and log a few messages with those attributes a few times. It also significantly improves performance when the logger is disabled. It is because calling [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) is quite an expensive operation, especially if the [Handler](https://pkg.go.dev/log/slog#Handler) is wrapped several times. That is, each layer will call the underlying handler's [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method and cause a lot of allocations. But what if the message is discarded because the logger is disabled? Yes, it will be a waste of CPU time. So for temporary [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) attribute sets, it is usually more efficient to hold them at the [Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger). If any of the [WithGroup](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithGroup), [Handler](https://pkg.go.dev/github.com/pamburus/slogx#Logger.Handler), or [LongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.LongTerm) methods are called later, the temporary attributes will be flushed using the [WithAttrs](https://pkg.go.dev/log/slog#Handler.WithAttrs) method.
+* It provides the [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) method, which acts as a sequence of [With](https://pkg.go.dev/github.com/pamburus/slogx#Logger.With) and [LongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.LongTerm) method calls and is needed for cases where the resulting logger is intended to be reused multiple times and may reside in a rather long-lived context.
+
+## Performance
+* See [benchmark results](doc/benchmark/README.md) for details.
+
+[doc-img]: https://pkg.go.dev/badge/github.com/pamburus/slogx
+[doc]: https://pkg.go.dev/github.com/pamburus/slogx
+[ci-img]: https://github.com/pamburus/slogx/actions/workflows/ci.yml/badge.svg
+[ci]: https://github.com/pamburus/slogx/actions/workflows/ci.yml
+[cov-img]: https://codecov.io/gh/pamburus/slogx/graph/badge.svg?token=0TF6JD4KDU
+[cov]: https://codecov.io/gh/pamburus/slogx
diff --git a/attr.go b/attr.go
new file mode 100644
index 0000000..f7c34ab
--- /dev/null
+++ b/attr.go
@@ -0,0 +1,108 @@
+package slogx
+
+import (
+ "log/slog"
+ "slices"
+)
+
+// ErrorAttr returns an attribute with the error.
+func ErrorAttr(err error) slog.Attr {
+ return slog.Any(ErrorKey, err)
+}
+
+const (
+ // ErrorKey is the key used for the error attribute.
+ ErrorKey = "error"
+)
+
+// AttrPack is a an optimized pack of attributes.
+type AttrPack struct {
+ front [4]slog.Attr
+ nFront int
+ back []slog.Attr
+}
+
+// Clone returns a copy of the AttrPack without a shared state.
+func (r AttrPack) Clone() AttrPack {
+ r.back = slices.Clip(r.back)
+
+ return r
+}
+
+// Len returns the number of attributes in the AttrPack.
+func (r AttrPack) Len() int {
+ return r.nFront + len(r.back)
+}
+
+// Enumerate calls f on each Attr in the AttrPack.
+func (r AttrPack) Enumerate(f func(slog.Attr) bool) {
+ for i := 0; i < r.nFront; i++ {
+ if !f(r.front[i]) {
+ return
+ }
+ }
+ for _, a := range r.back {
+ if !f(a) {
+ return
+ }
+ }
+}
+
+// Add appends the given Attrs to the AttrPack's list of Attrs.
+func (r *AttrPack) Add(attrs ...slog.Attr) {
+ var i int
+ for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ {
+ a := attrs[i]
+ if isEmptyGroup(a.Value) {
+ continue
+ }
+ r.front[r.nFront] = a
+ r.nFront++
+ }
+ if cap(r.back) > len(r.back) {
+ end := r.back[:len(r.back)+1][len(r.back)]
+ if !end.Equal(slog.Attr{}) {
+ panic("multiple copies of a slogx.AttrPack modified the shared state simultaneously, use Clone to avoid this")
+ }
+ }
+ ne := countEmptyGroups(attrs[i:])
+ r.back = slices.Grow(r.back, len(attrs[i:])-ne)
+ for _, a := range attrs[i:] {
+ if !isEmptyGroup(a.Value) {
+ r.back = append(r.back, a)
+ }
+ }
+}
+
+// Collect returns all the attributes in the AttrPack as a slice.
+func (r *AttrPack) Collect() []slog.Attr {
+ attrs := make([]slog.Attr, 0, r.Len())
+ r.Enumerate(func(a slog.Attr) bool {
+ attrs = append(attrs, a)
+
+ return true
+ })
+
+ return attrs
+}
+
+// ---
+
+func countEmptyGroups(as []slog.Attr) int {
+ n := 0
+ for _, a := range as {
+ if isEmptyGroup(a.Value) {
+ n++
+ }
+ }
+
+ return n
+}
+
+func isEmptyGroup(v slog.Value) bool {
+ if v.Kind() != slog.KindGroup {
+ return false
+ }
+
+ return len(v.Group()) == 0
+}
diff --git a/benchmark_test.go b/benchmark_test.go
new file mode 100644
index 0000000..3cd6222
--- /dev/null
+++ b/benchmark_test.go
@@ -0,0 +1,445 @@
+package slogx_test
+
+import (
+ "context"
+ "io"
+ "log/slog"
+ "testing"
+
+ "github.com/pamburus/slogx"
+)
+
+func BenchmarkLogger(b *testing.B) {
+ b.Run("slogx/AsIs", func(b *testing.B) {
+ benchmarkSLogXLogger(b, false)
+ })
+ b.Run("slogx/LongTerm", func(b *testing.B) {
+ benchmarkSLogXLogger(b, true)
+ })
+ b.Run("slog", benchmarkSLogLogger)
+}
+
+func benchmarkSLogXLogger(b *testing.B, longTerm bool) {
+ testEnabled := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("Enabled", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Enabled(context.Background(), slog.LevelInfo)
+ }
+ })
+ }
+
+ testLogAttrs := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("LogAttrs", func(b *testing.B) {
+ b.Run("NoAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg")
+ }
+ })
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ })
+ }
+
+ testWith := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("With", func(b *testing.B) {
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ b.Run("FiveAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ })
+ }
+
+ testWithLongTerm := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("With", func(b *testing.B) {
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ b.Run("FiveAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ })
+ }
+
+ testWithAndLog := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("LogWithAndLog", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testWithAndLogLongTerm := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("LogWithAndLog", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ logger.Log(slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testLogAfterWith := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("LogAfterWith", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testLogAfterWithLongTerm := func(b *testing.B, logger *slogx.Logger) {
+ b.Run("LogAfterWith", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ logger := logger.WithLongTerm(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Log(slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testAllForLogger := func(b *testing.B, logger *slogx.Logger) {
+ testEnabled(b, logger)
+ testLogAttrs(b, logger)
+ if longTerm {
+ testWithLongTerm(b, logger)
+ testWithAndLogLongTerm(b, logger)
+ testLogAfterWithLongTerm(b, logger)
+ } else {
+ testWith(b, logger)
+ testWithAndLog(b, logger)
+ testLogAfterWith(b, logger)
+ }
+ }
+
+ testWithSource := func(b *testing.B, handler slog.Handler, enabled bool) {
+ name := "WithSource"
+ if !enabled {
+ name = "WithoutSource"
+ }
+
+ b.Run(name, func(b *testing.B) {
+ b.Run("Unwrapped", func(b *testing.B) {
+ logger := slogx.New(handler).WithSource(enabled)
+ testAllForLogger(b, logger)
+ })
+
+ b.Run("3xWrapped", func(b *testing.B) {
+ handler = wrapHandlerN(handler, 3)
+ logger := slogx.New(handler).WithSource(enabled)
+ testAllForLogger(b, logger)
+ })
+ })
+ }
+
+ testAllForHandler := func(b *testing.B, handler slog.Handler) {
+ testWithSource(b, handler, false)
+ testWithSource(b, handler, true)
+ }
+
+ b.Run("Discard", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(b, slogx.Discard())
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(b, &enabledDiscardHandler{})
+ })
+ })
+
+ b.Run("JSON", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelError}))
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{}))
+ })
+ })
+}
+
+func benchmarkSLogLogger(b *testing.B) {
+ testEnabled := func(b *testing.B, logger *slog.Logger) {
+ b.Run("Enabled", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.Enabled(context.Background(), slog.LevelInfo)
+ }
+ })
+ }
+
+ testLogAttrs := func(b *testing.B, logger *slog.Logger) {
+ b.Run("LogAttrs", func(b *testing.B) {
+ b.Run("NoAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg")
+ }
+ })
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ })
+ }
+
+ testWith := func(b *testing.B, logger *slog.Logger) {
+ b.Run("With", func(b *testing.B) {
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ b.Run("FiveAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ })
+ }
+
+ testWithAndLog := func(b *testing.B, logger *slog.Logger) {
+ b.Run("WithAndLog", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"))
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testLogAfterWith := func(b *testing.B, logger *slog.Logger) {
+ b.Run("LogAfterWith", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ logger := logger.With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger.LogAttrs(context.Background(), slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testAllForLogger := func(b *testing.B, logger *slog.Logger) {
+ testEnabled(b, logger)
+ testLogAttrs(b, logger)
+ testWith(b, logger)
+ testWithAndLog(b, logger)
+ testLogAfterWith(b, logger)
+ }
+
+ testAllForHandler := func(b *testing.B, handler slog.Handler) {
+ b.Run("WithSource", func(b *testing.B) {
+ b.Run("Unwrapped", func(b *testing.B) {
+ logger := slog.New(handler)
+ testAllForLogger(b, logger)
+ })
+
+ b.Run("3xWrapped", func(b *testing.B) {
+ handler = wrapHandlerN(handler, 3)
+ logger := slog.New(handler)
+ testAllForLogger(b, logger)
+ })
+ })
+ }
+
+ b.Run("Discard", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(b, slogx.Discard())
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(b, &enabledDiscardHandler{})
+ })
+ })
+
+ b.Run("JSON", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelError}))
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{}))
+ })
+ })
+}
+
+// ---
+
+func wrapHandlerN(handler slog.Handler, times int) slog.Handler {
+ for i := 0; i != times; i++ {
+ handler = wrapHandler(handler)
+ }
+
+ return handler
+}
+
+func wrapHandler(handler slog.Handler) slog.Handler {
+ return &testHandlerWrapper{handler}
+}
+
+// ---
+
+type testHandlerWrapper struct {
+ base slog.Handler
+}
+
+func (h *testHandlerWrapper) Enabled(ctx context.Context, level slog.Level) bool {
+ return h.base.Enabled(ctx, level)
+}
+
+func (h *testHandlerWrapper) Handle(ctx context.Context, record slog.Record) error {
+ return h.base.Handle(ctx, record)
+}
+
+func (h *testHandlerWrapper) WithAttrs(attrs []slog.Attr) slog.Handler {
+ if len(attrs) == 0 {
+ return h
+ }
+
+ return &testHandlerWrapper{h.base.WithAttrs(attrs)}
+}
+
+func (h *testHandlerWrapper) WithGroup(key string) slog.Handler {
+ if key == "" {
+ return h
+ }
+
+ return &testHandlerWrapper{h.base.WithGroup(key)}
+}
+
+//---
+
+type enabledDiscardHandler struct{}
+
+func (h *enabledDiscardHandler) Enabled(context.Context, slog.Level) bool {
+ return true
+}
+
+func (h *enabledDiscardHandler) Handle(context.Context, slog.Record) error {
+ return nil
+}
+
+func (h *enabledDiscardHandler) WithAttrs([]slog.Attr) slog.Handler {
+ return h
+}
+
+func (h *enabledDiscardHandler) WithGroup(string) slog.Handler {
+ return h
+}
diff --git a/doc/benchmark/README.md b/doc/benchmark/README.md
new file mode 100644
index 0000000..c06f921
--- /dev/null
+++ b/doc/benchmark/README.md
@@ -0,0 +1,34 @@
+# Benchmark Results
+
+## Benchmarks of slogx.Logger using optimal selection of Logger.With or Logger.WithLongTerm method calls
+
+Performance of [slogx.Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) compared to [slog.Logger](https://pkg.go.dev/log/slog#Logger) with [slog.JSONHandler](https://pkg.go.dev/log/slog#JSONHandler) as a backend.
+Logger's [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) method is used in all LogAfterWith tests, as it is an optimal strategy for this use case.
+Values are in nanoseconds per operation.
+
+![Benchmark](logger/benchmark-lt-optimal.svg)
+
+## Benchmarks of slogx.Logger using only Logger.With method calls
+
+Performance of [slogx.Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) compared to [slog.Logger](https://pkg.go.dev/log/slog#Logger) with [slog.JSONHandler](https://pkg.go.dev/log/slog#JSONHandler) as a backend.
+Logger's [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) method is never used in these tests, so LogAfterWith tests have significant performance degradation.
+But at the same time in many other cases especially when the handler is disabled performance is significantly improved. For optimal performance use [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) when the resulting logger is planned to be used more than 3-4 times.
+Values are in nanoseconds per operation.
+
+![Benchmark](logger/benchmark-lt-never.svg)
+
+## Benchmarks of slogx.Logger using only Logger.WithLongTerm method calls
+
+Performance of [slogx.Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) compared to [slog.Logger](https://pkg.go.dev/log/slog#Logger) with [slog.JSONHandler](https://pkg.go.dev/log/slog#JSONHandler) as a backend.
+Logger's [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) method is always used in these tests, so LogAfterWith tests do not have significant performance degradation.
+But at the same time there is no significant performance improvements in other use cases as well. For optimal performance use [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) when the resulting logger is planned to be used more than 3-4 times.
+Values are in nanoseconds per operation.
+
+![Benchmark](logger/benchmark-lt-always.svg)
+
+## Benchmarks of usage context.Context with slogc package instead of slofx.Logger
+
+Performance comparison of [slogc](https://pkg.go.dev/github.com/pamburus/slogx/slogc) usage against [slogx.Logger](https://pkg.go.dev/github.com/pamburus/slogx#Logger) with optimal usage of [WithLongTerm](https://pkg.go.dev/github.com/pamburus/slogx#Logger.WithLongTerm) calls.
+Values are in nanoseconds per operation.
+
+![Benchmark](slogc/benchmark-slogc.svg)
diff --git a/doc/benchmark/logger/benchmark-lt-always.svg b/doc/benchmark/logger/benchmark-lt-always.svg
new file mode 100644
index 0000000..7bfae46
--- /dev/null
+++ b/doc/benchmark/logger/benchmark-lt-always.svg
@@ -0,0 +1,6387 @@
+
+
diff --git a/doc/benchmark/logger/benchmark-lt-never.svg b/doc/benchmark/logger/benchmark-lt-never.svg
new file mode 100644
index 0000000..8630404
--- /dev/null
+++ b/doc/benchmark/logger/benchmark-lt-never.svg
@@ -0,0 +1,6387 @@
+
+
diff --git a/doc/benchmark/logger/benchmark-lt-optimal.svg b/doc/benchmark/logger/benchmark-lt-optimal.svg
new file mode 100644
index 0000000..fdb5d86
--- /dev/null
+++ b/doc/benchmark/logger/benchmark-lt-optimal.svg
@@ -0,0 +1,6387 @@
+
+
diff --git a/doc/benchmark/logger/benchmark.log b/doc/benchmark/logger/benchmark.log
new file mode 100644
index 0000000..0bc6556
--- /dev/null
+++ b/doc/benchmark/logger/benchmark.log
@@ -0,0 +1,446 @@
+❯ GOMAXPROCS=8 go test -bench=. -benchmem . | tab
+goos: darwin
+goarch: arm64
+pkg: github.com/pamburus/slogx
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/Enabled-8 549055641 2.066 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 400409530 2.995 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.03 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 36792932 32.60 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-8 17381374 68.08 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 31074597 38.60 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 26637808 45.06 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 14870317 80.20 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.55 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 90591963 13.14 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 10.84 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/Enabled-8 320202615 3.755 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 213399945 5.649 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 90855746 13.28 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 36824076 32.56 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-8 17569180 69.13 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 28737552 42.40 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 25077736 49.10 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 14341586 83.73 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 79263949 14.32 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 76538338 16.03 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 89620791 13.58 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/Enabled-8 583866078 2.066 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 403382755 2.968 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.23 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 36678552 33.01 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 17477042 69.08 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 31251084 38.78 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 26480858 45.76 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 14723421 81.22 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.66 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 89273810 13.21 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 10.89 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/Enabled-8 317628094 3.781 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 212306133 5.662 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 90148462 13.43 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 36685372 32.97 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 17489566 69.28 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 29116083 41.58 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 24394126 48.53 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 14382589 83.16 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 80176612 14.25 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 75447772 15.98 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 89741432 13.56 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/Enabled-8 580163485 2.073 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 18524997 64.39 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 14019169 83.91 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 36953918 32.54 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-8 17370378 68.20 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 9024272 133.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 6206202 193.9 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 4683068 255.8 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 10952660 109.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 7784992 153.7 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 6880113 174.5 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/Enabled-8 320298660 3.749 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 14110528 85.06 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 12277412 98.09 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 36839384 32.61 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-8 17415481 68.51 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 7486712 160.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 5461039 217.7 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 4250336 280.7 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 9440074 127.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 6624410 180.5 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 5932606 202.9 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/Enabled-8 588590779 2.039 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 4482115 268.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 4057387 295.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 36552212 32.74 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 17421170 68.55 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 2863963 415.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 2416580 498.5 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 2023962 597.4 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 3145999 381.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 2605563 458.0 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 2350112 517.9 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/Enabled-8 319754073 3.749 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 4097119 294.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 3464898 320.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 36640708 32.80 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 17421433 68.37 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 2722924 441.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 2305526 520.8 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1936184 620.3 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 2963370 405.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 2490098 481.9 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 2243935 537.0 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/Enabled-8 466572812 2.536 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 294235437 4.083 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.82 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 36802243 32.60 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-8 17501086 68.25 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 30483254 39.38 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 25856868 46.35 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 14618427 81.95 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 95600702 12.51 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 84747007 14.23 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 11.73 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/Enabled-8 237579303 5.076 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 180647156 6.630 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 81797499 14.59 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 36894885 32.50 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-8 17569641 68.53 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 28598694 41.94 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 24269103 49.47 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 14198641 84.49 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 77833418 15.16 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 72366356 16.70 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 82881032 14.51 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/Enabled-8 479392288 2.573 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 295859811 4.072 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.83 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 36583599 32.71 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 17489598 68.37 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 30225081 39.68 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 25797435 46.56 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 14693209 81.78 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 97151564 12.62 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 83878930 14.26 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 11.74 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/Enabled-8 237172488 5.071 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 178091559 6.669 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 81212090 14.84 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 36417618 32.93 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 17336322 68.66 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 28617932 42.14 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 23679439 53.33 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 14149496 84.55 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 78819027 15.08 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 71845707 16.85 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 81493825 14.59 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/Enabled-8 513524780 2.246 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 4472592 271.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 2850897 420.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 36305830 32.69 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-8 17390589 68.58 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 2150720 559.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 1739182 691.8 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1488715 805.5 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 2300025 521.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 1845928 655.6 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 1659528 719.3 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/Enabled-8 292885356 4.094 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 4112197 291.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 2728689 439.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 36627800 32.67 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-8 17094108 68.26 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 2071437 579.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 1688011 713.6 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1417693 823.0 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 2196582 544.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 1783719 670.6 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 1629112 735.4 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/Enabled-8 528297097 2.279 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 2439686 491.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 1825096 660.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 36706320 32.58 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 17429530 68.20 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 1388660 866.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 1000000 1020 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1000000 1171 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 1449766 825.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 1227997 979.4 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 1000000 1087 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/Enabled-8 277280848 4.101 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 2309947 517.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 1747994 686.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 36805111 32.58 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 17422213 68.35 ns/op 48 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 1345963 890.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 1000000 1044 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1000000 1188 ns/op 176 B/op 2 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 1408926 851.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 1000000 1005 ns/op 80 B/op 1 allocs/op
+BenchmarkLogger/slogx/AsIs/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 1000000 1100 ns/op 128 B/op 1 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/Enabled-8 587702328 2.041 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 407506668 2.944 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.04 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 8508420 141.9 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-8 5818174 208.1 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 8606414 140.4 ns/op 304 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 7699959 156.5 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 5426044 220.8 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.17 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 84330606 14.23 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 11.75 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/Enabled-8 318777452 3.769 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 212265558 5.640 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 91536378 13.16 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 6053050 196.4 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-8 4543351 262.1 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 5932218 199.7 ns/op 352 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 5523777 216.7 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 4247532 281.0 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 90632728 13.20 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 72694779 16.38 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 85004148 13.89 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/Enabled-8 587754861 2.042 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 402857883 2.972 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.05 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 8499150 140.7 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 5785545 207.0 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 8617557 140.6 ns/op 304 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 7682854 157.1 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 5428840 222.8 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.20 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 84394862 14.18 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 11.73 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/Enabled-8 317201541 3.768 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 212381908 5.643 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 90195324 13.32 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 6098727 197.6 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 4564699 264.3 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 5969928 199.8 ns/op 352 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 5542296 216.4 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 4262991 282.0 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 90620751 13.24 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 72974017 16.39 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 86109720 13.91 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/Enabled-8 587714799 2.042 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 19021966 63.47 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 14501232 82.81 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 8455161 142.3 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-8 5787733 207.1 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 5492521 219.6 ns/op 304 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 5032946 237.7 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 3976126 302.6 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 14409596 83.52 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 15092716 79.54 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 15642472 76.74 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/Enabled-8 316597177 3.764 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 14035908 84.97 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 12214708 98.22 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 6086157 197.5 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-8 4541872 264.9 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 4010720 299.0 ns/op 352 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 3743690 320.6 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 3129361 383.4 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 12276878 97.70 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 11549455 104.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 12226604 98.19 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/Enabled-8 588486265 2.041 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 4471360 268.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 4053648 294.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 8463609 141.5 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 5788839 207.8 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 2496350 481.0 ns/op 304 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 2254041 530.5 ns/op 352 B/op 2 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1898276 631.8 ns/op 480 B/op 3 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 3693012 327.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 3301468 362.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 3082455 389.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/Enabled-8 318359668 3.766 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 4089634 293.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 3748119 320.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 6032706 197.1 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 4535682 264.6 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 2116975 566.7 ns/op 352 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 1938192 617.8 ns/op 400 B/op 5 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1672126 720.8 ns/op 528 B/op 6 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 3433864 350.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 3100107 386.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 2901588 414.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/Enabled-8 470406453 2.599 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 293365478 4.108 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.84 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 2877673 418.6 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-8 2028979 587.5 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 3128544 380.4 ns/op 512 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 2765625 435.8 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1972912 607.2 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 99448540 12.04 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 77248659 15.43 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 94524166 12.60 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/Enabled-8 230797099 5.182 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 181159340 6.632 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 81881684 14.67 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 2519102 476.8 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-8 1857826 650.2 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 2726487 442.1 ns/op 560 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 2406294 498.3 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1786905 672.5 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 81468468 14.67 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 68892601 17.48 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 78085814 15.27 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/Enabled-8 483997171 2.570 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 293920978 4.092 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 99788638 11.95 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 2850048 416.9 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 2045512 592.1 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 3159486 380.7 ns/op 512 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 2743636 435.2 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1980310 608.4 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 99549950 12.05 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 77512910 15.49 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 94118878 12.72 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/Enabled-8 236846935 5.077 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 180159530 6.698 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 82637991 14.54 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 2510024 475.9 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 1860517 649.3 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 2707279 439.6 ns/op 560 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 2410690 497.2 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1789221 671.6 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 81325838 14.63 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 69011294 17.48 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 78603477 15.29 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/Enabled-8 530403344 2.255 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogAttrs/NoAttrs-8 4464576 268.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogAttrs/ThreeAttrs-8 2875759 417.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-8 2891575 418.7 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-8 2042058 590.0 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 1468304 818.6 ns/op 512 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 1319166 908.5 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 1000000 1047 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 2809728 427.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 2585706 463.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 2813330 426.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/Enabled-8 293258505 4.075 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogAttrs/NoAttrs-8 4100019 292.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogAttrs/ThreeAttrs-8 2729404 439.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-8 2516917 473.8 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-8 1841642 650.1 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 1336000 898.7 ns/op 560 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 1216598 986.7 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 1000000 1129 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 2682258 447.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 2456256 488.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 2675780 449.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/Enabled-8 531470430 2.251 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 2438672 494.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 1824122 658.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 2884052 419.0 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 2032161 587.8 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-8 1000000 1108 ns/op 512 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-8 974899 1223 ns/op 560 B/op 7 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-8 848906 1397 ns/op 752 B/op 9 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 1711375 701.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 1547506 772.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 1573183 759.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/Enabled-8 290195532 4.130 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 2316030 515.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 1754522 685.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 2516814 476.0 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 1841646 649.6 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-8 991912 1187 ns/op 560 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-8 887488 1317 ns/op 608 B/op 10 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-8 803221 1484 ns/op 800 B/op 12 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 1642080 744.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 1465856 812.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slogx/LongTerm/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 1492964 806.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/Enabled-8 594147277 2.004 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 370258758 3.265 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 10.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 5789076 206.3 ns/op 448 B/op 7 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 3624445 324.3 ns/op 864 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/WithAndLog/TwoAndThreeAttrs-8 8450624 141.2 ns/op 240 B/op 5 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/WithAndLog/ThreeAndFourAttrs-8 5429814 219.0 ns/op 448 B/op 7 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/WithAndLog/FiveAndThreeAttrs-8 3540992 336.0 ns/op 864 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.01 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 81064648 14.45 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 10.98 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/Enabled-8 255787911 4.689 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 202358842 5.943 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 89179275 13.44 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 4488261 265.8 ns/op 496 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 3171129 381.9 ns/op 912 B/op 13 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/WithAndLog/TwoAndThreeAttrs-8 6056856 198.0 ns/op 288 B/op 8 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/WithAndLog/ThreeAndFourAttrs-8 4350814 276.3 ns/op 496 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/WithAndLog/FiveAndThreeAttrs-8 3093800 388.0 ns/op 912 B/op 13 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 89087414 13.52 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 71156790 16.86 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 88464860 13.42 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/Enabled-8 593307247 2.027 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 4616235 259.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 4005786 299.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 5818544 206.0 ns/op 448 B/op 7 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 3680156 324.0 ns/op 864 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/WithAndLog/TwoAndThreeAttrs-8 2552293 472.1 ns/op 240 B/op 5 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/WithAndLog/ThreeAndFourAttrs-8 2063244 582.0 ns/op 448 B/op 7 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/WithAndLog/FiveAndThreeAttrs-8 1656854 730.7 ns/op 864 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 3653109 328.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 3286243 365.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 3068613 390.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/Enabled-8 256875912 4.671 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 4200636 285.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 3677293 326.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 4611499 260.9 ns/op 496 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 3174752 377.3 ns/op 912 B/op 13 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/WithAndLog/TwoAndThreeAttrs-8 2166379 554.5 ns/op 288 B/op 8 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/WithAndLog/ThreeAndFourAttrs-8 1810603 668.4 ns/op 496 B/op 10 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/WithAndLog/FiveAndThreeAttrs-8 1484910 804.3 ns/op 912 B/op 13 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 3389518 355.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 3072781 390.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 2865369 417.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/Enabled-8 415956975 2.890 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 281147809 4.310 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 100000000 11.97 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/With/ThreeAttrs-8 2541174 468.8 ns/op 656 B/op 12 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/With/FiveAttrs-8 1731087 693.7 ns/op 1136 B/op 16 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/WithAndLog/TwoAndThreeAttrs-8 3226952 371.6 ns/op 448 B/op 10 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/WithAndLog/ThreeAndFourAttrs-8 2444688 491.5 ns/op 656 B/op 12 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/WithAndLog/FiveAndThreeAttrs-8 1700746 710.1 ns/op 1136 B/op 16 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 100000000 11.90 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 76323132 15.66 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 100000000 11.92 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/Enabled-8 213524444 5.622 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 175090984 6.860 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 82043803 14.67 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/With/ThreeAttrs-8 2278458 524.5 ns/op 704 B/op 15 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/With/FiveAttrs-8 1601876 744.5 ns/op 1184 B/op 19 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/WithAndLog/TwoAndThreeAttrs-8 2804304 430.4 ns/op 496 B/op 13 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/WithAndLog/ThreeAndFourAttrs-8 2184435 551.5 ns/op 704 B/op 15 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/WithAndLog/FiveAndThreeAttrs-8 1560952 767.6 ns/op 1184 B/op 19 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 81010831 14.95 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 66942958 17.76 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 82719872 14.73 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/Enabled-8 550210532 2.185 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/LogAttrs/NoAttrs-8 2480950 481.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/LogAttrs/ThreeAttrs-8 1810926 662.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/With/ThreeAttrs-8 2566536 469.0 ns/op 656 B/op 12 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/With/FiveAttrs-8 1739553 689.0 ns/op 1136 B/op 16 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/WithAndLog/TwoAndThreeAttrs-8 1000000 1078 ns/op 448 B/op 10 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/WithAndLog/ThreeAndFourAttrs-8 946154 1277 ns/op 656 B/op 12 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/WithAndLog/FiveAndThreeAttrs-8 802561 1500 ns/op 1136 B/op 16 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-8 1730617 693.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-8 1554423 772.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-8 1588707 758.7 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/Enabled-8 240605482 4.985 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/LogAttrs/NoAttrs-8 2382015 502.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/LogAttrs/ThreeAttrs-8 1753304 684.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/With/ThreeAttrs-8 2285960 524.9 ns/op 704 B/op 15 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/With/FiveAttrs-8 1607612 747.0 ns/op 1184 B/op 19 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/WithAndLog/TwoAndThreeAttrs-8 1000000 1163 ns/op 496 B/op 13 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/WithAndLog/ThreeAndFourAttrs-8 888649 1364 ns/op 704 B/op 15 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/WithAndLog/FiveAndThreeAttrs-8 751208 1579 ns/op 1184 B/op 19 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-8 1666695 719.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-8 1512511 792.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogger/slog/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-8 1532436 781.3 ns/op 0 B/op 0 allocs/op
+PASS
+ok github.com/pamburus/slogx 650.935s
\ No newline at end of file
diff --git a/doc/benchmark/slogc/benchmark-slogc.log b/doc/benchmark/slogc/benchmark-slogc.log
new file mode 100644
index 0000000..3f5cdd1
--- /dev/null
+++ b/doc/benchmark/slogc/benchmark-slogc.log
@@ -0,0 +1,183 @@
+Running tool: go test -benchmem -run=^$ -bench ^BenchmarkLogging$ github.com/pamburus/slogx/slogc
+
+goos: darwin
+goarch: arm64
+pkg: github.com/pamburus/slogx/slogc
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/Enabled-10 302408304 3.797 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/Log/NoAttrs-10 157477438 7.503 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/Log/ThreeAttrs-10 78319825 15.28 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-10 6996752 172.1 ns/op 400 B/op 3 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-10 5013043 239.0 ns/op 528 B/op 4 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 28902226 41.46 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 24543411 48.82 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 14250596 84.07 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 77948863 15.37 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 64540129 18.68 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 73791470 16.07 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/Enabled-10 181182806 6.623 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/Log/NoAttrs-10 100000000 10.03 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/Log/ThreeAttrs-10 67012588 17.77 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-10 5278203 224.5 ns/op 448 B/op 6 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-10 4088248 293.0 ns/op 576 B/op 7 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 27028928 44.47 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 23343142 51.33 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 13770817 88.89 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 56577974 20.94 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 56817844 21.40 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 55630888 19.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/Enabled-10 321027108 3.741 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/Log/NoAttrs-10 160871287 7.453 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/Log/ThreeAttrs-10 78076710 15.24 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/With/ThreeAttrs-10 7012234 179.3 ns/op 400 B/op 3 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/With/FiveAttrs-10 4823457 243.7 ns/op 528 B/op 4 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 28433266 41.68 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 24584601 49.14 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 14195016 85.28 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 77999529 15.30 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 64907113 18.43 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 73048242 15.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/Enabled-10 179969494 6.654 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/Log/NoAttrs-10 122238222 9.812 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/Log/ThreeAttrs-10 67251379 17.84 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/With/ThreeAttrs-10 5292042 225.7 ns/op 448 B/op 6 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/With/FiveAttrs-10 4111309 293.8 ns/op 576 B/op 7 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 27066600 44.39 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 23373037 51.39 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 13711099 87.04 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 67181413 19.19 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 56805180 21.19 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 56811120 20.14 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/Enabled-10 320391802 3.746 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/Log/NoAttrs-10 18869320 63.66 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/Log/ThreeAttrs-10 15204692 79.07 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-10 7060116 170.4 ns/op 400 B/op 3 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-10 5051988 237.8 ns/op 528 B/op 4 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 8759362 137.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 6007807 200.1 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 4551891 261.7 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 15221882 78.74 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 14006725 85.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 15246370 80.04 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/Enabled-10 179932008 6.677 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/Log/NoAttrs-10 13658312 88.27 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/Log/ThreeAttrs-10 11387404 104.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-10 5350111 224.5 ns/op 448 B/op 6 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-10 4105117 299.3 ns/op 576 B/op 7 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 7371069 164.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 5184034 221.6 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 4147477 288.9 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 11396443 105.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 10680230 111.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 11351793 105.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/Enabled-10 320614688 3.765 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/Log/NoAttrs-10 4063066 295.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/Log/ThreeAttrs-10 3460381 346.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/With/ThreeAttrs-10 6959058 172.0 ns/op 400 B/op 3 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/With/FiveAttrs-10 5031410 238.4 ns/op 528 B/op 4 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 2872258 420.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 2407125 497.1 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 1991977 604.2 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 3307240 362.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 3063679 391.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 3059085 392.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/Enabled-10 182764054 6.579 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/Log/NoAttrs-10 3745584 326.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/Log/ThreeAttrs-10 3233496 370.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/With/ThreeAttrs-10 5287141 224.7 ns/op 448 B/op 6 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/With/FiveAttrs-10 4043258 293.1 ns/op 576 B/op 7 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 2705053 449.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 2292948 527.6 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 1823280 635.8 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 3071106 392.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 2851533 421.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/Discard/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 2805516 425.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/Enabled-10 221369793 5.410 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/Log/NoAttrs-10 133056931 8.917 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/Log/ThreeAttrs-10 73210350 16.42 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/With/ThreeAttrs-10 2603835 467.6 ns/op 608 B/op 8 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/With/FiveAttrs-10 1867736 638.9 ns/op 800 B/op 10 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 28106006 42.78 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 24151105 49.89 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 13905579 86.30 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 73311171 16.33 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 60841513 19.83 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 70795938 16.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/Enabled-10 142398338 8.359 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/Log/NoAttrs-10 100000000 10.99 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/Log/ThreeAttrs-10 62287375 18.90 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/With/ThreeAttrs-10 2340777 511.6 ns/op 656 B/op 11 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/With/FiveAttrs-10 1727594 712.5 ns/op 848 B/op 13 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 26753266 45.01 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 22806172 52.29 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 13534514 88.72 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 64370343 18.57 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 53694178 22.31 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 61154548 19.80 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/Enabled-10 221955276 5.390 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/Log/NoAttrs-10 134528506 9.001 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/Log/ThreeAttrs-10 73236040 16.40 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/With/ThreeAttrs-10 2641196 455.4 ns/op 608 B/op 8 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/With/FiveAttrs-10 1872402 641.0 ns/op 800 B/op 10 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 27977896 42.80 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 24107882 49.89 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 13871181 86.36 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 73316205 16.30 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 60876106 19.81 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 70466990 16.97 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/Enabled-10 142236157 8.382 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/Log/NoAttrs-10 100000000 11.12 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/Log/ThreeAttrs-10 62619177 19.19 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/With/ThreeAttrs-10 2337238 518.1 ns/op 656 B/op 11 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/With/FiveAttrs-10 1720125 704.2 ns/op 848 B/op 13 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 26727421 44.97 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 22960372 52.54 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 13569025 88.78 ns/op 48 B/op 1 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 64280836 18.60 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 54039144 22.29 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Disabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 60986455 19.58 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/Enabled-10 275437646 4.403 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/Log/NoAttrs-10 4315434 278.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/Log/ThreeAttrs-10 2785440 431.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/With/ThreeAttrs-10 2613122 454.8 ns/op 608 B/op 8 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/With/FiveAttrs-10 1875732 637.0 ns/op 800 B/op 10 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 2122630 565.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 1720545 696.8 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 1473368 814.9 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 2740965 442.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 2456793 488.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 2749677 440.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/Enabled-10 160578510 7.422 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/Log/NoAttrs-10 3986242 299.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/Log/ThreeAttrs-10 2704306 444.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/With/ThreeAttrs-10 2327130 512.8 ns/op 656 B/op 11 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/With/FiveAttrs-10 1721772 695.2 ns/op 848 B/op 13 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 2045834 586.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 1675624 716.4 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 1426124 839.6 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 2649457 455.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 2405959 511.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithoutSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 2620563 453.8 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/Enabled-10 269167129 4.466 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/Log/NoAttrs-10 2302797 522.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/Log/ThreeAttrs-10 1651009 726.0 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/With/ThreeAttrs-10 2635567 455.0 ns/op 608 B/op 8 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/With/FiveAttrs-10 1878526 638.6 ns/op 800 B/op 10 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/TwoAndThreeAttrs-10 1366870 876.6 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/ThreeAndFourAttrs-10 1000000 1029 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogWithAndLog/FiveAndThreeAttrs-10 1000000 1191 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/TwoAndThreeAttrs-10 1637605 734.2 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/ThreeAndFourAttrs-10 1465765 819.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/Unwrapped/LogAfterWith/FiveAndThreeAttrs-10 1557883 769.3 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/Enabled-10 159909810 7.505 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/Log/NoAttrs-10 2187394 548.9 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/Log/ThreeAttrs-10 1624827 739.5 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/With/ThreeAttrs-10 2339976 512.8 ns/op 656 B/op 11 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/With/FiveAttrs-10 1724787 695.0 ns/op 848 B/op 13 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/TwoAndThreeAttrs-10 1333200 898.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/ThreeAndFourAttrs-10 1000000 1047 ns/op 80 B/op 1 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogWithAndLog/FiveAndThreeAttrs-10 993201 1205 ns/op 176 B/op 2 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/TwoAndThreeAttrs-10 1577520 761.1 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/ThreeAndFourAttrs-10 1411441 895.4 ns/op 0 B/op 0 allocs/op
+BenchmarkLogging/JSON/Enabled/WithSource/3xWrapped/LogAfterWith/FiveAndThreeAttrs-10 1503277 806.8 ns/op 0 B/op 0 allocs/op
+PASS
+ok github.com/pamburus/slogx/slogc 266.852s
\ No newline at end of file
diff --git a/doc/benchmark/slogc/benchmark-slogc.svg b/doc/benchmark/slogc/benchmark-slogc.svg
new file mode 100644
index 0000000..0162265
--- /dev/null
+++ b/doc/benchmark/slogc/benchmark-slogc.svg
@@ -0,0 +1,6387 @@
+
+
diff --git a/example/longterm/main.go b/example/longterm/main.go
new file mode 100644
index 0000000..65ca82b
--- /dev/null
+++ b/example/longterm/main.go
@@ -0,0 +1,16 @@
+// Package main provides an example of using the slogx logger.
+package main
+
+import (
+ "log/slog"
+ "os"
+
+ "github.com/pamburus/slogx"
+)
+
+func main() {
+ logger := slogx.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}))
+
+ l1 := logger.With(slog.String("a", "av"), slog.String("b", "bv")).LongTerm()
+ l1.Log(slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..63eb084
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/pamburus/slogx
+
+go 1.21.5
+
+require github.com/pamburus/go-tst v0.2.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..10ec275
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/pamburus/go-tst v0.2.0 h1:C6C0l6N/DXeGfX9vUNMDvko3i7ApUkundDUQK3bSKP4=
+github.com/pamburus/go-tst v0.2.0/go.mod h1:Jbmc83QXCwwfX4E2HhZ8n7tVM8tHbs6RhIl1CdayMls=
diff --git a/handler.go b/handler.go
new file mode 100644
index 0000000..887e8fa
--- /dev/null
+++ b/handler.go
@@ -0,0 +1,176 @@
+package slogx
+
+import (
+ "context"
+ "errors"
+ "log/slog"
+)
+
+// Join returns a new handler that joins the provided handlers.
+func Join(handlers ...slog.Handler) slog.Handler {
+ switch len(handlers) {
+ case 0:
+ return Discard()
+ case 1:
+ return handlers[0]
+ }
+
+ return &multiHandler{handlers}
+}
+
+// Discard returns a handler that discards all log records.
+func Discard() slog.Handler {
+ return discardHandlerInstance
+}
+
+// TweakHandler returns a builder for a new handler based on existing handler.
+func TweakHandler(handler slog.Handler) TweakHandlerBuilder {
+ return TweakHandlerBuilder{handler, handlerTweaks{}}
+}
+
+// ---
+
+// TweakHandlerBuilder is a builder for a new handler based on existing handler.
+type TweakHandlerBuilder struct {
+ handler slog.Handler
+ tweaks handlerTweaks
+}
+
+// WithDynamicAttr adds a dynamic attribute to the handler.
+func (b TweakHandlerBuilder) WithDynamicAttr(attr func(context.Context) slog.Attr) TweakHandlerBuilder {
+ b.tweaks.dynamicAttrs = append(b.tweaks.dynamicAttrs, attr)
+
+ return b
+}
+
+// Result returns the new handler.
+func (b TweakHandlerBuilder) Result() slog.Handler {
+ return &tweakedHandler{b.handler, b.tweaks}
+}
+
+// ---
+
+type handlerTweaks struct {
+ dynamicAttrs []func(context.Context) slog.Attr
+}
+
+// ---
+
+type tweakedHandler struct {
+ base slog.Handler
+ handlerTweaks
+}
+
+func (h *tweakedHandler) Enabled(ctx context.Context, level slog.Level) bool {
+ return h.base.Enabled(ctx, level)
+}
+
+func (h *tweakedHandler) Handle(ctx context.Context, record slog.Record) error {
+ if len(h.dynamicAttrs) != 0 {
+ record = record.Clone()
+ for _, attr := range h.dynamicAttrs {
+ if attr := attr(ctx); !attr.Equal(slog.Attr{}) {
+ record.AddAttrs(attr)
+ }
+ }
+ }
+
+ return h.base.Handle(ctx, record)
+}
+
+func (h *tweakedHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ if len(attrs) == 0 {
+ return h
+ }
+
+ return &tweakedHandler{h.base.WithAttrs(attrs), h.handlerTweaks}
+}
+
+func (h *tweakedHandler) WithGroup(key string) slog.Handler {
+ if key == "" {
+ return h
+ }
+
+ return &tweakedHandler{h.base.WithGroup(key), h.handlerTweaks}
+}
+
+// ---
+
+var discardHandlerInstance = &discardHandler{}
+
+// ---
+
+type discardHandler struct{}
+
+func (h *discardHandler) Enabled(context.Context, slog.Level) bool {
+ return false
+}
+
+func (h *discardHandler) Handle(context.Context, slog.Record) error {
+ return nil
+}
+
+func (h *discardHandler) WithAttrs([]slog.Attr) slog.Handler {
+ return h
+}
+
+func (h *discardHandler) WithGroup(string) slog.Handler {
+ return h
+}
+
+// ---
+
+type multiHandler struct {
+ handlers []slog.Handler
+}
+
+func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
+ for _, handler := range h.handlers {
+ if handler.Enabled(ctx, level) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (h *multiHandler) Handle(ctx context.Context, record slog.Record) error {
+ var errs []error
+
+ for _, handler := range h.handlers {
+ if handler.Enabled(ctx, record.Level) {
+ err := handler.Handle(ctx, record)
+ if err != nil {
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ return errors.Join(errs...)
+}
+
+func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ if len(attrs) == 0 {
+ return h
+ }
+
+ handlers := make([]slog.Handler, len(h.handlers))
+ for i, handler := range h.handlers {
+ handlers[i] = handler.WithAttrs(attrs)
+ }
+
+ return &multiHandler{handlers}
+}
+
+func (h *multiHandler) WithGroup(key string) slog.Handler {
+ if key == "" {
+ return h
+ }
+
+ handlers := make([]slog.Handler, len(h.handlers))
+ for i, handler := range h.handlers {
+ handlers[i] = handler.WithGroup(key)
+ }
+
+ return &multiHandler{handlers}
+}
diff --git a/internal/mock/attr.go b/internal/mock/attr.go
new file mode 100644
index 0000000..6dcd3b1
--- /dev/null
+++ b/internal/mock/attr.go
@@ -0,0 +1,42 @@
+package mock
+
+import (
+ "log/slog"
+)
+
+// NewAttrs returns a new slice of [Attr] based on the given slice of [slog.Attr].
+func NewAttrs(attrs []slog.Attr) []Attr {
+ return AttrsUsingFunc(len(attrs), func(fn func(slog.Attr)) {
+ for _, a := range attrs {
+ fn(a)
+ }
+ })
+}
+
+// AttrsUsingFunc returns a new slice of [Attr] based on the given number and function to get next [slog.Attr].
+func AttrsUsingFunc(n int, fn func(func(slog.Attr))) []Attr {
+ if n == 0 {
+ return nil
+ }
+
+ testAttrs := make([]Attr, 0, n)
+ fn(func(a slog.Attr) {
+ testAttrs = append(testAttrs, Attr{
+ Key: a.Key,
+ Value: a.Value.Any(),
+ })
+ })
+
+ return testAttrs
+}
+
+// NewAttr returns a new [Attr] based on the given key and value.
+func NewAttr(key string, value any) Attr {
+ return Attr{Key: key, Value: value}
+}
+
+// Attr is a mockable representation of [slog.Attr].
+type Attr struct {
+ Key string
+ Value any
+}
diff --git a/internal/mock/calllog.go b/internal/mock/calllog.go
new file mode 100644
index 0000000..8990686
--- /dev/null
+++ b/internal/mock/calllog.go
@@ -0,0 +1,56 @@
+package mock
+
+import (
+ "slices"
+ "sync"
+)
+
+// NewCallLog returns a new [CallLog].
+func NewCallLog() *CallLog {
+ return &CallLog{}
+}
+
+// CallLog is a log of method of function calls that can be used to build assertions.
+type CallLog struct {
+ mu sync.Mutex
+ calls CallList
+}
+
+// Calls returns all recorded calls in the log.
+func (l *CallLog) Calls() CallList {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ return slices.Clone(l.calls)
+}
+
+func (l *CallLog) append(call any) int {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ l.calls = append(l.calls, call)
+
+ return len(l.calls)
+}
+
+// ---
+
+// CallList is a list of recorded calls.
+type CallList []any
+
+// WithoutTime returns a new [CallList] with all time fields set to zero.
+func (l CallList) WithoutTime() CallList {
+ l = slices.Clone(l)
+
+ for i := range l {
+ if item, ok := l[i].(cloner); ok {
+ l[i] = item.clone()
+ }
+
+ if item, ok := l[i].(timeRemover); ok {
+ l[i] = item.withoutTime()
+ }
+ }
+
+ return l
+}
diff --git a/internal/mock/handler.go b/internal/mock/handler.go
new file mode 100644
index 0000000..aaf66d6
--- /dev/null
+++ b/internal/mock/handler.go
@@ -0,0 +1,120 @@
+// Package mock provides a mock slog.Handler implementation and other helpers.
+package mock
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "slices"
+)
+
+// ---
+
+// NewHandler returns a new [Handler] with the given [CallLog].
+func NewHandler(log *CallLog) *Handler {
+ return &Handler{
+ instance: "0",
+ log: log,
+ }
+}
+
+// ---
+
+// Handler is a mockable representation of [slog.Handler].
+type Handler struct {
+ instance string
+ log *CallLog
+}
+
+// Enabled records the call and returns true.
+func (h Handler) Enabled(_ context.Context, level slog.Level) bool {
+ h.log.append(HandlerEnabled{h.instance, level})
+
+ return true
+}
+
+// Handle records the call.
+func (h Handler) Handle(_ context.Context, record slog.Record) error {
+ h.log.append(HandlerHandle{h.instance, NewRecord(record)})
+
+ return nil
+}
+
+// WithAttrs records the call and returns a new [Handler] with the given attributes.
+func (h Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ h.instance = h.newInstance(
+ h.log.append(HandlerWithAttrs{h.instance, NewAttrs(attrs)}),
+ )
+
+ return &h
+}
+
+// WithGroup records the call and returns a new [Handler] with the given group.
+func (h Handler) WithGroup(group string) slog.Handler {
+ h.instance = h.newInstance(
+ h.log.append(HandlerWithGroup{h.instance, group}),
+ )
+
+ return &h
+}
+
+func (h Handler) newInstance(n int) string {
+ return fmt.Sprintf("%s.%d", h.instance, n)
+}
+
+// ---
+
+// HandlerEnabled is a representation of a call to [slog.Handler.Enabled]
+// that can be used to compare against recorded calls in a [CallLog].
+type HandlerEnabled struct {
+ Instance string
+ Level slog.Level
+}
+
+// HandlerHandle is a representation of a call to [slog.Handler.Handle]
+// that can be used to compare against recorded calls in a [CallLog].
+type HandlerHandle struct {
+ Instance string
+ Record Record
+}
+
+func (h HandlerHandle) clone() any {
+ h.Record = h.Record.clone().(Record)
+
+ return h
+}
+
+func (h HandlerHandle) withoutTime() any {
+ h.Record = h.Record.WithoutTime()
+
+ return h
+}
+
+// HandlerWithAttrs is a representation of a call to [slog.Handler.WithAttrs]
+// that can be used to compare against recorded calls in a [CallLog].
+type HandlerWithAttrs struct {
+ Instance string
+ Attrs []Attr
+}
+
+func (h HandlerWithAttrs) clone() any {
+ h.Attrs = slices.Clone(h.Attrs)
+
+ return h
+}
+
+// HandlerWithGroup is a representation of a call to [slog.Handler.WithGroup]
+// that can be used to compare against recorded calls in a [CallLog].
+type HandlerWithGroup struct {
+ Instance string
+ Key string
+}
+
+// ---
+
+var (
+ _ slog.Handler = Handler{}
+ _ cloner = (*HandlerHandle)(nil)
+ _ timeRemover = (*HandlerHandle)(nil)
+ _ cloner = (*HandlerWithAttrs)(nil)
+)
diff --git a/internal/mock/record.go b/internal/mock/record.go
new file mode 100644
index 0000000..e09cf8c
--- /dev/null
+++ b/internal/mock/record.go
@@ -0,0 +1,58 @@
+package mock
+
+import (
+ "log/slog"
+ "slices"
+ "time"
+)
+
+// NewRecord returns a new [Record] based on the given [slog.Record].
+func NewRecord(record slog.Record) Record {
+ return Record{
+ Time: record.Time,
+ Message: record.Message,
+ Level: record.Level,
+ PC: record.PC,
+ Attrs: recordAttrs(record),
+ }
+}
+
+// Record is a mockable representation of [slog.Record].
+type Record struct {
+ Time time.Time
+ Message string
+ Level slog.Level
+ PC uintptr
+ Attrs []Attr
+}
+
+// WithoutTime returns a copy of [Record] with [Record.Time] set to zero.
+func (r Record) WithoutTime() Record {
+ r.Time = time.Time{}
+
+ return r
+}
+
+func (r Record) clone() any {
+ r.Attrs = slices.Clone(r.Attrs)
+
+ return r
+}
+
+// ---
+
+func recordAttrs(record slog.Record) []Attr {
+ return AttrsUsingFunc(record.NumAttrs(), func(fn func(slog.Attr)) {
+ record.Attrs(func(a slog.Attr) bool {
+ fn(a)
+
+ return true
+ })
+ })
+}
+
+// ---
+
+var (
+ _ cloner = (*Record)(nil)
+)
diff --git a/internal/mock/util.go b/internal/mock/util.go
new file mode 100644
index 0000000..70e0e8a
--- /dev/null
+++ b/internal/mock/util.go
@@ -0,0 +1,9 @@
+package mock
+
+type cloner interface {
+ clone() any
+}
+
+type timeRemover interface {
+ withoutTime() any
+}
diff --git a/logger.go b/logger.go
new file mode 100644
index 0000000..633672e
--- /dev/null
+++ b/logger.go
@@ -0,0 +1,427 @@
+// Package slogx provides extensions to the [slog] package.
+// It focuses on performance and simplicity.
+// Only functions working with [slog.Attr] are provided.
+// Any slower alternatives are not supported.
+package slogx
+
+import (
+ "context"
+ "log/slog"
+ "runtime"
+ "time"
+)
+
+// New returns a new [Logger] with the given handler.
+func New(handler slog.Handler) *Logger {
+ return &Logger{commonLogger{
+ handler: handler,
+ src: true,
+ }}
+}
+
+// NewContextLogger returns a new [ContextLogger] with the given handler.
+func NewContextLogger(handler slog.Handler) *ContextLogger {
+ return &ContextLogger{commonLogger{
+ handler: handler,
+ src: true,
+ }}
+}
+
+// Default returns a new [Logger] with the default handler from [slog.Default].
+func Default() *Logger {
+ return New(defaultHandler())
+}
+
+// With returns a new [Logger] based on [Default] with the given attributes.
+func With(attrs ...slog.Attr) *Logger {
+ return Default().With(attrs...)
+}
+
+// WithGroup returns a new [Logger] based on [Default] with the given group.
+func WithGroup(group string) *Logger {
+ return Default().WithGroup(group)
+}
+
+// Debug logs a message at the debug level.
+func Debug(msg string, attrs ...slog.Attr) {
+ logAttrs(context.Background(), defaultHandler(), slog.LevelDebug, msg, attrs)
+}
+
+// Info logs a message at the info level.
+func Info(msg string, attrs ...slog.Attr) {
+ logAttrs(context.Background(), defaultHandler(), slog.LevelInfo, msg, attrs)
+}
+
+// Warn logs a message at the warn level.
+func Warn(msg string, attrs ...slog.Attr) {
+ logAttrs(context.Background(), defaultHandler(), slog.LevelWarn, msg, attrs)
+}
+
+// Error logs a message at the error level.
+func Error(msg string, attrs ...slog.Attr) {
+ logAttrs(context.Background(), defaultHandler(), slog.LevelError, msg, attrs)
+}
+
+// Log logs a message at the given level.
+func Log(level slog.Level, msg string, attrs ...slog.Attr) {
+ logAttrs(context.Background(), defaultHandler(), level, msg, attrs)
+}
+
+// ---
+
+// Logger is a simple logger that logs to a [slog.Handler].
+// It is an alternative to [slog.Logger] focused on performance and simplicity.
+// It forces to use [slog.Attr] for log attributes and does not support slow alternatives provided by [slog.Logger].
+// It also takes [slog.Attr] in [Logger.With] because it is the only high performance way to add attributes.
+type Logger struct {
+ commonLogger
+}
+
+// Handler returns the logger's handler.
+func (l *Logger) Handler() slog.Handler {
+ return l.handlerForExport()
+}
+
+// SlogLogger returns a new [slog.Logger] that logs to the associated handler.
+func (l *Logger) SlogLogger() *slog.Logger {
+ return slog.New(l.handler)
+}
+
+// ContextLogger returns a new [ContextLogger] that takes context in logging methods.
+func (l *Logger) ContextLogger() *ContextLogger {
+ return &ContextLogger{l.commonLogger}
+}
+
+// Enabled returns true if the given level is enabled.
+func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
+ return l.handler.Enabled(ctx, level)
+}
+
+// With returns a new [Logger] with the given attributes optimized for short usage (one or two times).
+func (l *Logger) With(attrs ...slog.Attr) *Logger {
+ if len(attrs) != 0 {
+ l = l.clone()
+ l.setWithAttrs(attrs)
+ }
+
+ return l
+}
+
+// WithLongTerm returns a new [Logger] with the given attributes optimized for long usage.
+func (l *Logger) WithLongTerm(attrs ...slog.Attr) *Logger {
+ if len(attrs) != 0 || l.attrs.Len() != 0 {
+ l = l.clone()
+ l.setWithAttrs(attrs)
+ l.setLongTerm()
+ }
+
+ return l
+}
+
+// WithGroup returns a new [Logger] with the given group.
+func (l *Logger) WithGroup(group string) *Logger {
+ if group != "" {
+ l = l.clone()
+ l.setWithGroup(group)
+ }
+
+ return l
+}
+
+// WithSource returns a new [Logger] that includes the source file and line in the log record if [enabled] is true.
+func (l *Logger) WithSource(enabled bool) *Logger {
+ if l.src != enabled {
+ l = l.clone()
+ l.src = enabled
+ }
+
+ return l
+}
+
+// Debug logs a message at the debug level.
+func (l *Logger) Debug(msg string, attrs ...slog.Attr) {
+ l.log(context.Background(), slog.LevelDebug, msg, attrs, 0)
+}
+
+// DebugContext logs a message at the debug level with the given context.
+func (l *Logger) DebugContext(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelDebug, msg, attrs, 0)
+}
+
+// Info logs a message at the info level.
+func (l *Logger) Info(msg string, attrs ...slog.Attr) {
+ l.log(context.Background(), slog.LevelInfo, msg, attrs, 0)
+}
+
+// InfoContext logs a message at the info level with the given context.
+func (l *Logger) InfoContext(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelInfo, msg, attrs, 0)
+}
+
+// Warn logs a message at the warn level.
+func (l *Logger) Warn(msg string, attrs ...slog.Attr) {
+ l.log(context.Background(), slog.LevelWarn, msg, attrs, 0)
+}
+
+// WarnContext logs a message at the warn level with the given context.
+func (l *Logger) WarnContext(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelWarn, msg, attrs, 0)
+}
+
+// Error logs a message at the error level.
+func (l *Logger) Error(msg string, attrs ...slog.Attr) {
+ l.log(context.Background(), slog.LevelError, msg, attrs, 0)
+}
+
+// ErrorContext logs a message at the error level with the given context.
+func (l *Logger) ErrorContext(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelError, msg, attrs, 0)
+}
+
+// Log logs a message at the given level.
+func (l *Logger) Log(level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(context.Background(), level, msg, attrs, 0)
+}
+
+// LogContext logs a message at the given level.
+func (l *Logger) LogContext(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(ctx, level, msg, attrs, 0)
+}
+
+// LogAttrs logs a message at the given level.
+func (l *Logger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(ctx, level, msg, attrs, 0)
+}
+
+// LongTerm returns a new [Logger] with the attributes applied to the handler.
+func (l *Logger) LongTerm() *Logger {
+ if l.attrs.Len() != 0 {
+ l = l.clone()
+ l.setLongTerm()
+ }
+
+ return l
+}
+
+func (l Logger) clone() *Logger {
+ return &l
+}
+
+// ---
+
+// ContextLogger is an alternative to [Logger] having only methods with context for logging messages.
+type ContextLogger struct {
+ commonLogger
+}
+
+// Logger returns a new [Logger] with the associated handler.
+func (l *ContextLogger) Logger() *Logger {
+ return &Logger{l.commonLogger}
+}
+
+// Handler returns the associated handler.
+func (l *ContextLogger) Handler() slog.Handler {
+ return l.handlerForExport()
+}
+
+// SlogLogger returns a new [slog.Logger] that logs to the associated handler.
+func (l *ContextLogger) SlogLogger() *slog.Logger {
+ return slog.New(l.handler)
+}
+
+// Enabled returns true if the given level is enabled.
+func (l *ContextLogger) Enabled(ctx context.Context, level slog.Level) bool {
+ return l.handler.Enabled(ctx, level)
+}
+
+// With returns a new [ContextLogger] with the given attributes.
+func (l *ContextLogger) With(attrs ...slog.Attr) *ContextLogger {
+ if len(attrs) != 0 {
+ l = l.clone()
+ l.setWithAttrs(attrs)
+ }
+
+ return l
+}
+
+// WithLongTerm returns a new [ContextLogger] with the given attributes optimized for multiple usage.
+func (l *ContextLogger) WithLongTerm(attrs ...slog.Attr) *ContextLogger {
+ if len(attrs) != 0 {
+ l = l.clone()
+ l.setWithAttrs(attrs)
+ l.setLongTerm()
+ }
+
+ return l
+}
+
+// WithGroup returns a new [ContextLogger] with the given group.
+func (l *ContextLogger) WithGroup(group string) *ContextLogger {
+ if group != "" {
+ l = l.clone()
+ l.setWithGroup(group)
+ }
+
+ return l
+}
+
+// WithSource returns a new [ContextLogger] that includes the source file and line in the log record if [enabled] is true.
+func (l *ContextLogger) WithSource(enabled bool) *ContextLogger {
+ if l.src != enabled {
+ l = l.clone()
+ l.src = enabled
+ }
+
+ return l
+}
+
+// Debug logs a message at the debug level.
+func (l *ContextLogger) Debug(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelDebug, msg, attrs, 0)
+}
+
+// Info logs a message at the info level.
+func (l *ContextLogger) Info(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelInfo, msg, attrs, 0)
+}
+
+// Warn logs a message at the warn level.
+func (l *ContextLogger) Warn(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelWarn, msg, attrs, 0)
+}
+
+// Error logs a message at the error level.
+func (l *ContextLogger) Error(ctx context.Context, msg string, attrs ...slog.Attr) {
+ l.log(ctx, slog.LevelError, msg, attrs, 0)
+}
+
+// Log logs a message at the given level.
+func (l *ContextLogger) Log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(ctx, level, msg, attrs, 0)
+}
+
+// LogAttrs logs a message at the given level.
+func (l *ContextLogger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(ctx, level, msg, attrs, 0)
+}
+
+// LogWithCallerSkip logs a message at the given level with additional skipping of the specified amount of call stack frames.
+func (l *ContextLogger) LogWithCallerSkip(ctx context.Context, skip int, level slog.Level, msg string, attrs ...slog.Attr) {
+ l.log(ctx, level, msg, attrs, skip)
+}
+
+// LongTerm returns a new [Logger] with the attributes applied to the handler.
+func (l *ContextLogger) LongTerm() *ContextLogger {
+ if l.attrs.Len() != 0 {
+ l = l.clone()
+ l.setLongTerm()
+ }
+
+ return l
+}
+
+func (l ContextLogger) clone() *ContextLogger {
+ return &l
+}
+
+// ---
+
+func defaultHandler() slog.Handler {
+ return slog.Default().Handler()
+}
+
+// ---
+
+type commonLogger struct {
+ handler slog.Handler
+ src bool
+ attrs AttrPack
+}
+
+func (l *commonLogger) handlerForExport() slog.Handler {
+ handler := l.handler
+ if l.attrs.Len() != 0 {
+ handler = handler.WithAttrs(l.attrs.Collect())
+ }
+
+ return handler
+}
+
+func (l *commonLogger) setWithAttrs(attrs []slog.Attr) {
+ if len(attrs) != 0 {
+ l.attrs = l.attrs.Clone()
+ l.attrs.Add(attrs...)
+ }
+}
+
+func (l *commonLogger) setWithGroup(group string) {
+ if group != "" {
+ l.setLongTerm()
+ l.handler = l.handler.WithGroup(group)
+ }
+}
+
+func (l *commonLogger) setLongTerm() {
+ if l.attrs.Len() != 0 {
+ l.handler = l.handlerForExport()
+ l.attrs = AttrPack{}
+ }
+}
+
+func (l *commonLogger) log(ctx context.Context, level slog.Level, msg string, attrs []slog.Attr, skip int) {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ if !l.handler.Enabled(ctx, level) {
+ return
+ }
+
+ var pcs [1]uintptr
+ if l.src {
+ runtime.Callers(skip+3, pcs[:])
+ }
+
+ r := slog.NewRecord(time.Now(), level, msg, pcs[0])
+
+ if l.attrs.Len() != 0 {
+ l.attrs.Enumerate(func(attr slog.Attr) bool {
+ r.AddAttrs(attr)
+
+ return true
+ })
+ }
+
+ r.AddAttrs(attrs...)
+
+ _ = l.handler.Handle(ctx, r)
+}
+
+// ---
+
+func logAttrs(ctx context.Context, handler slog.Handler, level slog.Level, msg string, attrs []slog.Attr) {
+ l := commonLogger{
+ handler: handler,
+ src: true,
+ }
+ l.log(ctx, level, msg, attrs, 1)
+}
+
+// ---
+
+type commonLoggerInterface[T any] interface {
+ Handler() slog.Handler
+ SlogLogger() *slog.Logger
+ Enabled(context.Context, slog.Level) bool
+ LogAttrs(context.Context, slog.Level, string, ...slog.Attr)
+ With(...slog.Attr) T
+ WithLongTerm(...slog.Attr) T
+ WithGroup(string) T
+ WithSource(bool) T
+ LongTerm() T
+}
+
+var (
+ _ commonLoggerInterface[*Logger] = (*Logger)(nil)
+ _ commonLoggerInterface[*ContextLogger] = (*ContextLogger)(nil)
+)
diff --git a/logger_test.go b/logger_test.go
new file mode 100644
index 0000000..4040591
--- /dev/null
+++ b/logger_test.go
@@ -0,0 +1,203 @@
+package slogx_test
+
+import (
+ "context"
+ "log/slog"
+
+ . "github.com/pamburus/go-tst/tst"
+ "github.com/pamburus/slogx"
+ "github.com/pamburus/slogx/internal/mock"
+
+ "testing"
+)
+
+func TestLogger(tt *testing.T) {
+ t := New(tt)
+
+ t.Run("Default", func(t Test) {
+ cl := mock.NewCallLog()
+ handler := mock.NewHandler(cl)
+ slog.SetDefault(slog.New(handler))
+ logger := slogx.Default().WithSource(false)
+
+ logger.Info("msg")
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ },
+ },
+ ))
+ })
+
+ test := func(name string, fn func(Test, *mock.CallLog, *slogx.Logger)) {
+ cl := mock.NewCallLog()
+ handler := mock.NewHandler(cl)
+ logger := slogx.New(handler).WithSource(false)
+
+ t.Run(name, func(t Test) {
+ fn(t, cl, logger)
+ })
+ }
+
+ test("Debug", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Debug("msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelDebug,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelDebug,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Info", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Info("msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Warn", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Warn("msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelWarn,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelWarn,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Error", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Error("msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelError,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelError,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Log", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Log(slog.LevelWarn, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelWarn,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelWarn,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("With", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger = logger.With(
+ slog.String("a", "va"),
+ slog.String("b", "vb"),
+ )
+ logger.Log(slog.LevelInfo, "msg", slog.String("c", "d"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{
+ {Key: "a", Value: "va"},
+ {Key: "b", Value: "vb"},
+ {Key: "c", Value: "d"},
+ },
+ },
+ },
+ ))
+ })
+
+ test("WithGroup", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger = logger.WithGroup("g1")
+ logger.Log(slog.LevelInfo, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerWithGroup{
+ Instance: "0",
+ Key: "g1",
+ },
+ mock.HandlerEnabled{
+ Instance: "0.1",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0.1",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{
+ {Key: "a", Value: "v"},
+ },
+ },
+ },
+ ))
+ })
+
+ test("Enabled", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ logger.Enabled(context.Background(), slog.LevelDebug)
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelDebug,
+ },
+ ))
+ })
+
+ test("SlogLogger", func(t Test, cl *mock.CallLog, logger *slogx.Logger) {
+ t.Expect(logger.SlogLogger().Handler()).To(Equal(logger.Handler()))
+ })
+}
diff --git a/slogc/README.md b/slogc/README.md
new file mode 100644
index 0000000..ac8eab4
--- /dev/null
+++ b/slogc/README.md
@@ -0,0 +1,13 @@
+# slogx [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
+
+Package [slogc](https://pkg.go.dev/github.com/pamburus/slogx/slogc) provides [Logger](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Logger), which is an alias for [slogx.ContextLogger](https://pkg.go.dev/github.com/pamburus/slogx#ContextLogger) and is an alternative to [slog.Logger](https://pkg.go.dev/log/slog#Logger) that focuses on performance and usability. See the [slogx](../README.md) package description for more details about [slogx.ContextLogger](https://pkg.go.dev/github.com/pamburus/slogx#ContextLogger) and its rationale. This package provides logging primitives that are context-centric. The [Logger](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Logger) can be stored in a context using the [New](https://pkg.go.dev/github.com/pamburus/slogx/slogc#New) function, and later used implicitly by providing only the context to a set of functions such as [Log](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Log), [Info](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Info), [Debug](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Debug), and so on. It can also be retrieved from the context using the [Get](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Get) method. If the context does not contain a value stored by the [New](https://pkg.go.dev/github.com/pamburus/slogx/slogc#New) method, a [Default](https://pkg.go.dev/github.com/pamburus/slogx/slogc#Default) logger is returned, which is constructed using a handler returned by [slog.Default](https://pkg.go.dev/log/slog#Default).
+
+## Performance
+* See [benchmark results](../doc/benchmark/README.md) for details.
+
+[doc-img]: https://pkg.go.dev/badge/github.com/pamburus/slogx/slogc
+[doc]: https://pkg.go.dev/github.com/pamburus/slogx/slogc
+[ci-img]: https://github.com/pamburus/slogx/actions/workflows/ci.yml/badge.svg
+[ci]: https://github.com/pamburus/slogx/actions/workflows/ci.yml
+[cov-img]: https://codecov.io/gh/pamburus/slogx/slogc/graph/badge.svg?token=0TF6JD4KDU
+[cov]: https://codecov.io/gh/pamburus/slogx/slogc
diff --git a/slogc/benchmark_test.go b/slogc/benchmark_test.go
new file mode 100644
index 0000000..b1c56f7
--- /dev/null
+++ b/slogc/benchmark_test.go
@@ -0,0 +1,223 @@
+package slogc_test
+
+import (
+ "context"
+ "io"
+ "log/slog"
+ "testing"
+
+ "github.com/pamburus/slogx"
+ "github.com/pamburus/slogx/slogc"
+)
+
+func BenchmarkLogging(b *testing.B) {
+ testEnabled := func(ctx context.Context, b *testing.B) {
+ b.Run("Enabled", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Get(ctx).Enabled(ctx, slog.LevelInfo)
+ }
+ })
+ }
+
+ testLogAttrs := func(ctx context.Context, b *testing.B) {
+ b.Run("Log", func(b *testing.B) {
+ b.Run("NoAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Log(ctx, slog.LevelInfo, "msg")
+ }
+ })
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ })
+ }
+
+ testWith := func(ctx context.Context, b *testing.B) {
+ b.Run("With", func(b *testing.B) {
+ b.Run("ThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.With(ctx, slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ }
+ })
+ b.Run("FiveAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.With(ctx, slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ })
+ }
+
+ testWithAndLog := func(ctx context.Context, b *testing.B) {
+ b.Run("LogWithAndLog", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := slogc.Get(ctx).With(slog.String("a", "av"), slog.String("b", "bv"))
+ logger.Log(ctx, slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := slogc.Get(ctx).With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ logger.Log(ctx, slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ logger := slogc.Get(ctx).With(slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ logger.Log(ctx, slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testLogAfterWith := func(ctx context.Context, b *testing.B) {
+ b.Run("LogAfterWith", func(b *testing.B) {
+ b.Run("TwoAndThreeAttrs", func(b *testing.B) {
+ ctx := slogc.With(ctx, slog.String("a", "av"), slog.String("b", "bv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ }
+ })
+ b.Run("ThreeAndFourAttrs", func(b *testing.B) {
+ ctx := slogc.With(ctx, slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("d", "dv"), slog.String("e", "ev"), slog.String("f", "fv"), slog.String("g", "gv"))
+ }
+ })
+ b.Run("FiveAndThreeAttrs", func(b *testing.B) {
+ ctx := slogc.With(ctx, slog.String("a", "av"), slog.String("b", "bv"), slog.String("c", "cv"), slog.String("d", "dv"), slog.String("e", "ev"))
+ b.ResetTimer()
+ for i := 0; i != b.N; i++ {
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("f", "fv"), slog.String("g", "gv"), slog.String("h", "hv"))
+ }
+ })
+ })
+ }
+
+ testAllForContext := func(ctx context.Context, b *testing.B) {
+ testEnabled(ctx, b)
+ testLogAttrs(ctx, b)
+ testWith(ctx, b)
+ testWithAndLog(ctx, b)
+ testLogAfterWith(ctx, b)
+ }
+
+ testWithSource := func(ctx context.Context, b *testing.B, handler slog.Handler, enabled bool) {
+ name := "WithSource"
+ if !enabled {
+ name = "WithoutSource"
+ }
+
+ b.Run(name, func(b *testing.B) {
+ b.Run("Unwrapped", func(b *testing.B) {
+ ctx := slogc.New(ctx, slogx.NewContextLogger(handler).WithSource(enabled))
+ testAllForContext(ctx, b)
+ })
+
+ b.Run("3xWrapped", func(b *testing.B) {
+ handler = wrapHandlerN(handler, 3)
+ ctx := slogc.New(ctx, slogx.NewContextLogger(handler).WithSource(enabled))
+ testAllForContext(ctx, b)
+ })
+ })
+ }
+
+ testAllForHandler := func(ctx context.Context, b *testing.B, handler slog.Handler) {
+ testWithSource(ctx, b, handler, false)
+ testWithSource(ctx, b, handler, true)
+ }
+
+ b.Run("Discard", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(context.Background(), b, slogx.Discard())
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(context.Background(), b, &enabledDiscardHandler{})
+ })
+ })
+
+ b.Run("JSON", func(b *testing.B) {
+ b.Run("Disabled", func(b *testing.B) {
+ testAllForHandler(context.Background(), b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelError}))
+ })
+ b.Run("Enabled", func(b *testing.B) {
+ testAllForHandler(context.Background(), b, slog.NewJSONHandler(io.Discard, &slog.HandlerOptions{}))
+ })
+ })
+}
+
+// ---
+
+func wrapHandlerN(handler slog.Handler, times int) slog.Handler {
+ for i := 0; i != times; i++ {
+ handler = wrapHandler(handler)
+ }
+
+ return handler
+}
+
+func wrapHandler(handler slog.Handler) slog.Handler {
+ return &testHandlerWrapper{handler}
+}
+
+// ---
+
+type testHandlerWrapper struct {
+ base slog.Handler
+}
+
+func (h *testHandlerWrapper) Enabled(ctx context.Context, level slog.Level) bool {
+ return h.base.Enabled(ctx, level)
+}
+
+func (h *testHandlerWrapper) Handle(ctx context.Context, record slog.Record) error {
+ return h.base.Handle(ctx, record)
+}
+
+func (h *testHandlerWrapper) WithAttrs(attrs []slog.Attr) slog.Handler {
+ if len(attrs) == 0 {
+ return h
+ }
+
+ return &testHandlerWrapper{h.base.WithAttrs(attrs)}
+}
+
+func (h *testHandlerWrapper) WithGroup(key string) slog.Handler {
+ if key == "" {
+ return h
+ }
+
+ return &testHandlerWrapper{h.base.WithGroup(key)}
+}
+
+//---
+
+type enabledDiscardHandler struct{}
+
+func (h *enabledDiscardHandler) Enabled(context.Context, slog.Level) bool {
+ return true
+}
+
+func (h *enabledDiscardHandler) Handle(context.Context, slog.Record) error {
+ return nil
+}
+
+func (h *enabledDiscardHandler) WithAttrs([]slog.Attr) slog.Handler {
+ return h
+}
+
+func (h *enabledDiscardHandler) WithGroup(string) slog.Handler {
+ return h
+}
diff --git a/slogc/name.go b/slogc/name.go
new file mode 100644
index 0000000..140819f
--- /dev/null
+++ b/slogc/name.go
@@ -0,0 +1,50 @@
+package slogc
+
+import (
+ "context"
+ "log/slog"
+)
+
+// WithName returns a new context with the logger name composed
+// of the base name that is already in context as a prefix and the provided name
+// using dot as a separator.
+// If the name is empty, the context is returned as is.
+func WithName(ctx context.Context, name string) context.Context {
+ if name == "" {
+ return ctx
+ }
+
+ base := Name(ctx)
+ if base != "" {
+ name = base + "." + name
+ }
+
+ return context.WithValue(ctx, &contextKeyName, name)
+}
+
+// Name returns the full logger name from the context.
+// If the name is not set, an empty string is returned.
+func Name(ctx context.Context) string {
+ if name, ok := ctx.Value(&contextKeyName).(string); ok {
+ return name
+ }
+
+ return ""
+}
+
+// NameAttr returns a dynamic attribute that contains the logger name.
+func NameAttr(key string) func(ctx context.Context) slog.Attr {
+ return func(ctx context.Context) slog.Attr {
+ if name := Name(ctx); name != "" {
+ return slog.String(key, name)
+ }
+
+ return slog.Attr{}
+ }
+}
+
+// ---
+
+var (
+ contextKeyName int
+)
diff --git a/slogc/name_test.go b/slogc/name_test.go
new file mode 100644
index 0000000..4ab082c
--- /dev/null
+++ b/slogc/name_test.go
@@ -0,0 +1,227 @@
+package slogc_test
+
+import (
+ "context"
+ "log/slog"
+ "testing"
+ "time"
+
+ . "github.com/pamburus/go-tst/tst"
+ "github.com/pamburus/slogx"
+ "github.com/pamburus/slogx/internal/mock"
+ "github.com/pamburus/slogx/slogc"
+)
+
+func TestName(tt *testing.T) {
+ t := New(tt)
+
+ someTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
+
+ t.Run("EmptyAttrKey", func(t Test) {
+ handler := &mock.Handler{}
+ t.Expect(newHandlerWithName(handler, "")).To(Equal(handler))
+ })
+
+ t.Run("Enabled", func(t Test) {
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+
+ t.Expect(handler.Enabled(context.Background(), slog.LevelInfo)).To(BeTrue())
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerEnabled{Instance: "0", Level: slog.LevelInfo},
+ ))
+ })
+
+ t.Run("WithEmptyAttrs", func(t Test) {
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+
+ t.Expect(handler.WithAttrs(nil)).To(Equal(handler))
+ })
+
+ t.Run("WithEmptyGroup", func(t Test) {
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+
+ t.Expect(handler.WithGroup("")).To(Equal(handler))
+ })
+
+ t.Run("WithGroup", func(t Test) {
+ ctx := context.Background()
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+
+ handler := newHandlerWithName(base, "@logger")
+ handler = handler.WithAttrs([]slog.Attr{slog.String("a1", "av1")})
+ handler = handler.WithGroup("g1")
+ handler = handler.WithAttrs([]slog.Attr{slog.String("a2", "av2")})
+
+ record1 := slog.NewRecord(someTime, slog.LevelDebug, "m1", 42)
+ record1.AddAttrs(
+ slog.String("a3", "av3"),
+ slog.String("a4", "av4"),
+ )
+
+ err := handler.Handle(slogc.WithName(ctx, "ab"), record1)
+ t.Expect(err).ToNot(HaveOccurred())
+
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerWithAttrs{
+ Instance: "0",
+ Attrs: []mock.Attr{
+ {Key: "a1", Value: "av1"},
+ },
+ },
+ mock.HandlerWithGroup{
+ Instance: "0.1",
+ Key: "g1",
+ },
+ mock.HandlerWithAttrs{
+ Instance: "0.1.2",
+ Attrs: []mock.Attr{
+ {Key: "a2", Value: "av2"},
+ },
+ },
+ mock.HandlerHandle{
+ Instance: "0.1.2.3",
+ Record: mock.Record{
+ Time: someTime,
+ Message: "m1",
+ Level: slog.LevelDebug,
+ PC: 42,
+ Attrs: []mock.Attr{
+ {Key: "a3", Value: "av3"},
+ {Key: "a4", Value: "av4"},
+ {Key: "@logger", Value: "ab"},
+ },
+ },
+ },
+ ))
+ })
+
+ t.Run("WithoutName", func(t Test) {
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+ record1 := slog.NewRecord(someTime, slog.LevelInfo, "m1", 0)
+
+ err := handler.Handle(context.Background(), record1)
+ t.Expect(err).ToNot(HaveOccurred())
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Time: someTime,
+ Message: "m1",
+ Level: slog.LevelInfo,
+ PC: 0,
+ Attrs: nil,
+ },
+ },
+ ))
+ })
+
+ t.Run("WithName", func(t Test) {
+ ctx := slogc.WithName(context.Background(), "a")
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+ record1 := slog.NewRecord(someTime, slog.LevelInfo, "m1", 0)
+
+ err := handler.Handle(slogc.WithName(ctx, "b"), record1)
+ t.Expect(err).ToNot(HaveOccurred())
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Time: someTime,
+ Message: "m1",
+ Level: slog.LevelInfo,
+ PC: 0,
+ Attrs: []mock.Attr{
+ {Key: "@logger", Value: "a.b"},
+ },
+ },
+ },
+ ))
+ })
+
+ t.Run("WithEmptyName", func(t Test) {
+ ctx := context.Background()
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+ record1 := slog.NewRecord(someTime, slog.LevelInfo, "m1", 0)
+
+ err := handler.Handle(slogc.WithName(ctx, ""), record1)
+ t.Expect(err).ToNot(HaveOccurred())
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Time: someTime,
+ Message: "m1",
+ Level: slog.LevelInfo,
+ PC: 0,
+ Attrs: nil,
+ },
+ },
+ ))
+ })
+
+ t.Run("WithAttrsAndName", func(t Test) {
+ ctx := context.Background()
+ cl := mock.NewCallLog()
+ base := mock.NewHandler(cl)
+ handler := newHandlerWithName(base, "@logger")
+
+ record1 := slog.NewRecord(someTime, slog.LevelInfo, "m1", 0)
+ record1.AddAttrs(
+ slog.Any("a1", "av1"),
+ slog.Any("a2", "av2"),
+ )
+
+ handler = handler.WithAttrs([]slog.Attr{
+ slog.Any("c1", "cv1"),
+ slog.Any("c2", "cv2"),
+ })
+ err := handler.Handle(slogc.WithName(ctx, "aa"), record1)
+ t.Expect(err).ToNot(HaveOccurred())
+ t.Expect(cl.Calls()...).To(Equal(
+ mock.HandlerWithAttrs{
+ Instance: "0",
+ Attrs: []mock.Attr{
+ {Key: "c1", Value: "cv1"},
+ {Key: "c2", Value: "cv2"},
+ },
+ },
+ mock.HandlerHandle{
+ Instance: "0.1",
+ Record: mock.Record{
+ Time: someTime,
+ Message: "m1",
+ Level: slog.LevelInfo,
+ PC: 0,
+ Attrs: []mock.Attr{
+ {Key: "a1", Value: "av1"},
+ {Key: "a2", Value: "av2"},
+ {Key: "@logger", Value: "aa"},
+ },
+ },
+ },
+ ))
+ })
+}
+
+func newHandlerWithName(handler slog.Handler, attrKey string) slog.Handler {
+ if attrKey == "" {
+ return handler
+ }
+
+ return slogx.TweakHandler(handler).
+ WithDynamicAttr(slogc.NameAttr(attrKey)).
+ Result()
+}
diff --git a/slogc/slogc.go b/slogc/slogc.go
new file mode 100644
index 0000000..a35b53d
--- /dev/null
+++ b/slogc/slogc.go
@@ -0,0 +1,86 @@
+// Package slogc provides context-centric logging facilities based on [slogx] and [slog] packages.
+package slogc
+
+import (
+ "context"
+ "log/slog"
+
+ "github.com/pamburus/slogx"
+)
+
+// Logger is an alias for [slogx.ContextLogger].
+type Logger = slogx.ContextLogger
+
+// New returns a new context with the provided logger.
+func New(ctx context.Context, logger *Logger) context.Context {
+ if ctx == nil {
+ ctx = context.Background()
+ }
+
+ return context.WithValue(ctx, &contextKeyLogger, logger)
+}
+
+// Get returns a [Logger] from the given context stored by [New].
+// If the context is nil or the logger is not found, a [Default] logger is returned.
+func Get(ctx context.Context) *Logger {
+ if ctx != nil {
+ if logger, ok := ctx.Value(&contextKeyLogger).(*Logger); ok {
+ return logger
+ }
+ }
+
+ return Default()
+}
+
+// Default returns a [Logger] with the default handler from [slog.Default].
+func Default() *Logger {
+ return slogx.Default().ContextLogger()
+}
+
+// With returns a new context with a modified logger containing additionally the provided attributes.
+func With(ctx context.Context, attrs ...slog.Attr) context.Context {
+ return New(ctx, Get(ctx).WithLongTerm(attrs...))
+}
+
+// WithGroup returns a new context with a modified logger containing the provided group.
+func WithGroup(ctx context.Context, key string) context.Context {
+ return New(ctx, Get(ctx).WithGroup(key))
+}
+
+// WithSource returns a new context with a modified logger containing the source information enabled flag.
+func WithSource(ctx context.Context, enabled bool) context.Context {
+ return New(ctx, Get(ctx).WithSource(enabled))
+}
+
+// ---
+
+// Debug logs a message at debug level.
+func Debug(ctx context.Context, msg string, attrs ...slog.Attr) {
+ Get(ctx).LogWithCallerSkip(ctx, 1, slog.LevelDebug, msg, attrs...)
+}
+
+// Info logs a message at info level.
+func Info(ctx context.Context, msg string, attrs ...slog.Attr) {
+ Get(ctx).LogWithCallerSkip(ctx, 1, slog.LevelInfo, msg, attrs...)
+}
+
+// Warn logs a message at warn level.
+func Warn(ctx context.Context, msg string, attrs ...slog.Attr) {
+ Get(ctx).LogWithCallerSkip(ctx, 1, slog.LevelWarn, msg, attrs...)
+}
+
+// Error logs a message at error level.
+func Error(ctx context.Context, msg string, attrs ...slog.Attr) {
+ Get(ctx).LogWithCallerSkip(ctx, 1, slog.LevelError, msg, attrs...)
+}
+
+// Log logs a message with attributes at the given level.
+func Log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
+ Get(ctx).LogWithCallerSkip(ctx, 1, level, msg, attrs...)
+}
+
+// ---
+
+var (
+ contextKeyLogger int
+)
diff --git a/slogc/slogc_test.go b/slogc/slogc_test.go
new file mode 100644
index 0000000..ad98f5d
--- /dev/null
+++ b/slogc/slogc_test.go
@@ -0,0 +1,198 @@
+package slogc_test
+
+import (
+ "context"
+ "log/slog"
+
+ . "github.com/pamburus/go-tst/tst"
+ "github.com/pamburus/slogx"
+ "github.com/pamburus/slogx/internal/mock"
+ "github.com/pamburus/slogx/slogc"
+
+ "testing"
+)
+
+func TestLogger(tt *testing.T) {
+ t := New(tt)
+
+ t.Run("Default", func(t Test) {
+ cl := mock.NewCallLog()
+ handler := mock.NewHandler(cl)
+ slog.SetDefault(slog.New(handler))
+ ctx := context.Background()
+ ctx = slogc.WithSource(ctx, false)
+ logger := slogc.Get(ctx)
+
+ logger.Info(ctx, "msg")
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ },
+ },
+ ))
+ })
+
+ test := func(name string, fn func(context.Context, *mock.CallLog, Test)) {
+ cl := mock.NewCallLog()
+ handler := mock.NewHandler(cl)
+ logger := slogx.NewContextLogger(handler).WithSource(false)
+ ctx := slogc.New(nil, logger)
+
+ t.Run(name, func(t Test) {
+ fn(ctx, cl, t)
+ })
+ }
+
+ test("Debug", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ slogc.Debug(ctx, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelDebug,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelDebug,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Info", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ slogc.Info(ctx, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Warn", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ slogc.Warn(ctx, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelWarn,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelWarn,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Error", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ slogc.Error(ctx, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelError,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelError,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("Log", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ slogc.Log(ctx, slog.LevelWarn, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerEnabled{
+ Instance: "0",
+ Level: slog.LevelWarn,
+ },
+ mock.HandlerHandle{
+ Instance: "0",
+ Record: mock.Record{
+ Level: slog.LevelWarn,
+ Message: "msg",
+ Attrs: []mock.Attr{{Key: "a", Value: "v"}},
+ },
+ },
+ ))
+ })
+
+ test("With", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ ctx = slogc.With(ctx,
+ slog.String("a", "va"),
+ slog.String("b", "vb"),
+ )
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("c", "d"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerWithAttrs{
+ Instance: "0",
+ Attrs: []mock.Attr{
+ {Key: "a", Value: "va"},
+ {Key: "b", Value: "vb"},
+ },
+ },
+ mock.HandlerEnabled{
+ Instance: "0.1",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0.1",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{
+ {Key: "c", Value: "d"},
+ },
+ },
+ },
+ ))
+ })
+
+ test("WithGroup", func(ctx context.Context, cl *mock.CallLog, t Test) {
+ ctx = slogc.WithGroup(ctx, "g1")
+ slogc.Log(ctx, slog.LevelInfo, "msg", slog.String("a", "v"))
+ t.Expect(cl.Calls().WithoutTime()...).To(Equal(
+ mock.HandlerWithGroup{
+ Instance: "0",
+ Key: "g1",
+ },
+ mock.HandlerEnabled{
+ Instance: "0.1",
+ Level: slog.LevelInfo,
+ },
+ mock.HandlerHandle{
+ Instance: "0.1",
+ Record: mock.Record{
+ Level: slog.LevelInfo,
+ Message: "msg",
+ Attrs: []mock.Attr{
+ {Key: "a", Value: "v"},
+ },
+ },
+ },
+ ))
+ })
+}