Skip to content

Commit 9f068ef

Browse files
committed
feat: add createConsoleMultiOutput for routing console entries to multiple outputs and createConsoleAsyncOutput for buffering entries for asynchronous writing.
(cherry picked from commit d7a45ed57c378c9ee912db002005fcf86e922a74)
1 parent 6e77c1c commit 9f068ef

File tree

1 file changed

+82
-2
lines changed

1 file changed

+82
-2
lines changed

packages/common/src/Console.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,5 +581,85 @@ export const createConsoleArrayOutput = (
581581
},
582582
});
583583

584-
// TODO: multiOutput - routes entries to different outputs by method
585-
// TODO: asyncOutput - buffers entries for better performance
584+
/**
585+
* Creates a {@link ConsoleOutput} that routes entries to multiple outputs.
586+
*
587+
* Useful for logging to both console and file simultaneously.
588+
*
589+
* ### Example
590+
*
591+
* ```ts
592+
* const output = createConsoleMultiOutput([
593+
* createNativeConsoleOutput(),
594+
* createConsoleArrayOutput(entries),
595+
* ]);
596+
* ```
597+
*/
598+
export const createConsoleMultiOutput = (
599+
outputs: ReadonlyArray<ConsoleOutput>,
600+
): ConsoleOutput => ({
601+
write: (entry, formatEntry) => {
602+
for (const output of outputs) output.write(entry, formatEntry);
603+
},
604+
flush: async () => {
605+
await Promise.all(outputs.map((o) => o.flush?.()));
606+
},
607+
});
608+
609+
/**
610+
* Creates a {@link ConsoleOutput} that buffers entries for async writing.
611+
*
612+
* Useful for high-throughput scenarios where synchronous logging would be a
613+
* bottleneck. Call `flush()` to ensure all buffered entries are written.
614+
*
615+
* ### Example
616+
*
617+
* ```ts
618+
* const entries: Array<ConsoleEntry> = [];
619+
* const output = createConsoleAsyncOutput({
620+
* write: async (entry) => {
621+
* entries.push(entry);
622+
* },
623+
* });
624+
*
625+
* // Usage with Bun file writer:
626+
* // const writer = Bun.file("app.log").writer();
627+
* // const output = createConsoleAsyncOutput({
628+
* // write: async (entry) => {
629+
* // writer.write(JSON.stringify(entry) + "\\n");
630+
* // },
631+
* // flush: () => writer.flush(),
632+
* // });
633+
* ```
634+
*/
635+
export const createConsoleAsyncOutput = (config: {
636+
readonly write: (entry: ConsoleEntry) => void | Promise<void>;
637+
readonly flush?: () => void | Promise<void>;
638+
}): ConsoleOutput => {
639+
const buffer: Array<ConsoleEntry> = [];
640+
let flushPromise: Promise<void> | null = null;
641+
642+
const processBuffer = async (): Promise<void> => {
643+
while (buffer.length > 0) {
644+
const entry = buffer.shift();
645+
if (entry) await config.write(entry);
646+
}
647+
await config.flush?.();
648+
};
649+
650+
return {
651+
write: (entry) => {
652+
buffer.push(entry);
653+
// Auto-flush when buffer grows (debounced)
654+
if (!flushPromise && buffer.length >= 10) {
655+
flushPromise = processBuffer().finally(() => {
656+
flushPromise = null;
657+
});
658+
}
659+
},
660+
flush: async () => {
661+
if (flushPromise) await flushPromise;
662+
await processBuffer();
663+
},
664+
};
665+
};

0 commit comments

Comments
 (0)