mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
Fix process.emitWarning to use Node.js-compatible formatting
This change makes Bun's warning output much less scary by matching Node.js behavior. Changes: - Add --trace-warnings CLI flag support - Format warnings as: (bun:PID) WarningName: message - Show stack traces only when --trace-warnings is passed - Show helpful hint about --trace-warnings flag (only once per process) - Warnings go to stderr (not console.warn which showed full error objects) Before: process.emitWarning() would dump full error object with stack trace, making every warning look like a critical error After: - Without --trace-warnings: Shows minimal format with helpful hint - With --trace-warnings: Shows stack trace like Node.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1450,6 +1450,65 @@ Process::~Process()
|
||||
}
|
||||
|
||||
extern "C" bool Bun__NODE_NO_WARNINGS();
|
||||
extern "C" bool Bun__NODE_TRACE_WARNINGS();
|
||||
|
||||
// Track whether we've shown the hint message about --trace-warnings
|
||||
static bool hasShownTraceWarningsHint = false;
|
||||
|
||||
static void printWarningToStderr(JSC::JSGlobalObject* globalObject, JSValue errorInstance, bool traceWarnings)
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
|
||||
// Get the warning name (e.g., "Warning", "DeprecationWarning")
|
||||
auto warningName = errorInstance.get(globalObject, vm.propertyNames->name);
|
||||
String nameStr = "Warning"_s;
|
||||
if (warningName.isString()) {
|
||||
nameStr = warningName.getString(globalObject);
|
||||
}
|
||||
|
||||
// Get the warning message
|
||||
auto warningMessage = errorInstance.get(globalObject, vm.propertyNames->message);
|
||||
String messageStr = ""_s;
|
||||
if (warningMessage.isString()) {
|
||||
messageStr = warningMessage.getString(globalObject);
|
||||
}
|
||||
|
||||
// Get PID
|
||||
pid_t pid = getpid();
|
||||
|
||||
// Format: (bun:PID) WarningName: message
|
||||
auto firstLine = makeString("(bun:"_s, String::number(pid), ") "_s, nameStr);
|
||||
if (!messageStr.isEmpty()) {
|
||||
firstLine = makeString(firstLine, ": "_s, messageStr);
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s\n", firstLine.utf8().data());
|
||||
|
||||
if (traceWarnings) {
|
||||
// Show stack trace
|
||||
auto stackValue = errorInstance.get(globalObject, vm.propertyNames->stack);
|
||||
if (stackValue.isString()) {
|
||||
String stackStr = stackValue.getString(globalObject);
|
||||
// The stack typically includes the error name and message as first line,
|
||||
// so we need to extract just the stack frames
|
||||
auto lines = stackStr.split('\n');
|
||||
bool firstLine = true;
|
||||
for (const auto& line : lines) {
|
||||
// Skip the first line if it looks like an error message line
|
||||
if (firstLine && (line.startsWith(nameStr) || line.contains(messageStr))) {
|
||||
firstLine = false;
|
||||
continue;
|
||||
}
|
||||
firstLine = false;
|
||||
fprintf(stderr, "%s\n", line.utf8().data());
|
||||
}
|
||||
}
|
||||
} else if (!hasShownTraceWarningsHint) {
|
||||
// Show the hint about --trace-warnings only once per process
|
||||
fprintf(stderr, "(Use `bun --trace-warnings ...` to show where the warning was created)\n");
|
||||
hasShownTraceWarningsHint = true;
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunction_emitWarning, (JSC::JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
@@ -1466,8 +1525,8 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_emitWarning, (JSC::JSGlobalObject * lexicalG
|
||||
process->wrapped().emit(ident, args);
|
||||
return JSValue::encode(jsUndefined());
|
||||
} else if (!Bun__NODE_NO_WARNINGS()) {
|
||||
auto jsArgs = JSValue::encode(value);
|
||||
Bun__ConsoleObject__messageWithTypeAndLevel(reinterpret_cast<Bun::ConsoleObject*>(globalObject->consoleClient().get())->m_client, static_cast<uint32_t>(MessageType::Log), static_cast<uint32_t>(MessageLevel::Warning), globalObject, &jsArgs, 1);
|
||||
// Use Node.js-compatible warning formatting
|
||||
printWarningToStderr(globalObject, value, Bun__NODE_TRACE_WARNINGS());
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
@@ -342,6 +342,11 @@ pub export fn Bun__NODE_NO_WARNINGS() bool {
|
||||
return bun.feature_flag.NODE_NO_WARNINGS.get();
|
||||
}
|
||||
|
||||
extern var Bun__Node__ProcessTraceWarnings: bool;
|
||||
pub export fn Bun__NODE_TRACE_WARNINGS() bool {
|
||||
return Bun__Node__ProcessTraceWarnings;
|
||||
}
|
||||
|
||||
pub export fn Bun__suppressCrashOnProcessKillSelfIfDesired() void {
|
||||
if (bun.feature_flag.BUN_INTERNAL_SUPPRESS_CRASH_ON_PROCESS_KILL_SELF.get()) {
|
||||
bun.crash_handler.suppressReporting();
|
||||
|
||||
@@ -102,6 +102,7 @@ pub const runtime_params_ = [_]ParamType{
|
||||
clap.parseParam("--expose-gc Expose gc() on the global object. Has no effect on Bun.gc().") catch unreachable,
|
||||
clap.parseParam("--no-deprecation Suppress all reporting of the custom deprecation.") catch unreachable,
|
||||
clap.parseParam("--throw-deprecation Determine whether or not deprecation warnings result in errors.") catch unreachable,
|
||||
clap.parseParam("--trace-warnings Show stack traces on process warnings") catch unreachable,
|
||||
clap.parseParam("--title <STR> Set the process title") catch unreachable,
|
||||
clap.parseParam("--zero-fill-buffers Boolean to force Buffer.allocUnsafe(size) to be zero-filled.") catch unreachable,
|
||||
clap.parseParam("--use-system-ca Use the system's trusted certificate authorities") catch unreachable,
|
||||
@@ -784,6 +785,9 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
if (args.flag("--throw-deprecation")) {
|
||||
Bun__Node__ProcessThrowDeprecation = true;
|
||||
}
|
||||
if (args.flag("--trace-warnings")) {
|
||||
Bun__Node__ProcessTraceWarnings = true;
|
||||
}
|
||||
if (args.option("--title")) |title| {
|
||||
CLI.Bun__Node__ProcessTitle = title;
|
||||
}
|
||||
@@ -1319,6 +1323,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
export var Bun__Node__ZeroFillBuffers = false;
|
||||
export var Bun__Node__ProcessNoDeprecation = false;
|
||||
export var Bun__Node__ProcessThrowDeprecation = false;
|
||||
export var Bun__Node__ProcessTraceWarnings = false;
|
||||
|
||||
pub const BunCAStore = enum(u8) { bundled, openssl, system };
|
||||
pub export var Bun__Node__CAStore: BunCAStore = .bundled;
|
||||
|
||||
165
test/js/node/process/process-trace-warnings.test.ts
Normal file
165
test/js/node/process/process-trace-warnings.test.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
test("process.emitWarning without --trace-warnings shows minimal format", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `process.emitWarning('test warning');`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should show minimal format with PID
|
||||
expect(stderr).toMatch(/\(bun:\d+\) Warning: test warning/);
|
||||
// Should show the hint about --trace-warnings
|
||||
expect(stderr).toContain("(Use `bun --trace-warnings ...` to show where the warning was created)");
|
||||
// Should NOT show stack trace
|
||||
expect(stderr).not.toContain("at ");
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("process.emitWarning with --trace-warnings shows stack trace", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `process.emitWarning('test warning');`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "--trace-warnings", "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should show minimal format with PID
|
||||
expect(stderr).toMatch(/\(bun:\d+\) Warning: test warning/);
|
||||
// Should show stack trace
|
||||
expect(stderr).toContain("at ");
|
||||
// Should NOT show the hint when --trace-warnings is used
|
||||
expect(stderr).not.toContain("(Use `bun --trace-warnings ...` to show where the warning was created)");
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("process.emitWarning with custom type", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `process.emitWarning('custom message', 'CustomWarning');`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should show custom warning type
|
||||
expect(stderr).toMatch(/\(bun:\d+\) CustomWarning: custom message/);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("multiple warnings show hint only once", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `
|
||||
process.emitWarning('warning 1');
|
||||
process.emitWarning('warning 2');
|
||||
process.emitWarning('warning 3');
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should show all three warnings
|
||||
expect(stderr).toContain("Warning: warning 1");
|
||||
expect(stderr).toContain("Warning: warning 2");
|
||||
expect(stderr).toContain("Warning: warning 3");
|
||||
|
||||
// Should show the hint only once
|
||||
const hintCount = (stderr.match(/Use `bun --trace-warnings/g) || []).length;
|
||||
expect(hintCount).toBe(1);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("DeprecationWarning is shown by default", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `process.emitWarning('deprecated API', 'DeprecationWarning');`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toMatch(/\(bun:\d+\) DeprecationWarning: deprecated API/);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("NODE_NO_WARNINGS=1 suppresses warnings", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `process.emitWarning('test warning');`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: { ...bunEnv, NODE_NO_WARNINGS: "1" },
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Should NOT show any warning
|
||||
expect(stderr).not.toContain("Warning:");
|
||||
expect(stderr).not.toContain("test warning");
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("process.emitWarning with code and detail", async () => {
|
||||
using dir = tempDir("trace-warnings-test", {
|
||||
"test.js": `
|
||||
process.emitWarning('test warning', {
|
||||
type: 'CustomWarning',
|
||||
code: 'CODE001',
|
||||
detail: 'Additional details here'
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "test.js"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toMatch(/\(bun:\d+\) CustomWarning: test warning/);
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user