Skip to main content

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}