Skip to content

build: make the logs option reach Rust - #38913

Open
robobun wants to merge 3 commits into
mainfrom
farm/d38c4b24/rust-logs-knob
Open

build: make the logs option reach Rust#38913
robobun wants to merge 3 commits into
mainfrom
farm/d38c4b24/rust-logs-knob

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • The build's logs option does nothing for Rust. release-assertions (documented as "Release + assertions + logs", used by bun run build:assert) and bun run build:logs (--profile=release --logs=on) produce binaries in which every scoped_log! is compiled out, so BUN_DEBUG_<scope>=1 prints nothing; conversely bun bd --logs=off still logs. Configure even reports the option as live: bun scripts/build.ts --profile=release-assertions --configure-only prints features: assertions, logs, baseline.
  • Cause, build side: scripts/build/buildOptionsRs.ts:67 emits pub const ENABLE_LOGS: bool = cfg!(bun_debug); and scripts/build/rust.ts:467 passes --cfg=bun_debug only when cfg.debug. cfg.logs (config.ts:889, default debug) is read nowhere except the configure-time features: line (config.ts:1576); C++ never consumed it, so Rust is the only consumer that matters.
  • Cause, Rust side: the loggers do not even read ENABLE_LOGS. scoped_log! (src/bun_core/output.rs:1550), syslog! (src/sys/lib.rs:4930), mark_binding! (src/bun_core/Global.rs:442), mark_binding() / mark_member_binding() (src/jsc/lib.rs:1384, :1393) and the ReachableFiles dump (src/bundler/bundle_v2.rs:1981) gate on env::IS_DEBUG, with comments asserting it equals ENABLE_LOGS. Fixing the generated constant alone would therefore still log nothing in a non-debug build.
  • On main (generated into scratch build dirs, commands in the details block): release-assertions gets ENABLE_LOGS = cfg!(bun_debug) and no --cfg=bun_debug in the cargo edge; debug --logs=off gets --cfg=bun_debug.

