diff --git a/docs/runtime/nodejs-compat.mdx b/docs/runtime/nodejs-compat.mdx index 114339a97717..b3cd0872ddbd 100644 --- a/docs/runtime/nodejs-compat.mdx +++ b/docs/runtime/nodejs-compat.mdx @@ -21,7 +21,7 @@ We update this page regularly. It reflects the latest version of Bun's compatibi ### [`node:console`](https://nodejs.org/api/console.html) -🟢 Fully implemented. Bun writes console output directly to the stdout/stderr file descriptors and formats it with its own inspector. As a result, replacing `process.stdout.write` does not capture the output, and object layout differs from `util.inspect`. `console.trace()` writes to stdout and `console.time*()` to stderr. +🟢 Fully implemented. Bun writes console output directly to the stdout/stderr file descriptors and formats it with its own inspector. As a result, replacing `process.stdout.write` does not capture the output, and object layout differs from `util.inspect`. `console.trace()` writes to stderr (Node-compatible) and `console.time*()` to stderr. ### [`node:dgram`](https://nodejs.org/api/dgram.html) diff --git a/src/jsc/ConsoleObject.rs b/src/jsc/ConsoleObject.rs index a9270cf88662..3d0d8002b4b5 100644 --- a/src/jsc/ConsoleObject.rs +++ b/src/jsc/ConsoleObject.rs @@ -407,8 +407,9 @@ fn message_with_type_and_level_( // Lock/unlock a mutex incase two JS threads are console.log'ing at the same // time. We do this the slightly annoying way to avoid assigning a pointer. + // Node routes console.trace to stderr; keep Assert / warn / error there too. let use_stderr = matches!(level, MessageLevel::Warning | MessageLevel::Error) - || message_type == MessageType::Assert; + || matches!(message_type, MessageType::Assert | MessageType::Trace); let _stream_lock = ConsoleStreamLock::acquire(use_stderr); if message_type == MessageType::Clear { @@ -431,7 +432,9 @@ fn message_with_type_and_level_( return Ok(()); } - let enable_colors = if matches!(level, MessageLevel::Warning | MessageLevel::Error) { + let enable_colors = if matches!(level, MessageLevel::Warning | MessageLevel::Error) + || message_type == MessageType::Trace + { Output::enable_ansi_colors_stderr() } else { Output::enable_ansi_colors_stdout() @@ -450,7 +453,9 @@ fn message_with_type_and_level_( // long-lived `&mut ConsoleObject` across the re-derive in the empty-`Log` // arm below. let raw_writer: &mut bun_core::io::Writer = unsafe { - if matches!(level, MessageLevel::Warning | MessageLevel::Error) { + if matches!(level, MessageLevel::Warning | MessageLevel::Error) + || message_type == MessageType::Trace + { (*console).error_writer() } else { (*console).writer() diff --git a/test/js/bun/console/console-trace-stderr.test.ts b/test/js/bun/console/console-trace-stderr.test.ts new file mode 100644 index 000000000000..ba1e806f03bc --- /dev/null +++ b/test/js/bun/console/console-trace-stderr.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe } from "harness"; + +test("console.trace writes to stderr, not stdout", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", `console.trace("hello")`], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(exitCode).toBe(0); + expect(stdout).toBe(""); + expect(stderr).toContain("hello"); + expect(stderr.toLowerCase()).toMatch(/trace/i); +});