feat(spur-logging): add structured JSON logging - #737
Open
maybeharshit wants to merge 2 commits into
Open
Conversation
maybeharshit
force-pushed
the
feat/structured-json-logging
branch
from
September 1, 2026 07:04
df710e8 to
316e668
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #737 +/- ##
==========================================
- Coverage 80.15% 80.11% -0.04%
==========================================
Files 184 185 +1
Lines 87772 87989 +217
==========================================
+ Hits 70348 70487 +139
- Misses 17424 17502 +78 🚀 New features to boost your workflow:
|
Every binary now initializes one shared subscriber (the new spur-logging crate) instead of four independent tracing_subscriber::fmt() setups. In json mode each line is one flat JSON object with a fixed schema (timestamp, level, component, target, message, plus native-typed fields such as job_id/node/ error); text mode keeps the existing human-readable output. Format precedence: --log-format > [logging].format > (TTY ? text : json), so a service with no TTY gets JSON and an interactive terminal gets text. Level precedence: RUST_LOG > --log-level > [logging].level > info. Config is loaded before logging init so [logging] can choose the format. LoggingConfig gains #[serde(default)] on all fields and its default format changes from "text" to unset; it is TOML-only (not Raft/WAL or proto) and field types are unchanged, so this is backward compatible. Co-authored-by: Cursor <[email protected]>
Structured JSON is now the default when stderr is not a TTY, so the e2e daemons emit JSON, which broke tests that scrape spurctld/spurd logs for key=value fields (multi-node suspend fan-out, device GPU count). Pin the default e2e cluster config to text; JSON stays covered by test_json_logging. Also reword the [logging] prose so pyspelling does not flag stderr/systemd's (inline-literal stderr, and drop the possessive so bare systemd matches the wordlist). Co-authored-by: Cursor <[email protected]>
maybeharshit
force-pushed
the
feat/structured-json-logging
branch
from
September 3, 2026 05:23
872e5ce to
534a0da
Compare
maybeharshit
marked this pull request as ready for review
September 3, 2026 06:36
maybeharshit
requested review from
biluriuday,
sajmera-pensando,
sgopinath1,
shiv-tyagi and
yansun1996
as code owners
September 3, 2026 06:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a shared
spur-loggingcrate so every binary (spurctld,spurd,spur-k8s-operator,spur) installs one subscriber with a single schema, replacing four independenttracing_subscriber::fmt()setups. Injsonmode each line is one flat JSON object;textmode keeps the existing human-readable output.Every JSON line carries a fixed schema:
timestamp(RFC 3339, nanosecond),level(lowercase),component,target(module path),message, plus any structured fields the event already passed (job_id,node,error, ...), each keeping its native JSON type — a numericjob_idstays a number.--log-format>[logging].format> (stderr is a TTY ?text:json).RUST_LOG>--log-level>[logging].level>info.Approach / design
FormatEventrather than stockfmt().json(), which uppercaseslevel, has no top-levelcomponent, and nests span fields underspan. AVisitimpl records each field with its native JSON type.job_id/nodeare flattened from what call sites already pass — no span-propagation layer and no call-site rewrites.[logging]can choose the format; nothing logs before init.LoggingConfiggains#[serde(default)]on all fields and its defaultformatchanges"text"to unset. It is TOML-config only (not Raft/WAL state, not proto) and field types are unchanged, so this is backward compatible.Behavior change (intended, documented)
A daemon under a service manager (no TTY) now emits JSON by default where it previously emitted text; interactive terminals still get text.
examples/spur.confshipsformat = "json", anddocs/admin-guide/configuration.rstdocuments the formats, the TTY default, and precedence.Testing
spur-logging(schema/required fields, lowercase level, native-typedjob_id, message fallback, quote/newline robustness, level & format precedence).cargo clippy --workspace --exclude spur-ffi --all-targets --locked— no warnings.cargo test— green; the only failures are 10spurdcgroup/PTY tests that need kernel features absent in the build sandbox and pass on a real host.tests/native_host/e2e/test_json_logging.py(valid JSON schema from real daemons; numericjob_idin the controller'sjob submittedline after a real submit; text mode stays readable), plus the existingtest_single_node.py(18 tests) all pass with JSON as the piped default.Follow-ups (out of scope)
Span-based propagation (fields on events that don't pass them), a file sink (
logging.file), and OpenTelemetry are intentionally deferred.Made with Cursor