Fix

  • rust.ts: pass --check-cfg=cfg(bun_logs) always and --cfg=bun_logs exactly when cfg.logs, next to bun_debug / bun_asan. buildOptionsRs.ts emits ENABLE_LOGS = cfg!(bun_logs). Cargo.toml registers cfg(bun_logs) in unexpected_cfgs like the other RUSTFLAGS cfgs, so a bare cargo check does not warn about the generated file.
  • The six sites above gate on env::ENABLE_LOGS instead of IS_DEBUG; ScopedLogger::is_visible() / log() already did. ENABLE_LOGS gets a doc comment; the comments claiming the two constants are equal are updated.
  • Why this is right: ENABLE_LOGS is the Rust spelling of the logs option (the Zig build passed it as -Denable_logs=${cfg.logs} and gated Output.Scoped on it), and the option was only meant to default to debug, not to be debug. The two constants became one value in build: enable Rust debug-assertions for asan/assertions builds; decouple IS_DEBUG #32520, which moved the loggers onto IS_DEBUG so that release-asan would not pick up logs; they are unchanged by this PR because cfg.logs is false there. Every profile CI builds (ci-build, with and without --asan=on) and the default debug build resolve to the same ENABLE_LOGS as before (the test pins debug, release and release+asan); only release-assertions and explicit --logs=on|off change, and those are the cases that asked for it.
  • Why a cfg and not a literal in build_options.rs: a bare cargo check, cargo clippy and the cargo miri test CI job read build/debug/codegen/build_options.rs with no RUSTFLAGS. With a literal true from a debug configure, scoped_log! bodies would go live there and ScopedLogger::evaluate_is_visible() scans the environment through bun_core::strings (Highway FFI), which Miri cannot call; bun_ptr's ref-count paths and bun_shell_parser's brace expansion, both in the Miri crate list, call scoped_log!. A cfg keeps bare cargo on logs-off semantics exactly as bun_debug does today.
  • Plain release codegen is unchanged: cfg!(bun_logs) without the flag is the same false constant the old cfg!(bun_debug) was, so the if still folds away. The new --check-cfg flag changes RUSTFLAGS, so the first build after this lands recompiles the Rust side once.
  • rust.ts also stops passing -Zlocation-detail=none when cfg.logs is set (second commit, found in self-review). That flag was keyed on release && !assertions, which release --logs=on satisfies, and the loggers this PR turns on there include mark_binding() and the test runner's group::begin(), which print Location::caller(): with the flag they logged [jsc] (<redacted>:0). release-assertions already kept the flag off; shipped profiles have logs off and are unchanged.
  • Out of scope, noted for the record: --tinycc has the same kind of bug (ENABLE_TINYCC and tcc_externs! hard-code the target default instead of following cfg.tinycc) and can use the same --cfg mechanism; it touches the FFI crate and overlaps feat(ffi): enable bun:ffi on FreeBSD #31528, so it is left to a separate change.
  • Verified:
    • test/internal/source-lints/build-logs-option.test.ts (new file; this directory keeps one file per build-script topic and nothing existing covers rustflags or build_options.rs): resolves release-assertions, release --logs=on, debug --logs=off, debug, release and release+asan configs, generates build_options.rs for each into a temp dir, reads the cfg it names and checks it against the rustflags cargoBuildInvocation() emits; checks the cfg is registered in Cargo.toml; checks the three macros gate on ENABLE_LOGS; scans src/**/*.rs for IS_DEBUG ... .is_visible() guards; and checks -Zlocation-detail=none is passed for release but not for release --logs=on or release-assertions (fails with the second commit's rust.ts hunk reverted). With src/ stashed the two source tests fail (listing the six sites); with scripts/ stashed the three changed configs fail (bun_debug is named but not set for release-assertions and --logs=on, set for debug --logs=off); with only Cargo.toml stashed the registration test fails; the debug / release / release+asan cases pass in every state, which is the "nothing else moves" claim. 0.3s on a release bun, ~6s under bun bd.
    • bun bd (debug, --cfg=bun_logs now in the cargo edge): BUN_DEBUG_fs=1 bun bd -e 1, BUN_DEBUG_SYS=1, BUN_DEBUG_JSC=1 (mark_binding) and BUN_DEBUG_ReachableFiles=1 bun bd build (the bundle_v2 site) all still print.
    • bun scripts/build.ts --profile=release --logs=on (what bun run build:logs runs) on this branch, on top of a release tree built from main the day before: with BUN_DEBUG_fs=1 the main-built bun-profile prints nothing and the rebuilt one prints [fs] open(/) ...; BUN_DEBUG_JSC=1 prints [jsc] (src/jsc/VirtualMachine.rs:2382) ... (mark_binding; it was [jsc] (<redacted>:0) before the second commit, rebuilt to confirm); BUN_DEBUG_SYS=1 bun-profile build --compile prints [sys] ioctl_ficlone(6, 5) = -1 / [sys] copy_file_range(...) (syslog!, from bun_sys::copy_file). Without any BUN_DEBUG_* variable the binary stays quiet (BUN_DEBUG_QUIET_LOGS=1 in this environment; visible scopes otherwise print by default, as in debug builds). The stripped bun behaves the same. Note that some individual call sites are additionally wrapped in cfg!(debug_assertions) (Fd::close's [sys] close(...), posix_spawn's), so those still need release-assertions; that gating predates this PR.
    • --configure-only for release-assertions, release, release --logs=on, debug, debug --logs=off and release-asan: --cfg=bun_logs appears in exactly the first, third and fourth; --check-cfg=cfg(bun_logs) in all six.
    • rustfmt --check on the touched .rs files; tsc -p scripts/build reports the same 11 pre-existing diagnostics as main; windows-cross-config, macos-cross-config and build-debug-info-flags tests still pass.

Background

  • logs option: a boolean in the flat Config (scripts/build/config.ts), defaulting to debug; settable per profile (profiles.ts, only release-assertions sets it) or with --logs=on|off. It is the TypeScript build's version of the old CMake ENABLE_LOGS / Zig -Denable_logs.
  • Scoped logging: declare_scope!(name, hidden|visible) defines a ScopedLogger; scoped_log! writes through it when the scope is enabled at runtime (BUN_DEBUG_<name>=1, BUN_DEBUG_ALL, or visible by default). The macro wraps the whole body, including argument evaluation, in if <const> && scope.is_visible() so that builds with the constant false compile the call to nothing; that constant is what this PR changes.
  • build_options.rs: generated at configure time by buildOptionsRs.ts from Config and include!d as bun_core::build_options; env.rs re-exports its values as Environment::*. A bare cargo invocation (no BUN_CODEGEN_DIR) reads the copy in build/debug/codegen/, which is why constants that must follow RUSTFLAGS rather than whichever profile last configured that directory are written as cfg!(...).
  • --cfg / --check-cfg: rustc takes arbitrary cfg names from RUSTFLAGS; --check-cfg=cfg(name) (or the unexpected_cfgs entry in Cargo.toml) declares the name so the unexpected_cfgs lint does not fire on cfg!(name) in builds that do not set it. rust.ts already wires bun_asan, bun_debug, bun_codegen_embed and socket_fault_injection this way.
  • IS_DEBUG (cfg!(bun_debug)): true only for the dev cargo profile; it gates Debug-build conveniences (debug_warn!, DUMP_SOURCE, the bun-debug name). It stays as is; logging is the one thing moved off it.
Configure probes on main vs this branch
$ bun scripts/build.ts --profile=release-assertions --build-dir=/tmp/relassert --configure-only
  features     assertions, logs, baseline
$ grep ENABLE_LOGS /tmp/relassert/codegen/build_options.rs
pub const ENABLE_LOGS: bool = cfg!(bun_debug);
$ grep -o -- '--cfg=[a-z_]*' /tmp/relassert/build.ninja | sort -u
--cfg=bun_codegen_embed                                   <- no bun_debug: ENABLE_LOGS is false

$ bun scripts/build.ts --profile=debug --logs=off --build-dir=/tmp/dbg-nologs --configure-only
  features     asan, assertions, baseline                 <- logs reported off...
$ grep -o -- '--cfg=[a-z_]*' /tmp/dbg-nologs/build.ninja | sort -u
--cfg=bun_asan
--cfg=bun_debug                                           <- ...but ENABLE_LOGS = cfg!(bun_debug) is true
--cfg=socket_fault_injection

This branch (ENABLE_LOGS = cfg!(bun_logs) in every build_options.rs; --check-cfg=cfg(bun_logs) in every build.ninja):

release-assertions   --cfg=bun_codegen_embed --cfg=bun_logs
release --logs=on    --cfg=bun_codegen_embed --cfg=bun_logs
debug                --cfg=bun_asan --cfg=bun_debug --cfg=bun_logs --cfg=socket_fault_injection
debug --logs=off     --cfg=bun_asan --cfg=bun_debug --cfg=socket_fault_injection
release              --cfg=bun_codegen_embed
release-asan         --cfg=bun_asan --cfg=bun_codegen_embed --cfg=socket_fault_injection

The source scan's output with src/ at main:

+   "src/bun_core/Global.rs:442",
+   "src/bun_core/output.rs:1550",
+   "src/bundler/bundle_v2.rs:1981",
+   "src/jsc/lib.rs:1384",
+   "src/jsc/lib.rs:1393",
+   "src/sys/lib.rs:4930",

The build's `logs` option (on by default in debug, set by the
release-assertions profile and `bun run build:logs`, overridable with
--logs=on|off) did nothing for Rust: buildOptionsRs.ts emitted
`ENABLE_LOGS = cfg!(bun_debug)` and rust.ts only passes --cfg=bun_debug
for Debug builds, so a release build configured with logs compiled every
scoped logger out and a debug build configured without still logged.
scoped_log!, syslog!, mark_binding! and the two mark_binding fns also
gated on IS_DEBUG directly, so fixing the constant alone would not have
been enough.

