You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: update logging docs for slog-native architecture (#73)
* docs: update logging docs for slog-native architecture
Update all logging references to reflect the slog-native Handler:
- howto/Log.md: rewrite backends section (SetDefault/NewHandler/NewHandlerWithInner),
update context-aware logs to show AddAttrsToContext + slog.LogAttrs,
update OverrideLogLevel example with slog
- Packages.md: rewrite log package description for slog-native
- Index.md: update feature table and package table
- FAQ.md: SetLogger -> SetDefault
- howto/production.md: fix wrong function name (AddToLogContext -> AddToContext)
Ref: go-coldbrew/log#27
* docs: add handler composability examples to Log howto
Add concrete examples showing how to compose ColdBrew's Handler with
custom inner handlers, slog-multi fan-out, and external middleware wrapping.
All done through the log package via NewHandlerWithInner.
* fix: add missing imports to code snippets in Log howto
Add context, net/http, os imports to code examples so they compile
when copy-pasted. Addresses Copilot review comments.
* fix: correct tracing description — stats handlers, not interceptors
* fix: error handling in file handler example, define logFile in fan-out
* fix: clarify sampling handler is a placeholder
* fix: add return to example, clarify SetDefault in wrapping pattern
Copy file name to clipboardExpand all lines: FAQ.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -34,7 +34,7 @@ The dependency chain (`options → errors → log → ...`) only means that `log
34
34
35
35
## Why are configuration functions not thread-safe?
36
36
37
-
Functions like `interceptors.AddUnaryServerInterceptor()`, `interceptors.SetFilterFunc()`, and `log.SetLogger()` follow the **init-only pattern**: they must be called during application startup (in `init()` or early in `main()`), before any concurrent access begins.
37
+
Functions like `interceptors.AddUnaryServerInterceptor()`, `interceptors.SetFilterFunc()`, and `log.SetDefault()` follow the **init-only pattern**: they must be called during application startup (in `init()` or early in `main()`), before any concurrent access begins.
38
38
39
39
This is intentional and consistent across the entire codebase. The interceptor chain is assembled once at startup and then read concurrently — adding mutexes would add overhead to every single request for a code path that only runs once.
Copy file name to clipboardExpand all lines: Index.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,8 +26,8 @@ A Kubernetes-native Go microservice framework for building production-grade gRPC
26
26
| Feature | Description |
27
27
|---------|-------------|
28
28
|**gRPC + REST Gateway**| Define your API once in protobuf — get gRPC, REST, and [Swagger docs](/architecture#self-documenting-apis) automatically via [grpc-gateway]. HTTP gateway supports JSON, `application/proto`, and `application/protobuf`[content types](/howto/APIs/#http-content-type) out of the box |
29
-
|**Structured Logging**|Pluggable backends — [slog](default), zap, go-kit, logrus — with per-request context fields and trace ID propagation |
30
-
|**Distributed Tracing**|[OpenTelemetry] and [New Relic] support with automatic span creation in interceptors — traces can be sent to any OTLP-compatible backend including [Jaeger]|
29
+
|**Structured Logging**|Native [slog]with custom Handler — per-request context fields, trace ID propagation, and typed attrs for zero-boxing performance|
30
+
|**Distributed Tracing**|[OpenTelemetry] and [New Relic] support with automatic span creation via gRPC stats handlers — traces can be sent to any OTLP-compatible backend including [Jaeger]|
31
31
|**Prometheus Metrics**| Built-in request latency, error rate, and circuit breaker metrics at `/metrics`|
32
32
|**Error Tracking**| Stack traces, gRPC status codes, and async notification to [Sentry], Rollbar, or Airbrake |
33
33
|**Rate Limiting**| Per-pod token bucket rate limiter — disabled by default, pluggable via custom [`ratelimit.Limiter`](https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/ratelimit#Limiter) interface for distributed or per-tenant rate limiting. Config: `RATE_LIMIT_PER_SECOND`. See [interceptors howto](/howto/interceptors#rate-limiting)|
@@ -130,7 +130,7 @@ ColdBrew is modular — use the full framework or pick individual packages:
130
130
|[**core**](https://github.com/go-coldbrew/core)| gRPC server + HTTP gateway, health checks, graceful shutdown |
131
131
|[**interceptors**](https://github.com/go-coldbrew/interceptors)| Server/client interceptors for logging, tracing, metrics, retries |
132
132
|[**errors**](https://github.com/go-coldbrew/errors)| Enhanced errors with stack traces and gRPC status codes |
133
-
|[**log**](https://github.com/go-coldbrew/log)|Structured logging with pluggable backends|
133
+
|[**log**](https://github.com/go-coldbrew/log)|slog-native structured logging with context field injection|
134
134
|[**tracing**](https://github.com/go-coldbrew/tracing)| Distributed tracing (OpenTelemetry, Jaeger, New Relic) |
135
135
|[**options**](https://github.com/go-coldbrew/options)| Request-scoped key-value store via context |
136
136
|[**grpcpool**](https://github.com/go-coldbrew/grpcpool)| Round-robin gRPC connection pool |
Copy file name to clipboardExpand all lines: Packages.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,7 +25,7 @@ ColdBrew config package contains the configuration for the core package. It uses
25
25
Documentation can be found at [config-docs]
26
26
27
27
## [Log]
28
-
log provides a minimal interface for structured logging in services. It provides a simple interface to log errors, warnings, info and debug messages. It also provides a mechanism to add contextual information to logs. The default backend is [slog](https://pkg.go.dev/log/slog) (Go's standard structured logging). We also provide implementations for zap, gokit (deprecated), and logrus (deprecated). A slog bridge allows third-party code that uses `slog` directly to route its logs through ColdBrew's logging pipeline.
28
+
log provides slog-native structured logging for ColdBrew services. It uses a custom `slog.Handler` that automatically injects per-request context fields (trace ID, gRPC method, HTTP path) into every log record. Native `slog.LogAttrs` calls work out of the box after `core.New()` initializes the framework. Use `log.AddAttrsToContext` to add typed context fields without interface boxing, or `log.AddToContext` for untyped key-value pairs. The Handler is composable — it can wrap any `slog.Handler` for custom output formats or fan-out.
@@ -11,84 +11,167 @@ description: "Context-aware logging and trace ID propagation in ColdBrew"
11
11
1. TOC
12
12
{:toc}
13
13
14
-
## Logging backends
14
+
## Logging with slog
15
15
16
-
ColdBrew's log package supports pluggable backends. The default is **slog** (Go's standard structured logging).
16
+
ColdBrew uses a custom `slog.Handler` that automatically injects per-request context fields (trace ID, gRPC method, HTTP path) into every log record. After `core.New()` initializes the framework, native `slog` calls work out of the box:
17
17
18
-
| Backend | Package | Status |
19
-
|---------|---------|--------|
20
-
|**slog**|`loggers/slog`| Default, recommended |
21
-
|**zap**|`loggers/zap`| Supported |
22
-
|**gokit**|`loggers/gokit`| Deprecated |
23
-
|**logrus**|`loggers/logrus`| Deprecated |
24
-
|**stdlog**|`loggers/stdlog`| Minimal, for simple use cases |
Use `slog.LogAttrs` with typed attribute constructors (`slog.String`, `slog.Int`, `slog.Duration`, etc.) for the best performance — they avoid `interface{}` boxing. `slog.InfoContext` and `slog.ErrorContext` also work but box all values through `any`.
36
+
37
+
### Custom handler configuration
38
+
39
+
To customize the handler (e.g., change output format or wrap with middleware like slog-multi):
27
40
28
41
```go
29
42
import (
30
43
"github.com/go-coldbrew/log"
31
44
"github.com/go-coldbrew/log/loggers"
32
-
cbslog "github.com/go-coldbrew/log/loggers/slog"
33
45
)
34
46
35
47
funcinit() {
36
-
log.SetLogger(log.NewLogger(cbslog.NewLogger(
48
+
log.SetDefault(log.NewHandler(
37
49
loggers.WithJSONLogs(true),
38
50
loggers.WithCallerInfo(true),
39
-
)))
51
+
))
40
52
}
41
53
```
42
54
43
-
### slog bridge
55
+
### Handler composability
56
+
57
+
ColdBrew's `Handler` is a standard `slog.Handler` — it can wrap any inner handler, and can itself be wrapped by handler middleware. All composition is done through the `log` package using `log.NewHandlerWithInner`.
44
58
45
-
If your application or third-party libraries use `slog` directly, you can route those calls through ColdBrew's logging pipeline (context fields, level overrides, interceptors):
59
+
**Custom inner handler** (e.g., write to a file instead of stdout):
The gokit and logrus backends are deprecated. Both upstream libraries are in maintenance mode and no longer actively developed. Migrate to the slog backend for better performance and long-term support. No new logging code is required; if you explicitly configured one of these backends, remove that backend selection and ColdBrew will use slog by default.
79
+
**Fan-out to multiple destinations** (e.g., stdout + file, using [slog-multi](https://github.com/samber/slog-multi)):
// ColdBrew wraps the fan-out handler — context fields appear in both outputs
99
+
multi:= slogmulti.Fanout(stdout, file)
100
+
log.SetDefault(log.NewHandlerWithInner(multi))
101
+
}
102
+
```
103
+
104
+
**Wrapping ColdBrew's handler** (e.g., adding sampling on top):
105
+
106
+
```go
107
+
import (
108
+
"log/slog"
109
+
110
+
"github.com/go-coldbrew/log"
111
+
)
112
+
113
+
funcinit() {
114
+
cbHandler:= log.NewHandler() // ColdBrew handler with default JSON output
115
+
116
+
// Wrap with any slog.Handler middleware — e.g., slog-sampling, slog-dedup, etc.
117
+
// NewSamplingHandler is a placeholder for your chosen middleware.
118
+
sampled:=NewSamplingHandler(cbHandler, 0.1)
119
+
120
+
// Use log.SetDefault for ColdBrew's handler so log.GetHandler()/log.SetLevel() work,
121
+
// then override slog.SetDefault with the wrapped version for native slog calls.
122
+
log.SetDefault(cbHandler)
123
+
slog.SetDefault(slog.New(sampled))
124
+
}
125
+
```
126
+
127
+
In all cases, `slog.LogAttrs` calls and ColdBrew context fields work automatically — the Handler injects context fields regardless of where it sits in the chain.
61
128
62
129
## Context-aware logs
63
130
64
-
In any service there is a set of common items that you want to log with every log message. These items are usually things like the request-id, trace, user-id, etc. It is useful to have these items in the log message so that you can filter on them in your log aggregation system. This is especially useful when you have multiple points of logs and you want to be able to trace a request through the system.
131
+
ColdBrew provides a way to add per-request fields to the log context. Any fields added via `log.AddToContext` or `log.AddAttrsToContext` are automatically included in all log calls that use that context — both ColdBrew's `log.Info`and native `slog.LogAttrs`.
65
132
66
-
ColdBrew provides a way to add these items to the log message using the `log.AddToContext` function. This function takes a `context.Context` and `key, value`. AddToContext adds log fields to context. Any info added here will be added to all logs using this context.
133
+
### Adding context fields
134
+
135
+
Use `log.AddAttrsToContext` for typed fields (zero boxing) or `log.AddToContext` for untyped key-value pairs:
67
136
68
137
```go
69
138
import (
139
+
"context"
140
+
"log/slog"
141
+
"net/http"
142
+
70
143
"github.com/go-coldbrew/log"
71
144
)
72
145
73
146
funchandler(whttp.ResponseWriter, r *http.Request) {
74
147
ctx:= r.Context()
75
-
ctx = log.AddToContext(ctx, "request-id", "1234")
148
+
149
+
// Typed attrs — the Handler recovers the slog.Attr at log time
ColdBrew interceptors automatically add `grpcMethod`, trace ID, and HTTP path to the context — you don't need to add these yourself.
174
+
92
175
## Trace ID propagation in logs
93
176
94
177
When you have multiple services, it is useful to be able to trace a request through the system. This is especially useful when you have a request that spans multiple services and you want to be able to see the logs for each service in the context of the request. Having a propagating trace id is a good way to do this.
@@ -147,41 +230,43 @@ It is useful to be able to override the log level at request time. This is usefu
147
230
148
231
```go
149
232
import (
233
+
"context"
234
+
"log/slog"
235
+
"net/http"
236
+
150
237
"github.com/go-coldbrew/log"
151
238
"github.com/go-coldbrew/log/loggers"
152
239
)
153
240
154
241
funcinit() {
155
242
// set global log level to info
156
-
// this is typically set by the ColdBrew cookiecutter using the LOG_LEVEL environment variable
243
+
// this is typically set by the ColdBrew framework using the LOG_LEVEL environment variable
157
244
log.SetLevel(loggers.InfoLevel)
158
245
}
159
246
160
247
funchandler(whttp.ResponseWriter, r *http.Request) {
0 commit comments