Skip to main content

base_console/
lib.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Automatic terminal and JSON output for ordinary `tracing` events.
5//!
6//! Applications call [`init`] once. Libraries continue using `tracing` directly.
7//! For existing subscribers and isolated captures, compose [`Console::layer`] instead.
8//!
9//! ```no_run
10//! base_console::init();
11//! tracing::info!(connections = 3_u64, "Server started");
12//! ```
13//!
14//! # Composition
15//!
16//! [`Console`] configures the format, colour, writer, and filter, then builds a composable [`Console::layer`], an isolated [`Console::subscriber`], or a global installation with [`Console::init`] or [`Console::try_init`]. A console's filter applies only to its own output.
17//!
18//! ```
19//! use base_console::{Console, OutputFormat};
20//! use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
21//!
22//! let console = Console::default()
23//!     .with_format(OutputFormat::Text)
24//!     .with_ansi(false)
25//!     .with_filter(EnvFilter::new("info,base_http=debug"));
26//!
27//! let subscriber = tracing_subscriber::registry().with(console.layer());
28//! tracing::subscriber::with_default(subscriber, || {
29//!     tracing::info!("Scoped output");
30//! });
31//! ```
32//!
33//! For asynchronous scopes, use `tracing::Instrument` and `tracing::instrument::WithSubscriber` to attach spans and subscribers to futures before spawning them. A thread-local default guard must not be held across `.await`.
34//!
35//! # Output
36//!
37//! JSON includes `timestamp` (UTC), `elapsed_seconds` (process-local console uptime), `level`, `target`, `name`, `location`, `fields`, and a root-to-leaf `spans` array. Each enabled span has a name and its current fields; spans excluded by the console's filter are omitted. Reserved metadata stays separate from user fields. Typed errors are objects containing `message` and an ordered `causes` array; cause traversal stops after 32 entries and sets `truncated` when more remain.
38//!
39//! Integer fields remain JSON numbers, except that a 128-bit integer which does not fit in 64 bits is recorded as a decimal string, because most JSON consumers cannot represent it exactly and `serde_json` needs `arbitrary_precision` to write it as a number. The `arbitrary-precision` feature enables that `serde_json` feature and records such integers as numbers instead; it changes number handling for every `serde_json` user in the build, so enable it only in an application that already accepts that. Non-finite floating-point fields become JSON `null`; `%value` and `?value` remain text representations. Backtraces and captured span traces are text fields. The initial output schema may evolve before a stable release.
40//!
41//! Writing is synchronous. Each console serializes complete records through its writer and flushes the destination before logging returns; it starts no background threads and requires no runtime. Text output escapes control characters from messages and diagnostic text, while JSON uses standard string escaping.
42mod as_console_error;
43mod console;
44mod console_fields;
45mod console_format;
46mod console_init_error;
47mod failure;
48mod output_format;
49
50pub use console::Console;
51pub use console_init_error::ConsoleInitError;
52pub use failure::Failure;
53pub use output_format::OutputFormat;
54
55#[doc(hidden)]
56pub use tracing as __tracing;
57
58#[doc(hidden)]
59pub use as_console_error::AsConsoleError as __AsConsoleError;
60
61/// Emit a captured [`Failure`] as an error event at the caller's target and source location.
62///
63/// The error's causes remain typed; captured backtraces and original span context are included as text fields. Ordinary `tracing` subscribers can consume this event too. The failure expression is evaluated once. An explicit `target:` may override the caller's module path.
64///
65/// ```
66/// let failure = base_console::Failure::capture(std::io::Error::other("disconnected"));
67/// base_console::failure!(failure, "Request {} failed", 42);
68/// ```
69#[macro_export]
70macro_rules! failure {
71    (target: $target:expr, $failure:expr, $($message:tt)+) => {{
72        use $crate::__AsConsoleError as _;
73        let failure = &$failure;
74        $crate::__tracing::error!(
75            target: $target,
76            error = (*failure.error()).console_error(),
77            backtrace = failure.captured_backtrace().map($crate::__tracing::field::display),
78            span_trace = failure.span_trace(),
79            $($message)+
80        );
81    }};
82    ($failure:expr, $($message:tt)+) => {
83        $crate::failure!(target: module_path!(), $failure, $($message)+)
84    };
85}
86
87/// Install a global console using `RUST_LOG`, defaulting to `info`.
88///
89/// Output goes to stderr: terminal text with colour when attached to a terminal, otherwise newline-delimited JSON. The process's `log` logger is left unchanged. Libraries should leave installation to their application. Use [`try_init`] to handle setup errors.
90///
91/// # Panics
92///
93/// Panics if `RUST_LOG` cannot be read or parsed, or a global subscriber is already installed.
94#[track_caller]
95pub fn init() {
96    try_init().expect("could not initialize the console");
97}
98
99/// Try to install a global console using `RUST_LOG`, defaulting to `info`.
100///
101/// Uses the same output defaults as [`init`]. Returns an error if `RUST_LOG` cannot be read or parsed, or a global subscriber is already installed. The process's `log` logger is left unchanged.
102pub fn try_init() -> Result<(), ConsoleInitError> {
103    Console::default().with_env_filter()?.try_init()?;
104    Ok(())
105}