diff --git a/src/bun.js/ConsoleObject.zig b/src/bun.js/ConsoleObject.zig index 9cd00c91e3..90acd2a40a 100644 --- a/src/bun.js/ConsoleObject.zig +++ b/src/bun.js/ConsoleObject.zig @@ -248,7 +248,7 @@ fn messageWithTypeAndLevel_( writer.writeAll("undefined\n") catch {}; if (message_type == .Trace) { - writeTrace(Writer, writer, global); + writeTrace(Writer, writer, global, enable_colors); buffered_writer.flush() catch {}; } } @@ -668,7 +668,7 @@ pub const TablePrinter = struct { } }; -pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject) void { +pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject, enable_ansi_colors: bool) void { var holder = ZigException.Holder.init(); var vm = VirtualMachine.get(); defer holder.deinit(vm); @@ -688,20 +688,14 @@ pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject false, ); - if (Output.enable_ansi_colors_stderr) - VirtualMachine.printStackTrace( + switch (enable_ansi_colors) { + inline else => |color| VirtualMachine.printStackTrace( Writer, writer, exception.stack, - true, - ) catch {} - else - VirtualMachine.printStackTrace( - Writer, - writer, - exception.stack, - false, - ) catch {}; + color, + ) catch {}, + } } pub const FormatOptions = struct { diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 15b01208a7..31dcfb07aa 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -2390,7 +2390,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp Output.flush(); if (!globalThis.hasException()) { - JSC.ConsoleObject.writeTrace(@TypeOf(&writer), &writer, globalThis); + JSC.ConsoleObject.writeTrace(@TypeOf(&writer), &writer, globalThis, Output.enable_ansi_colors_stderr); } Output.flush(); } diff --git a/test/js/node/worker_threads/worker_threads.test.ts b/test/js/node/worker_threads/worker_threads.test.ts index 0242c0f7f4..e439a79495 100644 --- a/test/js/node/worker_threads/worker_threads.test.ts +++ b/test/js/node/worker_threads/worker_threads.test.ts @@ -578,7 +578,7 @@ describe("stdio", () => { expect(writeFn).toHaveBeenCalledTimes(1); }); - it.todo(`console uses overridden process.${stream} in worker`, async () => { + it(`console uses overridden process.${stream} in worker`, async () => { const worker = new Worker( /* js */ ` import { Writable } from "node:stream"; @@ -600,6 +600,58 @@ describe("stdio", () => { expect(await resultPromise).toBe("[wrapped] hello\n"); }); }); + + describe("console", () => { + it("all functions are captured", async () => { + const worker = new Worker( + /* js */ ` + console.assert(); + console.assert(false); + // TODO: https://github.com/oven-sh/bun/issues/19953 + // this should be "Assertion failed: should be true," not "should be true" + // but we still want to make sure it is captured in workers + console.assert(false, "should be true"); + console.debug("debug"); + console.error("error"); + console.info("info"); + console.log("log"); + console.table([{ a: 5 }]); + // TODO: https://github.com/oven-sh/bun/issues/19952 + // this goes to the wrong place but we still want to make sure it is captured in workers + console.trace("trace"); + console.warn("warn"); + `, + { eval: true, stdout: true, stderr: true }, + ); + // normalize the random blob URL and lines and columns from internal modules + const stdout = (await readToEnd(worker.stdout)) + .replace(/blob:[0-9a-f\-]{36}/, "blob:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + .replaceAll(/\(\d+:\d+\)$/gm, "(line:col)"); + const stderr = await readToEnd(worker.stderr); + + expect(stdout).toBe(`debug +info +log +┌───┬───┐ +│ │ a │ +├───┼───┤ +│ 0 │ 5 │ +└───┴───┘ +trace + at blob:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:15:17 + at loadAndEvaluateModule (line:col) + at asyncFunctionResume (line:col) + at promiseReactionJobWithoutPromiseUnwrapAsyncContext (line:col) + at promiseReactionJob (line:col) +`); + expect(stderr).toBe(`Assertion failed +Assertion failed +should be true +error +warn +`); + }); + }); }); describe("getHeapSnapshot", () => { diff --git a/test/js/web/workers/worker-fixture-console.js b/test/js/web/workers/worker-fixture-console.js new file mode 100644 index 0000000000..bbc300eea8 --- /dev/null +++ b/test/js/web/workers/worker-fixture-console.js @@ -0,0 +1,41 @@ +import { Writable } from "node:stream"; + +class WrapStream extends Writable { + #base; + #message; + + constructor(base, message) { + super(); + this.#base = base; + this.#message = message; + } + + _write(chunk, encoding, callback) { + const string = chunk.toString("utf8"); + this.#base.write(`[${this.#message}] ${string}`, "utf8", callback); + } +} +if (Bun.isMainThread) { + process.stdout = new WrapStream(process.stdout, "parent process.stdout"); + process.stderr = new WrapStream(process.stderr, "parent process.stderr"); + new Worker(import.meta.filename); +} else { + process.stdout = new WrapStream(process.stdout, "worker process.stdout"); + process.stderr = new WrapStream(process.stderr, "worker process.stderr"); + + console.assert(); + console.assert(false); + // TODO: https://github.com/oven-sh/bun/issues/19953 + // this should be "Assertion failed: should be true," not "should be true" + // but we still want to make sure it is not in workers + console.assert(false, "should be true"); + console.debug("debug"); + console.error("error"); + console.info("info"); + console.log("log"); + console.table([{ a: 5 }]); + // TODO: https://github.com/oven-sh/bun/issues/19952 + // this goes to the wrong place but we still want to make sure it is not in workers + console.trace("trace"); + console.warn("warn"); +} diff --git a/test/js/web/workers/worker-fixture-process-stdio.js b/test/js/web/workers/worker-fixture-process-stdio.js new file mode 100644 index 0000000000..f83826a253 --- /dev/null +++ b/test/js/web/workers/worker-fixture-process-stdio.js @@ -0,0 +1,26 @@ +import { Writable } from "node:stream"; + +class WrapStream extends Writable { + #base; + #message; + + constructor(base, message) { + super(); + this.#base = base; + this.#message = message; + } + + _write(chunk, encoding, callback) { + const string = chunk.toString("utf8"); + this.#base.write(`[${this.#message}] ${string}`, "utf8", callback); + } +} + +if (Bun.isMainThread) { + process.stdout = new WrapStream(process.stdout, "parent process.stdout"); + process.stderr = new WrapStream(process.stderr, "parent process.stderr"); + new Worker(import.meta.filename); +} else { + process.stdout.write("stdout"); + process.stderr.write("stderr"); +} diff --git a/test/js/web/workers/worker.test.ts b/test/js/web/workers/worker.test.ts index 99a3c5b164..21d2bbba6f 100644 --- a/test/js/web/workers/worker.test.ts +++ b/test/js/web/workers/worker.test.ts @@ -296,6 +296,58 @@ describe("web worker", () => { expect(err.error).toBe(null); }); }); + + describe("stdio", () => { + test("process stdio in worker does not go to process stdio in parent", async () => { + const proc = Bun.spawn({ + cmd: [bunExe(), "worker-fixture-process-stdio.js"], + env: bunEnv, + cwd: __dirname, + stdout: "pipe", + stderr: "pipe", + }); + await proc.exited; + expect(proc.exitCode).toBe(0); + const stdout = await new Response(proc.stdout).text(); + const stderr = await new Response(proc.stderr).text(); + expect(stdout).toBe("stdout"); + expect(stderr).toBe("stderr"); + }); + + test("console functions in worker do not go to process stdio in worker or parent", async () => { + const proc = Bun.spawn({ + cmd: [bunExe(), "worker-fixture-console.js"], + env: bunEnv, + cwd: __dirname, + stdout: "pipe", + stderr: "pipe", + }); + await proc.exited; + expect(proc.exitCode).toBe(0); + // normalize lines and columns from internal modules + const stdout = (await new Response(proc.stdout).text()).replaceAll(/\(\d+:\d+\)$/gm, "(line:col)"); + const stderr = await new Response(proc.stderr).text(); + + expect(stdout).toBe(`debug +info +log +┌───┬───┐ +│ │ a │ +├───┼───┤ +│ 0 │ 5 │ +└───┴───┘ +trace + at ${__dirname}/worker-fixture-console.js:39:11 + at loadAndEvaluateModule (line:col) +`); + expect(stderr).toBe(`Assertion failed +Assertion failed +should be true +error +warn +`); + }); + }); }); // TODO: move to node:worker_threads tests directory