feat: gate all logging behind logging feature flag#59
feat: gate all logging behind logging feature flag#59
logging feature flag#59Conversation
Make tracing, tracing-subscriber, and tracing-appender optional dependencies activated by the new `logging` feature flag. When disabled, all log macros compile to no-ops with zero binary overhead. - `default = ["logging"]` so dev builds get logging automatically - Release without logging: `cargo build --release --no-default-features` - New `src/logging.rs` facade re-exports tracing macros or no-ops - All 25 library files switched from `use tracing::` to `use crate::logging::` - CLI log flags (--log-level, --log-format, --log-dir, --log-max-files) gated behind `#[cfg(feature = "logging")]` - Subscriber init in both binaries gated behind the feature
Update CI/release workflows and deployment scripts to build release binaries with --no-default-features, which excludes the logging feature flag. Tests retain logging (default features) for debugging visibility. Affected: - .github/workflows/ci.yml — CI build job - .github/workflows/release.yml — cross and native release builds - scripts/testnet/build-and-deploy.sh — production deployment - scripts/testnet/testnet-endurance.sh — endurance test binary
…roach Keep logging in default features so dev builds are ergonomic. Release builds explicitly use --no-default-features to strip logging.
There was a problem hiding this comment.
Pull request overview
Introduces a logging feature flag to make the tracing stack optional and route all logging calls through a src/logging.rs facade so builds can compile with zero logging code when --no-default-features is used.
Changes:
- Adds
src/logging.rsfacade that re-exportstracingmacros when enabled and provides no-op macros when disabled. - Gates
tracing,tracing-subscriber, andtracing-appenderbehind a newloggingfeature (enabled by default). - Updates library modules and binaries to use the facade and
#[cfg(feature = "logging")]for subscriber initialization / CLI logging flags.
Reviewed changes
Copilot reviewed 32 out of 35 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| Cargo.toml | Makes tracing deps optional and adds logging feature (default-on). |
| src/lib.rs | Exposes logging module from the crate root. |
| src/logging.rs | Implements the logging facade (re-export vs no-op). |
| src/node.rs | Switches node logging imports to crate::logging::*. |
| src/devnet.rs | Switches devnet logging imports to crate::logging::*. |
| src/client/chunk_protocol.rs | Switches client protocol logging imports to crate::logging::*. |
| src/storage/handler.rs | Switches storage handler logging imports to crate::logging::*. |
| src/storage/lmdb.rs | Switches LMDB logging imports and one qualified warn call to crate::logging. |
| src/replication/mod.rs | Switches replication engine logging imports to crate::logging::*. |
| src/replication/audit.rs | Switches audit logging imports to crate::logging::*. |
| src/replication/bootstrap.rs | Switches bootstrap logging imports to crate::logging::*. |
| src/replication/fresh.rs | Switches fresh-write logging imports to crate::logging::*. |
| src/replication/neighbor_sync.rs | Switches neighbor sync logging imports to crate::logging::*. |
| src/replication/paid_list.rs | Switches paid list logging imports to crate::logging::*. |
| src/replication/pruning.rs | Switches pruning logging imports to crate::logging::*. |
| src/replication/quorum.rs | Switches quorum logging imports to crate::logging::*. |
| src/replication/scheduling.rs | Switches scheduling logging imports to crate::logging::*. |
| src/payment/metrics.rs | Switches payment metrics logging imports to crate::logging::*. |
| src/payment/quote.rs | Switches quote logging imports and enabled checks to crate::logging. |
| src/payment/single_node.rs | Switches single-node payment logging import to crate::logging::info. |
| src/payment/verifier.rs | Switches verifier logging imports and enabled checks to crate::logging. |
| src/upgrade/apply.rs | Switches upgrade apply logging imports to crate::logging::*. |
| src/upgrade/binary_cache.rs | Switches binary cache logging imports to crate::logging::*. |
| src/upgrade/mod.rs | Switches upgrade module logging imports to crate::logging::*. |
| src/upgrade/monitor.rs | Switches monitor logging imports to crate::logging::*. |
| src/upgrade/release_cache.rs | Switches release cache logging import to crate::logging::debug. |
| src/upgrade/rollout.rs | Switches rollout logging import to crate::logging::debug. |
| src/upgrade/signature.rs | Switches signature logging import to crate::logging::debug. |
| src/bin/ant-node/cli.rs | Gates CLI logging fields/types behind #[cfg(feature = "logging")]. |
| src/bin/ant-node/main.rs | Gates tracing subscriber init behind feature; routes log macros through facade. |
| src/bin/ant-devnet/main.rs | Gates tracing subscriber init behind feature; routes log macros through facade. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/logging.rs
Outdated
| ($($arg:tt)*) => {}; | ||
| } | ||
|
|
||
| #[cfg(not(feature = "logging"))] | ||
| #[macro_export] | ||
| #[doc(hidden)] | ||
| macro_rules! __log_noop_warn { | ||
| ($($arg:tt)*) => {}; | ||
| } | ||
|
|
||
| #[cfg(not(feature = "logging"))] | ||
| #[macro_export] | ||
| #[doc(hidden)] | ||
| macro_rules! __log_noop_debug { | ||
| ($($arg:tt)*) => {}; | ||
| } | ||
|
|
||
| #[cfg(not(feature = "logging"))] | ||
| #[macro_export] | ||
| #[doc(hidden)] | ||
| macro_rules! __log_noop_error { | ||
| ($($arg:tt)*) => {}; | ||
| } | ||
|
|
||
| #[cfg(not(feature = "logging"))] | ||
| #[macro_export] | ||
| #[doc(hidden)] | ||
| macro_rules! __log_noop_trace { | ||
| ($($arg:tt)*) => {}; |
There was a problem hiding this comment.
The no-op logging macros expand to an empty token stream (=> {}), which can break valid tracing-style usage in expression position (e.g. let _ = info!(...) would become let _ = ;). To keep the facade API-compatible, have these no-op macros expand to a unit expression so they are usable both as statements and expressions, while still not evaluating arguments.
| ($($arg:tt)*) => {}; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_warn { | |
| ($($arg:tt)*) => {}; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_debug { | |
| ($($arg:tt)*) => {}; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_error { | |
| ($($arg:tt)*) => {}; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_trace { | |
| ($($arg:tt)*) => {}; | |
| ($($arg:tt)*) => { | |
| () | |
| }; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_warn { | |
| ($($arg:tt)*) => { | |
| () | |
| }; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_debug { | |
| ($($arg:tt)*) => { | |
| () | |
| }; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_error { | |
| ($($arg:tt)*) => { | |
| () | |
| }; | |
| } | |
| #[cfg(not(feature = "logging"))] | |
| #[macro_export] | |
| #[doc(hidden)] | |
| macro_rules! __log_noop_trace { | |
| ($($arg:tt)*) => { | |
| () | |
| }; |
| /// Stub for `tracing::Level` when logging is disabled. | ||
| #[cfg(not(feature = "logging"))] | ||
| #[allow(non_upper_case_globals, dead_code)] | ||
| pub mod Level { | ||
| /// Debug level stub. | ||
| pub const DEBUG: () = (); | ||
| /// Info level stub. | ||
| pub const INFO: () = (); | ||
| /// Warn level stub. |
There was a problem hiding this comment.
When logging is disabled, Level is defined as a pub mod Level, which will trigger the non_snake_case lint for module names and also changes the kind of item compared to the feature-enabled pub use tracing::Level (a type). Consider making the stub a pub struct Level with associated constants instead, so crate::logging::Level::DEBUG keeps the same “type + associated consts” shape across feature flags and avoids the module naming lint.
src/lib.rs
Outdated
| pub mod ant_protocol; | ||
| pub mod client; | ||
| pub mod config; | ||
| #[macro_use] |
There was a problem hiding this comment.
#[macro_use] on the logging module looks redundant here: when logging is enabled, the module only re-exports macros (no local macro_rules! items for macro_use to pull into scope), and when disabled the no-op macros are #[macro_export] (already available at the crate root). Removing #[macro_use] would reduce confusion about macro scoping and avoid relying on legacy macro import behavior.
| #[macro_use] |
- Fix `pub mod Level` → `pub struct Level` to avoid non_snake_case lint
- Remove unnecessary `#[macro_use]` on logging module
- Extract `init_logging()` from main() to fix too_many_lines clippy lint
- No-op macros now expand to `()` instead of `{}` for expression-position safety
- Replace `eprintln!` in `BootstrapPeersConfig::discover()` with
`crate::logging::warn!` to prevent stderr leaks in no-logging builds
- Fix direct indexing `candidate_prices[len/2]` → safe `.get()` in
merkle payment verifier (CDX-002)
- Add `#[allow(clippy::manual_unwrap_or_default)]` where match arms
intentionally log errors
- Override RUSTFLAGS in CI/release no-default-features builds to allow
expected unused-variable warnings from compiled-out log macros
Use checked_shl() instead of raw << on attacker-controlled depth value from on-chain data. A depth >= 64 would panic (or abort in release), making this a remotely-triggerable crash via crafted merkle proof.
|
@claude @greptile-apps please review |
|
Claude finished @grumbach's task —— View job Code Review: feat: gate all logging behind
|
| Priority | Issue | Action |
|---|---|---|
| Low | Unused variable suppression | Document the tradeoff in src/logging.rs |
| Low | Test coverage | Add CI job testing with --no-default-features |
| Low | Module-level allows | Consider moving RUSTFLAGS to module attributes |
Verdict
Approve with minor suggestions.
The implementation is sound and achieves the stated privacy goals. The facade pattern is well-executed, migration is comprehensive, and the feature flag hygiene is excellent. The Copilot review had one valid suggestion (the () expansion) but two incorrect observations (no #[macro_use] exists, Level is already correct).
The minor suggestions above would improve maintainability but aren't blockers. The current implementation ships zero logging code in release binaries while maintaining full dev ergonomics.
Test coverage: ✅ Manual verification shows both builds work
Breaking changes: ✅ None - logging on by default
Privacy goal: ✅ Achieved - release builds have no logging
Migration completeness: ✅ All 36 files properly updated
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 36 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/config.rs
Outdated
| Err(_err) => { | ||
| crate::logging::warn!( | ||
| "Failed to load bootstrap peers from {}", | ||
| path.display(), |
There was a problem hiding this comment.
The error from from_file(&path) is currently discarded (Err(_err)), and the warning message no longer includes the underlying failure reason. When logging is enabled, including the error (e.g., as {err} or a structured field) would make diagnosing invalid/unreadable bootstrap config files much easier without affecting --no-default-features builds (macros compile out).
| Err(_err) => { | |
| crate::logging::warn!( | |
| "Failed to load bootstrap peers from {}", | |
| path.display(), | |
| Err(err) => { | |
| crate::logging::warn!( | |
| "Failed to load bootstrap peers from {}: {}", | |
| path.display(), | |
| err, |
.github/workflows/ci.yml
Outdated
| env: | ||
| # When logging is compiled out, variables used only in log macros | ||
| # become unused. These warnings are expected and harmless. | ||
| RUSTFLAGS: "-A unused_variables -A unused_assignments -A dead_code" |
There was a problem hiding this comment.
This step overrides the workflow-wide RUSTFLAGS: "-D warnings" with -A ... -A dead_code, which can hide genuine warnings/regressions in --no-default-features release builds (not just logging-related ones). Prefer fixing the underlying unused/dead-code sources (e.g., guard log-only computations with crate::logging::enabled!(...), prefix truly log-only locals with _, or use targeted #[allow(...)] at the smallest scope) and avoid broadly disabling dead_code if possible.
| env: | |
| # When logging is compiled out, variables used only in log macros | |
| # become unused. These warnings are expected and harmless. | |
| RUSTFLAGS: "-A unused_variables -A unused_assignments -A dead_code" |
.github/workflows/release.yml
Outdated
| - name: Build (cross, no logging) | ||
| if: matrix.cross | ||
| run: cross build --release --target ${{ matrix.target }} | ||
| run: cross build --release --no-default-features --target ${{ matrix.target }} | ||
| env: | ||
| RUSTFLAGS: "-A unused_variables -A unused_assignments -A dead_code" | ||
|
|
||
| - name: Build (native) | ||
| - name: Build (native, no logging) | ||
| if: ${{ !matrix.cross }} | ||
| run: cargo build --release --target ${{ matrix.target }} | ||
| run: cargo build --release --no-default-features --target ${{ matrix.target }} | ||
| env: | ||
| RUSTFLAGS: "-A unused_variables -A unused_assignments -A dead_code" |
There was a problem hiding this comment.
These build steps override the workflow-wide RUSTFLAGS: "-D warnings" with -A ... -A dead_code, which reduces warning hygiene for release artifacts and can mask non-logging-related issues. Consider addressing unused/dead-code introduced by compiled-out logging at the source (enabled guards / targeted #[allow]) and avoid disabling dead_code globally for the build if possible.
| let median_price = *candidate_prices | ||
| .get(candidate_prices.len() / 2) | ||
| .ok_or_else(|| Error::Payment("empty candidate pool in merkle proof".into()))?; | ||
| let shift = u32::from(payment_info.depth); | ||
| let multiplier = 1u64 | ||
| .checked_shl(shift) | ||
| .ok_or_else(|| Error::Payment("merkle proof depth too large".into()))?; | ||
| let total_amount = median_price * Amount::from(multiplier); |
There was a problem hiding this comment.
This hunk changes merkle-payment expected-amount calculation behavior (safe .get(...) with a new error, and overflow-checked depth shift) in addition to the logging feature-flag work described in the PR summary. Please call this functional change out explicitly in the PR description (or split it into a separate PR) so reviewers/release notes capture the payment-verification behavior change.
Instead of suppressing warnings via RUSTFLAGS in CI, use crate-level cfg_attr to allow unused_variables and unused_assignments only when the logging feature is disabled. This is self-documenting and scoped: #![cfg_attr(not(feature = "logging"), allow(unused_variables, unused_assignments))] Applied to lib.rs (covers the library) and both binary entry points. Removed all RUSTFLAGS overrides from ci.yml and release.yml.
Restore the error message in the bootstrap peers discovery warning so failures are diagnosable when logging is enabled.
Add module-level doc explaining that log-only variables become unused when macros are compiled out, and how the crate-level cfg_attr handles it.
Run unit tests without the logging feature to catch regressions in the release configuration where all log macros are compiled out.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 36 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The single_node payment tests spawn Anvil for EVM verification. Without Foundry installed the tests fail with "No such file or directory".
Summary
Release binaries ship with all logging compiled out for privacy. Logs are metadata, and metadata kills privacy — publicly released binaries must not emit traces, structured logs, or any observable side-channel that could be used to fingerprint, correlate, or surveil node operators.
Logging remains available for local development and testing via the
loggingfeature flag.Changes
tracing,tracing-subscriber,tracing-appenderare now optional dependencies behind aloggingfeature flagdefault = ["logging"]—cargo build/cargo testinclude logging automatically for development ergonomicsinfo!,warn!,debug!,error!,trace!) compile to no-ops — zero binary overheadsrc/logging.rsfacade module with no-op macro stubsuse tracing::touse crate::logging::--log-level,--log-format,--log-dir,--log-max-files) gated behind#[cfg(feature = "logging")]cargo build --release --no-default-features(no logging)#![cfg_attr(not(feature = "logging"), allow(unused_variables, unused_assignments))]to handle variables only used in log macroseprintln!inBootstrapPeersConfig::discover()withcrate::logging::warn!to prevent stderr leaks in no-logging buildsPayment verifier hardening (bonus fixes from review)
While reviewing, two pre-existing bugs in
src/payment/verifier.rswere identified and fixed:candidate_prices[len/2]replaced with safe.get().ok_or()?to prevent panic on empty candidate pools1u64 << depthreplaced withchecked_shl()to prevent remotely-triggerable panic/abort via crafted merkle proof with depth >= 64How it works
cargo buildcargo testcargo build --release --no-default-featurescargo test --features test-utilsTest plan
cargo check— passes (with logging)cargo check --no-default-features— passes (without logging)cargo clippy --all-targets --all-features -- -D warnings— 0 errorsRUSTFLAGS="-D warnings" cargo check --no-default-features— 0 errorscargo test— passes both wayscargo build --release --no-default-features— full release build succeeds