The handler calls logger.FCScope(r.Context()). Every request now has its own buffer for log lines.
1A request gets its own buffer
2A debug line is held back
3Another debug line
4The request succeeds: silence
5A second request, same lines
6This time an error is logged
7The error flushes the whole trail
←→ step · space play
All steps as text
A request gets its own buffer. The handler calls logger.FCScope(r.Context()). Every request now has its own buffer for log lines.
A debug line is held back. log.DebugContext(ctx, "loaded config") goes into the request's buffer. Nothing is written to the sink yet.
Another debug line. log.DebugContext(ctx, "queried db") joins it in the buffer. The sink is still empty.
The request succeeds: silence. The request finishes without an error. A successful request logs nothing below the activation level, so neither debug line is ever written.
A second request, same lines. The next request gets a fresh buffer and logs the same two debug lines. They wait in its buffer.
This time an error is logged. log.ErrorContext(ctx, "boom") fires inside the request.
The error flushes the whole trail. The first error flushes the buffer: both debug lines and the error are written. You get the full story exactly when something breaks.
The setup from the README:
fc:=logger.NewFingersCrossed(logger.NewWriterSink(os.Stderr,logger.NewJSONEncoder(),logger.LevelTrace),)log:=logger.New(logger.WithTransport(logger.NewSyncTransport(fc)),logger.WithLevel(logger.LevelTrace))funchandler(whttp.ResponseWriter,r*http.Request){ctx:=logger.FCScope(r.Context())// one buffer per requestlog.DebugContext(ctx,"loaded config")log.DebugContext(ctx,"queried db")// if everything succeeds → nothing is emitted// if log.ErrorContext(ctx, "boom") fires → the two Debug lines + the error are all flushed}
The README calls this the killer feature: full detail when a request fails, and quiet logs when it does not.