rust.ts now passes --cfg=bun_logs exactly when cfg.logs is set (plus the
matching --check-cfg, and the cfg is registered in Cargo.toml for bare
cargo), build_options.rs reads ENABLE_LOGS from it, and the loggers gate
on ENABLE_LOGS. Debug, release and release-asan resolve to the same
values as before; release-assertions and --logs=on|off now take effect.

A cfg rather than a literal in build_options.rs keeps a bare
`cargo check` / `cargo miri test` (which read build/debug's generated
file without any RUSTFLAGS) on logs-off semantics, as with bun_debug.
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e90fc627-d982-44d0-8ffa-b54d8d50e43e

📥 Commits

Reviewing files that changed from the base of the PR and between 7d276b9 and 63181fb.

📒 Files selected for processing (12)
  • Cargo.toml
  • scripts/build/buildOptionsRs.ts
  • scripts/build/rust.ts
  • src/bun_core/Global.rs
  • src/bun_core/env.rs
  • src/bun_core/output.rs
  • src/bundler/bundle_v2.rs
  • src/jsc/lib.rs
  • src/output/lib.rs
  • src/sys/lib.rs
  • src/sys/windows/mod.rs
  • test/internal/source-lints/build-logs-option.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on main with configure only (no build needed): bun scripts/build.ts --profile=release-assertions --configure-only reports features: assertions, logs while the generated build_options.rs has ENABLE_LOGS = cfg!(bun_debug) and the cargo edge passes no --cfg=bun_debug; --profile=debug --logs=off still passes --cfg=bun_debug. Probes and the six IS_DEBUG logger guards are listed in the PR body.

