Skip to content

config: let a caller tee the log stream, and the handler's structured lines - #508

Open
paulwe wants to merge 1 commit into
mainfrom
system-log-tee
Open

paulwe wants to merge 1 commit into
mainfrom
system-log-tee

Conversation

@paulwe

@paulwe paulwe commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

A deployment that ships ingress's logs somewhere has nowhere to install a tee: InitLogger builds the zap logger itself, and its variadic slot is already spoken for by the logger values. LoggerTee carries one in on the config instead. The zero value builds no core, so existing callers are unaffected.

The handler subprocess is the other half. Its own structured lines are passed straight through to the parent's stdout rather than its zap core, so a tee on the parent never sees them. NewHandlerLoggerWithSink hands them to a caller-supplied sink instead, which keeps the decision about where they go with the parent that owns the transport.

Requires protocol at or past logger.WithTee, hence the bump.

Consumed by livekit/cloud-ingress, which uses both halves to publish its logs to the observability warehouse.

@paulwe
paulwe requested a review from a team as a code owner September 22, 2026 04:08

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +37 to +38
if sink != nil {
sink(line)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Split structured logs reach sink malformed

When structured output spans multiple Write calls, sink receives only the fragment ending in }. The complete handler record is lost.

Learn more

A command's stdout and stderr are streams. One logical line can be split across multiple Write calls, especially when a record exceeds the copy buffer. This logger classifies each incoming chunk immediately and does not retain text before the last newline. A split JSON record therefore sends its first fragment through the ordinary error logger and passes only its final fragment to sink.

Example: A record {"level":"info","msg":"..."} arrives as {"level":"info", and "msg":"..."}\n. The first write is logged as an error. The sink receives only "msg":"..."}, which is invalid JSON instead of the expected complete record.

Recommended fix: Buffer incomplete text across Write calls and classify only newline-terminated records. Protect the buffer if the writer can receive concurrent calls, and define how any final unterminated record is flushed.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

… lines

A deployment that ships ingress's logs somewhere has nowhere to install a
tee: InitLogger builds the zap logger itself, and its variadic slot is
already spoken for by the logger values. LoggerTee carries one in on the
config instead. The zero value builds no core, so existing callers are
unaffected.

The handler subprocess is the other half. Its own structured lines are
passed straight through to the parent's stdout rather than its zap core,
so a tee on the parent never sees them. NewHandlerLoggerWithSink hands
them to a caller-supplied sink instead, which keeps the decision about
where they go with the parent that owns the transport.

Requires protocol at or past logger.WithTee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants