feat: add structured JSON logging and file output support#16
Conversation
There was a problem hiding this comment.
Pull request overview
Adds structured, query-friendly tracing output for saorsa-node, including JSON formatting and optional daily-rotated file logging, and embeds build-time git commit metadata for traceability.
Changes:
- Introduces
--log-format,--log-dir, and--log-max-filesCLI flags and wires them intotracing_subscriber+tracing-appendersetup. - Embeds a
SAORSA_GIT_COMMITvalue at build time and logs it alongside the crate version. - Converts key
info!logs in node startup and upgrade monitoring to structured tracing fields.
Reviewed changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/upgrade/monitor.rs |
Switches upgrade-monitor logs to structured fields for better querying. |
src/node.rs |
Adds version/commit fields and structured fields for startup/upgrade logs. |
src/bin/saorsa-node/main.rs |
Implements log format + file output configuration via tracing layers and appender guards. |
src/bin/saorsa-node/cli.rs |
Adds CLI flags / env vars for log format and file output configuration. |
build.rs |
Computes and injects SAORSA_GIT_COMMIT into the build. |
Cargo.toml |
Adds tracing-appender dependency to support rolling file logs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryThis PR adds structured JSON logging, daily-rotated file output, and build-time git commit embedding to Key changes:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| build.rs | Adds git commit hash embedding via git rev-parse --short HEAD at build time; correctly falls back to "unknown" when git is unavailable, but only watches .git/HEAD — not the branch ref file — so the hash can become stale on incremental builds after new commits on the same branch. |
| src/bin/saorsa-node/main.rs | Adds a four-arm match to configure a boxed tracing_subscriber layer supporting text/JSON × stdout/file combinations; uses tracing_appender::non_blocking with a WorkerGuard correctly held for the duration of main(); the #[allow(clippy::collection_is_never_read)] suppression is intentional and well-commented. |
| src/bin/saorsa-node/cli.rs | Adds --log-format, --log-dir, and --log-max-files CLI flags with sensible defaults (text, no dir, 7 files) and corresponding env-var overrides; CliLogFormat enum is cleanly derived with ValueEnum and Default. |
| src/node.rs | Converts info! format-string calls to structured tracing fields; introduces a duplicate version/commit log at the start of run() that is already emitted by main.rs before calling this function. |
| src/upgrade/monitor.rs | Straightforward conversion of info! format-string messages to structured fields (new_version, delay_hours, delay_minutes, current_version, version); no functional changes. |
| Cargo.toml | Adds tracing-appender = "0.2" dependency; all other entries unchanged. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[CLI parse] --> B{log_format × log_dir}
B -->|Text, None| C[fmt::layer stdout]
B -->|Json, None| D[fmt::layer JSON stdout]
B -->|Text, Some dir| E[rolling::Builder daily]
B -->|Json, Some dir| F[rolling::Builder daily]
E --> G[non_blocking writer\n+ WorkerGuard held]
F --> H[non_blocking writer\n+ WorkerGuard held]
G --> I[fmt::layer file text]
H --> J[fmt::layer file JSON]
C & D & I & J --> K[tracing_subscriber::registry\n.with layer\n.with EnvFilter\n.init]
K --> L[node.run]
L --> M[Goodbye! log\nGuard drops → flush]
Last reviewed commit: b584f97
| fn main() { | ||
| // Rerun if feature configuration changes | ||
| // Rerun if the git HEAD changes (new commit, branch switch, etc.) | ||
| println!("cargo:rerun-if-changed=.git/HEAD"); |
There was a problem hiding this comment.
Stale commit hash on incremental builds
cargo:rerun-if-changed=.git/HEAD only tracks the HEAD pointer file (which typically contains ref: refs/heads/main — a static string that does not change when new commits are made on the current branch). Cargo will therefore skip re-running the build script after a git commit on the same branch, meaning the embedded SAORSA_GIT_COMMIT value becomes stale until something else triggers a full rebuild.
To keep the embedded hash up-to-date you need to also watch the ref that HEAD points to, as well as packed-refs (which is updated when refs are packed):
// Rerun if the git HEAD pointer changes (branch switch, detached HEAD, etc.)
println!("cargo:rerun-if-changed=.git/HEAD");
// Rerun if the current branch tip moves (new commits)
println!("cargo:rerun-if-changed=.git/refs/heads");
// Rerun if refs are packed (e.g. after `git gc`)
println!("cargo:rerun-if-changed=.git/packed-refs");
println!("cargo:rerun-if-changed=build.rs");Alternatively, consider the vergen crate which handles all of these edge cases automatically.
Prompt To Fix With AI
This is a comment left during a code review.
Path: build.rs
Line: 7
Comment:
**Stale commit hash on incremental builds**
`cargo:rerun-if-changed=.git/HEAD` only tracks the HEAD *pointer* file (which typically contains `ref: refs/heads/main` — a static string that does not change when new commits are made on the current branch). Cargo will therefore skip re-running the build script after a `git commit` on the same branch, meaning the embedded `SAORSA_GIT_COMMIT` value becomes stale until something else triggers a full rebuild.
To keep the embedded hash up-to-date you need to also watch the ref that HEAD points to, as well as `packed-refs` (which is updated when refs are packed):
```rust
// Rerun if the git HEAD pointer changes (branch switch, detached HEAD, etc.)
println!("cargo:rerun-if-changed=.git/HEAD");
// Rerun if the current branch tip moves (new commits)
println!("cargo:rerun-if-changed=.git/refs/heads");
// Rerun if refs are packed (e.g. after `git gc`)
println!("cargo:rerun-if-changed=.git/packed-refs");
println!("cargo:rerun-if-changed=build.rs");
```
Alternatively, consider the [`vergen`](https://crates.io/crates/vergen) crate which handles all of these edge cases automatically.
How can I resolve this? If you propose a fix, please make it concise.
src/node.rs
Outdated
| info!( | ||
| version = env!("CARGO_PKG_VERSION"), | ||
| commit = env!("SAORSA_GIT_COMMIT"), | ||
| "Starting saorsa-node" | ||
| ); |
There was a problem hiding this comment.
Duplicate version/commit log at startup
main.rs already logs version and commit with the message "saorsa-node starting" (line 78–82) immediately before calling node.run(). This info! in run() will produce a second, nearly identical log line at every startup, which is redundant and may confuse operators querying logs in Elasticsearch.
Consider either removing the duplicate here and keeping only the one in main.rs, or removing the version/commit fields from this call and using a distinct message (e.g., "Node runtime loop started") that adds context rather than repeating what was already logged.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/node.rs
Line: 428-432
Comment:
**Duplicate version/commit log at startup**
`main.rs` already logs `version` and `commit` with the message `"saorsa-node starting"` (line 78–82) immediately before calling `node.run()`. This `info!` in `run()` will produce a second, nearly identical log line at every startup, which is redundant and may confuse operators querying logs in Elasticsearch.
Consider either removing the duplicate here and keeping only the one in `main.rs`, or removing the `version`/`commit` fields from this call and using a distinct message (e.g., `"Node runtime loop started"`) that adds context rather than repeating what was already logged.
How can I resolve this? If you propose a fix, please make it concise.Add CLI flags (--log-format, --log-dir, --log-max-files) for JSON log output and daily-rotated file logging via tracing-appender. Embed git commit hash at build time. Convert key info! statements in node.rs and upgrade/monitor.rs to use structured tracing fields for Elasticsearch queryability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b584f97 to
f0387cb
Compare
Add unit and e2e tests covering the remaining Section 18 scenarios: Unit tests (32 new): - Quorum: #4 fail→abandoned, #16 timeout→inconclusive, #27 single-round dual-evidence, #28 dynamic threshold undersized, #33 batched per-key, #34 partial response unresolved, #42 quorum-derived paid-list auth - Admission: #5 unauthorized peer, #7 out-of-range rejected - Config: #18 invalid config rejected, #26 dynamic paid threshold - Scheduling: #8 dedup safety, #8 replica/paid collapse - Neighbor sync: #35 round-robin cooldown skip, #36 cycle completion, #38 snapshot stability mid-join, #39 unreachable removal + slot fill, #40 cooldown peer removed, #41 cycle termination guarantee, consecutive rounds, cycle preserves sync times - Pruning: #50 hysteresis prevents premature delete, #51 timestamp reset on heal, #52 paid/record timestamps independent, #23 entry removal - Audit: #19/#53 partial failure mixed responsibility, #54 all pass, #55 empty failure discard, #56 repair opportunity filter, response count validation, digest uses full record bytes - Types: #13 bootstrap drain, repair opportunity edge cases, terminal state variants - Bootstrap claims: #46 first-seen recorded, #49 cleared on normal E2e tests (4 new): - #2 fresh offer with empty PoP rejected - #5/#37 neighbor sync request returns response - #11 audit challenge multi-key (present + absent) - Fetch not-found for non-existent key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
--log-format(text/json),--log-dir, and--log-max-filesCLI flags for structured JSON log output and daily-rotated file logging viatracing-appenderSAORSA_GIT_COMMITenv var inbuild.rs) for log traceabilityinfo!statements innode.rsandupgrade/monitor.rsto use structured tracing fields (e.g.version,commit,current_version,new_version,listen_addrs) for Elasticsearch queryability without regex extractionTest plan
cargo build --releasesucceedscargo test— all 151 unit + 43 e2e tests passcargo clippy --all-features -- -D clippy::panic -D clippy::unwrap_used -D clippy::expect_usedpasses clean--log-format jsonoutputs valid JSON to stdout--log-dir /tmp/saorsa-logswrites daily-rotated files--log-max-files 3retains only 3 rotated files🤖 Generated with Claude Code