Test: test/internal/source-lints/build-logs-option.test.ts. Fails on main for the release-assertions, release --logs=on and debug --logs=off configs and for the logger guards in src/; passes with this branch. The second commit (keep #[track_caller] locations in logs builds, from self-review) adds one more case that fails without its rust.ts hunk.

Builds: debug rebuilt with the new rustflags, BUN_DEBUG_<scope>=1 bun bd -e 1 still logs. --profile=release --logs=on rebuilt on this branch logs through scoped_log!, syslog! and mark_binding with real file:line ([fs] open(/), [sys] copy_file_range(...), [jsc] (src/jsc/VirtualMachine.rs:2382)) where the main-built release binary printed nothing; details in the PR body.

CI on the first push: GitHub checks green (Source lints runs the new test; cargo clippy and cargo miri cover bare cargo against the generated cfg!(bun_logs)); Buildkite had no test failures, only the darwin 14 aarch64 test lane waiting on agent capacity. The comment-length bot threads are answered and resolved.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical wiring of a new --cfg=bun_logs flag following the existing bun_debug/bun_asan pattern, plus swapping six IS_DEBUG guards to ENABLE_LOGS.

What was reviewed:

  • Confirmed cfg.logs defaults to debug (config.ts:889) and release-assertions sets it (profiles.ts:173), so debug/release/release-asan resolve to the same ENABLE_LOGS as before; only release-assertions and explicit --logs=on|off change.
  • Checked ScopedLogger::is_visible() already gates on ENABLE_LOGS, so the #[cfg(bun_debug)] self-check in scoped_writer() remains unreachable under debug --logs=off.
  • Verified no remaining IS_DEBUG && ….is_visible() sites in src/**/*.rs; the new test's mockToolchain/linuxConfig pattern matches sibling build-debug-info-flags.test.ts.
Extended reasoning...

Overview

This PR makes the build system's logs option actually reach Rust code. It adds a --cfg=bun_logs RUSTFLAGS entry in scripts/build/rust.ts (gated on cfg.logs), changes buildOptionsRs.ts to emit ENABLE_LOGS = cfg!(bun_logs) instead of cfg!(bun_debug), registers the cfg in Cargo.toml's unexpected_cfgs list, and swaps six logger guard sites from env::IS_DEBUG to env::ENABLE_LOGS (in scoped_log!, syslog!, mark_binding!, two mark_binding fns, and one bundler dump). The remaining edits are comment/doc updates. A new source-lint test pins the config→rustflags→constant chain for six profiles and scans src/**/*.rs for regressions.

Security risks

None. This affects only compile-time gating of debug-log macros; no user input handling, no auth/crypto, no runtime API surface. Release builds are byte-for-byte unchanged (cfg!(bun_logs) without the flag folds to the same false as cfg!(bun_debug) did).

Level of scrutiny

Low-to-moderate. The change is mechanical and follows the exact pattern already used for bun_asan/bun_debug/socket_fault_injection in rust.ts and Cargo.toml. It touches build plumbing and debug-only logging infrastructure, not shipped code paths. I verified against config.ts and profiles.ts that every CI-built profile (debug, release, release+asan) resolves ENABLE_LOGS identically before and after — only release-assertions and explicit --logs= overrides change, which is the stated intent.

Other factors

  • The new test follows the established mockToolchain()/linuxConfig() pattern from build-debug-info-flags.test.ts and windows-cross-config.test.ts (same fake-toolchain, same linuxSysroot: buildDir trick). It uses tempDir, is hermetic, and the PR reports 0.3s runtime.
  • I confirmed ScopedLogger::is_visible() (output.rs:1460) already returns false when !ENABLE_LOGS, so log() early-returns before reaching scoped_writer() — the #[cfg(bun_debug)] unreachable! self-check cannot fire under debug --logs=off. The comment update there (dropping release-assertions from the list of profiles with debug_assertions && !ENABLE_LOGS) is correct post-change.
  • Grep confirms no remaining IS_DEBUG … is_visible() guards in the tree, matching the test's source scan.
  • No prior human or bot review comments to address (CodeRabbit was rate-limited).

-Zlocation-detail=none was passed for every non-assertions release
build, which now includes `release --logs=on` (bun run build:logs). The
mark_binding() and test-runner group::begin() loggers that build turns
on print Location::caller(), so they logged `<redacted>:0`. Gate the
flag on !cfg.logs as well; the shipped profiles have logs off and are
unchanged.
Comment thread src/bun_core/Global.rs Outdated
Comment thread src/bun_core/env.rs Outdated
Comment thread src/bun_core/output.rs Outdated
Comment thread src/bun_core/output.rs Outdated
Comment thread src/output/lib.rs
Comment thread src/sys/lib.rs
Comment thread src/bun_core/Global.rs
Comment thread src/bun_core/env.rs
Comment thread src/bun_core/output.rs
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:51 AM PT - Aug 15th, 2026

@robobun, your commit 63181fb is building: #98002

Comment thread src/bun_core/output.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Because it touches build infrastructure across several layers (rust.ts RUSTFLAGS, workspace Cargo.toml, and the scoped_log!/syslog!/mark_binding! macro gates), a human look would still be worthwhile.

What was reviewed:

  • The new bun_logs cfg follows the exact bun_asan/bun_debug pattern in rust.ts and unexpected_cfgs; cfg.logs defaults to debug (config.ts:889), so debug/release/release-asan resolve to the same ENABLE_LOGS as before.
  • The six IS_DEBUGENABLE_LOGS swaps are the complete set — grepped src/**/*.rs for remaining IS_DEBUG … is_visible() guards, none left; the new source-lint test enforces this.
  • The added && !cfg.logs on -Zlocation-detail=none only affects release --logs=on (release-assertions was already excluded by !cfg.assertions), so shipped release binaries are unchanged.
Extended reasoning...

Overview

This PR wires the build's logs option through to Rust. It adds a --cfg=bun_logs RUSTFLAG (set when cfg.logs), changes the generated build_options::ENABLE_LOGS to read cfg!(bun_logs) instead of cfg!(bun_debug), registers the cfg in Cargo.toml's unexpected_cfgs, and updates six logger gates (scoped_log!, syslog!, mark_binding!, mark_binding(), mark_member_binding(), and the ReachableFiles dump) to check env::ENABLE_LOGS instead of env::IS_DEBUG. It also gates -Zlocation-detail=none on !cfg.logs so #[track_caller]-based loggers keep their file:line in a logs-enabled release build. A new 223-line source-lint test pins every piece: it resolves six configs, generates build_options.rs for each into a temp dir, checks the emitted rustflags, verifies the Cargo.toml registration, and scans src/**/*.rs for the old guard pattern.

Security risks

None. This affects only debug-logging plumbing and build-time RUSTFLAGS. No user-facing API, no untrusted-input handling, no auth/crypto surface.

Level of scrutiny

Moderate. Each individual change is mechanical — the new cfg mirrors the existing bun_asan/bun_debug/socket_fault_injection wiring exactly, and the Rust edits are one-token swaps in compile-time-const conditions plus comment rewording. Shipped-binary behavior is unchanged: cfg.logs defaults to debug, so release (false), release-asan (false), and debug (true) all produce the same ENABLE_LOGS value as before. Only release-assertions (which explicitly sets logs: true and is documented as "Release + assertions + logs") and manual --logs=on|off overrides change — that is the bug being fixed. However, the change spans build scripts, the workspace Cargo.toml, and macros that expand at every scoped_log! call site, and it changes RUSTFLAGS (forcing a one-time full Rust recompile after merge). That cross-cutting nature makes it worth a maintainer's confirmation rather than an auto-approve.

Other factors

The comment-cop bot flagged over-long comments in an earlier revision; the author shortened them in 63181fb and all threads are resolved. CI is green on GitHub checks (including cargo clippy, cargo miri, and the new source-lint test); Buildkite build 97515 passed 177/179 with the remainder being an unrelated darwin agent-capacity stall. No CODEOWNERS cover these paths, and there are no outstanding human review comments. The scoped_writer() self-check under #[cfg(bun_debug)] remains sound: in a debug build cfg.logs defaults to true, so the unreachable! only fires if someone runs bun bd --logs=off and then reaches a logger — which is exactly what the check is meant to catch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant