cortex/observability.rs
1// Copyright 2015-2025 Deyan Ginev. See the LICENSE
2// file at the top-level directory of this distribution.
3//
4// Licensed under the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>.
5// This file may not be copied, modified, or distributed
6// except according to those terms.
7
8//! Observability bootstrap (Arm 8): one `tracing` subscriber for the binaries.
9//!
10//! The dispatcher's hot path emits leveled `tracing` events instead of raw `eprintln!`/`println!`,
11//! so per-dispatched-task narration is `trace`/`debug` and is **filtered out at the default `info`
12//! level** — a high task rate no longer pays a synchronous, locked-`stderr` write per event
13//! (KNOWN_ISSUES D-11). Verbosity is runtime-controlled via `RUST_LOG` (e.g. `RUST_LOG=debug`,
14//! `RUST_LOG=cortex=trace`), so the detail is available on demand without a rebuild.
15
16/// Initializes the process-wide `tracing` subscriber: a plain **stderr** formatter filtered by
17/// `RUST_LOG` (default `info`). Idempotent and panic-free — uses `try_init`, so a second call (or a
18/// test that already installed a subscriber) is a no-op rather than a panic. Call once at the top
19/// of each binary's `main` (the dispatcher and frontend; the CLI uses [`init_cli_tracing`]).
20pub fn init_tracing() {
21 use tracing_subscriber::{EnvFilter, fmt};
22 // Default (when `RUST_LOG` is unset): app events at `info`, but Rocket's per-request internals at
23 // `warn` only. The frontend shares this subscriber (Rocket 0.5 logs via `tracing`), and its
24 // live-ops dashboard polls `/admin/status.json` every few seconds — at `info` Rocket would log
25 // ~4 lines per poll forever, drowning the app's own events. `rocket=warn` keeps the launch banner
26 // (emitted at `warn`) + any Rocket warnings/errors while silencing the per-request flood; the
27 // app's `info`/`warn` events (e.g. the P-2 slow-report warning) still show. The dispatcher has no
28 // Rocket, so the directive is inert there. Override anytime with `RUST_LOG` (e.g.
29 // `RUST_LOG=rocket=info` to restore per-request traces, `RUST_LOG=cortex=debug` for app detail).
30 let filter =
31 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,rocket=warn"));
32 // Write to **stderr**, not stdout: a log stream belongs on stderr (stdout is reserved for program
33 // output), and `fmt()`'s default writer is `io::stdout` — so this `.with_writer` is load-bearing,
34 // not cosmetic. `try_init` returns `Err` if a global subscriber is already set — ignore it.
35 let _ = fmt()
36 .with_env_filter(filter)
37 .with_target(false)
38 .with_writer(std::io::stderr)
39 .try_init();
40}
41
42/// Initializes the `tracing` subscriber for the **`cortex` CLI**. Like [`init_tracing`] (stderr,
43/// `RUST_LOG`-filtered, idempotent), but when `RUST_LOG` is unset the level is driven by the CLI's
44/// `-v`/`-q` flags rather than defaulting to `info`:
45///
46/// | flags | level |
47/// |--------------|---------|
48/// | `-q` | `error` |
49/// | *(none)* | `warn` |
50/// | `-v` | `info` |
51/// | `-vv` | `debug` |
52/// | `-vvv`+ | `trace` |
53///
54/// The default is `warn` (not `info`) because the CLI's *normal* output is each subcommand's own
55/// stdout / `--json`; `tracing` events are opt-in diagnostics. Events always go to **stderr** so
56/// they never corrupt a `--json` subcommand's machine output on stdout. An explicit `RUST_LOG`
57/// always wins over the flags (so `RUST_LOG=cortex=trace cortex status` works regardless of `-v`).
58///
59/// `json` selects newline-delimited JSON events (`--log-format json`) for machine ingestion (the
60/// agent-facing log shape); the default is the human text formatter.
61pub fn init_cli_tracing(verbose: u8, quiet: bool, json: bool) {
62 use tracing_subscriber::{EnvFilter, fmt};
63 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
64 let level = if quiet {
65 "error"
66 } else {
67 match verbose {
68 0 => "warn",
69 1 => "info",
70 2 => "debug",
71 _ => "trace",
72 }
73 };
74 // Keep Rocket quiet for parity with `init_tracing`; the directive is inert in the CLI (no
75 // Rocket), but harmless and consistent if a shared filter is ever logged.
76 EnvFilter::new(format!("{level},rocket=warn"))
77 });
78 // Both formats write to stderr (stdout stays clean for `--json` subcommand output).
79 if json {
80 let _ = fmt()
81 .with_env_filter(filter)
82 .with_writer(std::io::stderr)
83 .json()
84 .try_init();
85 } else {
86 let _ = fmt()
87 .with_env_filter(filter)
88 .with_target(false)
89 .with_writer(std::io::stderr)
90 .try_init();
91 }
92}