fix(multistream): isolate per-stream write() errors so remaining streams still receive log data - #2455
Open
LeSingh1 wants to merge 2 commits into
Open
fix(multistream): isolate per-stream write() errors so remaining streams still receive log data#2455LeSingh1 wants to merge 2 commits into
LeSingh1 wants to merge 2 commits into
Conversation
…logs
Previously, if any stream's synchronous write() threw an exception inside
multistream.write(), the loop would abort immediately and all streams ordered
after the failing one would never receive the log line.
This fix wraps each stream.write(data) call in a try/catch. The caught error
is surfaced via a dedicated errorEmitter so callers can observe it with
res.on('error', fn) without interfering with the existing res.emit() behaviour
(which propagates events like 'message' down to every child stream). When no
error listener is registered the error is swallowed to preserve the
non-throwing contract of multistream.write().
Two new tests are added:
- one that asserts subsequent streams receive all log lines when an earlier
stream throws synchronously
- one that asserts no exception escapes when there is no error listener
mcollina
requested changes
Jun 22, 2026
mcollina
left a comment
Member
There was a problem hiding this comment.
I think the best outcome would be to move the res object to
be a full EventEmitter instead.
LeSingh1
force-pushed
the
fix/multistream-write-error-isolation
branch
from
July 10, 2026 14:42
b60e66e to
b486f9a
Compare
mcollina
requested changes
Jul 11, 2026
| stream.emit(...args) | ||
| } | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
Ahum, this doesn't look a good idea after all. Do we need the return value to have an emit property?
If we can't remove this, the previous approach was in fact better. My bad.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
pino.multistream().write()iterates over all registered streams and callsstream.write(data)on each. There is no error isolation around individual writes: if any stream's synchronouswrite()throws, the iteration loop aborts immediately and every stream that comes after the failing one in the sorted list never receives the log line. Logs are silently dropped with no indication that anything went wrong.Minimal reproduction:
Fix
Wrap each
stream.write(data)call in atry/catchinsidemultistream.write(). The caught error is surfaced via a dedicatedEventEmitter(errorEmitter) so callers can observe it withres.on('error', fn)— without interfering with the existingres.emit()method, which propagates events like'message'down to every child stream for pino's internal config protocol.When no
'error'listener is registered the error is swallowed, preserving the existing non-throwing contract ofmultistream.write().What changed in
lib/multistream.js:errorEmitter(EventEmitter) instance.on/once/removeListener/offonresdelegating toerrorEmitter.stream.write(data)intry/catch; emits'error'onerrorEmitterwhen there is at least one listener, otherwise swallows.New tests (in
test/multistream.test.js)multistream.write continues writing to remaining streams when one stream throws synchronously— asserts that a stream ordered after a throwing stream still receives all log lines and that an'error'event is emitted for each throwing write.multistream.write does not throw when a stream write() throws and no error listener is registered— assertsdoesNotThrowwhen no listener is attached.Test results
All 32 multistream tests pass (including the 2 new ones), and the full basic + serializer suites (126 tests) continue to pass.
Checklist
res.emit()stream-propagation behaviourres.on('error')listener see no behaviour change)