v0.1.12 SetConsoleWriter routes console output to a host writer
Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s

A host that draws on the terminal (a TUI) cannot let the logger write to
stdout or stderr, yet the records are worth showing inside the host.
SetErrorHandler already covers internal diagnostics; SetConsoleWriter is
its sibling for console output.

- SetConsoleWriter(w) sends console output of every console_target to w,
  split's warn-and-above records included; nil restores stdout/stderr.
  It holds across ApplyConfig and is safe while logging.
- One stored value: ApplyConfig and SetConsoleWriter publish the console
  writers through storeConsole; the processor's split branch now reads
  the stored warn writer instead of writing os.Stderr directly.
- TestLoggerConsoleTargets becomes TestConsoleWriterTakesEveryTarget,
  which asserts where each target's records land; a second test checks
  that nil restores the configured writers.
- README names both hooks for terminal hosts; the API doc's callback
  contract covers console writers.

Verified: go build ./... ok; gofmt -l on changed files clean; go vet ./...
ok; go test ./... ok (all four packages, under 1 s); go test -race on the
new tests ok; mutation run 6 of 6 caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyaMAhyEzAp3ULLWJZG4jQ
This commit is contained in:
Claude authored and Lixen Wraith committed 2026-10-10 20:14:00 -04:00
1 parent 2417e51da1
commit fe711ed2a5
7 files changed
+72 -20

No files matched your search

+30 -8
View File
@@ -22,6 +22,7 @@ type Logger struct {
ctxKeys atomic.Pointer[formatter.ContextKeys]
spawner atomic.Pointer[func(func())]
errHandler atomic.Pointer[func(string)]
consoleW io.Writer // SetConsoleWriter's writer, under initMu
state State
initMu sync.Mutex // serializes lifecycle and configuration changes
sendMu sync.RWMutex // joins enqueues before detaching a processor
@@ -185,14 +186,7 @@ func (l *Logger) applyConfig(cfg *Config) error {
}
}
var writer io.Writer = io.Discard
if cfg.EnableConsole {
writer = os.Stdout
if cfg.ConsoleTarget == "stderr" {
writer = os.Stderr
}
}
l.state.StdoutWriter.Store(&sink{w: writer})
l.storeConsole(cfg)
l.currentConfig.Store(cfg)
l.rebuildFormatter(cfg)
l.state.Flags.Store(flagsFromConfig(cfg))
@@ -382,6 +376,34 @@ func (l *Logger) SetErrorHandler(fn func(string)) {
l.errHandler.Store(&fn)
}
// SetConsoleWriter sends console output to w instead of stdout and stderr,
// for every console_target, split included; nil restores them. It holds across
// ApplyConfig and is safe while logging. Writes must return promptly and must
// not call lifecycle/configuration methods or Flush, as for SetErrorHandler.
func (l *Logger) SetConsoleWriter(w io.Writer) {
l.initMu.Lock()
defer l.initMu.Unlock()
l.consoleW = w
l.storeConsole(l.getConfig())
}
// storeConsole publishes the writers the processor uses for cfg's console output
func (l *Logger) storeConsole(cfg *Config) {
w, warn := l.consoleW, l.consoleW
switch {
case !cfg.EnableConsole:
w, warn = io.Discard, io.Discard
case w != nil:
case cfg.ConsoleTarget == "stderr":
w, warn = os.Stderr, os.Stderr
case cfg.ConsoleTarget == "split":
w, warn = os.Stdout, os.Stderr
default:
w, warn = os.Stdout, os.Stdout
}
l.state.StdoutWriter.Store(&sink{w: w, warn: warn})
}
// SetContextKeys names the record keys for Context values; empty names are
// omitted. Safe to call at any time; rebuilds the formatter.
func (l *Logger) SetContextKeys(tag string, vals ...string) {