charmbracelet / charmbracelet/log
Output `io.Writer` concurrency gotcha compared to stdlib
- Dominant language
- Go
- Stars
- 3.4k
- Forks
- 103
- PR merge metrics
- No merged PRs in 30d
Description
When using `log/slog` methods `WithAttrs` or `WithGroup` you get a cloned handler that has some predefined inputs.
Now `charmbracelet/log` has similar functionality provided by the `With` method that provides you a cloned `Logger`.
There is a curcial difference in making the cloned logger safe for concurrent use. While `log/slog` does provide that safety, `charmbracelet/log` does not. This can be surprising.
Specifically this is about concurrent usege of the configured output `io.Writer` interface.
`log/slog` [shares a mutex between all clones](https://cs.opensource.google/go/go/+/refs/tags/go1.24.5:src/log/slog/handler.go;l=211) for protecting concurrent writes to the output.
```go
func (h *commonHandler) clone() *commonHandler {
// We can't use assignment because we can't copy the mutex.
return &commonHandler{
// ... parts omitted for brevity ...
mu: h.mu, // mutex shared among all clones of this handler
}
}
```
`charmbracelet/log` doesn't have an output-specific mutex at all. It uses a more catch-all mutex for this purpose and [does not share it among clones](https://github.com/charmbracelet/log/blob/4dcdb75977075373f3eca87b2b7b75ed18af3c7d/logger.go#L337).
```go
// With returns a new logger with the given keyvals added.
func (l *Logger) With(keyvals ...interface{}) *Logger {
// ... parts omitted for brevity ...
l.mu.Lock()
sl := *l
l.mu.Unlock()
sl.mu = &sync.RWMutex{}
return &sl
}
```
---
I don't know whether you consider this a bug or a feature, but at the very least some documentation to highlight this behavior would be useful, especially as it differs from stdlib.
As it stands now, the configured output `io.Writer` interface needs to be safe for concurrent use. This is kind of tricky in the `os.Stderr` case, because it will depend on the specific kernel implementation whether concurrent writes are interleaved, intermixed, overlapped or what.
Contributor guide
Research direction
Review logger.go's With method and the existing documentation around cloned loggers and the configured io.Writer. Document that cloned loggers do not share an output mutex and that the configured writer must support concurrent use, noting the contrast with log/slog; done when users can find and understand this caveat.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- observability-sre
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100