nexosim/tracing.rs
1//! Support for structured logging.
2//!
3//! # Overview
4//!
5//! When the `tracing` feature is activated, each tracing event or span emitted
6//! by a model is wrapped in a [`tracing::Span`] with the following metadata:
7//!
8//! - name: `model`,
9//! - target: `nexosim`,
10//! - verbosity level: [`Level::INFO`](tracing::Level::INFO),
11//! - a unique field called `name`, associated to the model name provided in
12//! [`SimInit::add_model`](crate::simulation::SimInit::add_model).
13//!
14//! The emission of `model` spans can be readily used for [event
15//! filtering](#event-filtering-examples), using for instance the
16//! [`tracing_subscriber::fmt`][mod@tracing_subscriber::fmt] subscriber. By
17//! default, however, this subscriber will timestamp events with the wall clock
18//! time. Because it is often desirable to log events using the simulation time
19//! instead of (or on top of) the wall clock time, this module provides a custom
20//! [`SimulationTime`] timer compatible with
21//! [`tracing_subscriber::fmt`][mod@tracing_subscriber::fmt].
22//!
23//!
24//! # Configuration
25//!
26//! Using the `tracing-subscriber` crate, simulation events can be logged to
27//! standard output by placing the following call anywhere before
28//! [`SimInit::init`](crate::simulation::SimInit::init):
29//!
30//! ```
31//! tracing_subscriber::fmt::init();
32//! ```
33//!
34//! Logging from a model is then a simple matter of using the `tracing` macros,
35//! for instance:
36//!
37//! ```
38//! use tracing::warn;
39//!
40//! pub struct MyModel { /* ... */ }
41//!
42//! impl MyModel {
43//! pub fn some_input_port(&mut self, _some_parameter: i32) {
44//! // ...
45//! warn!("something happened inside the simulation");
46//! // ...
47//! }
48//! }
49//! ```
50//!
51//! However, this will stamp events with the system time rather than the
52//! simulation time. To use simulation time instead, a dedicated timer can be
53//! configured:
54//!
55//! ```
56//! use nexosim::tracing::SimulationTime;
57//!
58//! tracing_subscriber::fmt()
59//! .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
60//! .with_timer(SimulationTime::with_system_timer())
61//! .init();
62//! ```
63//!
64//! This timer will automatically revert to system time stamping for tracing
65//! events generated outside of simulation models, e.g.:
66//!
67//! ```text
68//! [2001-02-03 04:05:06.789012345] WARN model{name="my_model"}: my_simulation: something happened inside the simulation
69//! 2024-09-10T14:39:24.670921Z INFO my_simulation: something happened outside the simulation
70//! ```
71//!
72//! Alternatively, `SimulationTime::with_system_timer_always` can be used to
73//! always prepend the system time even for simulation events:
74//!
75//! ```text
76//! 2024-09-10T14:39:22.124945Z [2001-02-03 04:05:06.789012345] WARN model{name="my_model"}: my_simulation: something happened inside the simulation
77//! 2024-09-10T14:39:24.670921Z INFO my_simulation: something happened outside the simulation
78//! ```
79//!
80//!
81//! # Event filtering examples
82//!
83//! Note that event filtering based on the `RUST_LOG` environment variable
84//! requires the `env-filter` feature of the
85//! [`tracing-subscriber`][tracing_subscriber] crate.
86//!
87//! The following `RUST_LOG` directive could be used to only let warnings and
88//! errors pass through but still see model span information (which is emitted
89//! as info):
90//!
91//! ```text
92//! $ RUST_LOG="warn,[model]=info" cargo run --release my_simulation
93//! [2001-01-01 00:00:06.000000000] WARN model{name="kettle"}: my_simulation: water is boiling
94//! [2001-01-01 00:01:36.000000000] WARN model{name="timer"}: my_simulation: ring ring
95//! [2001-01-01 00:01:36.000000000] WARN model{name="kettle"}: my_simulation: water is ready
96//! ```
97//!
98//! In order to see warnings or errors for the `kettle` model only, this
99//! directive could be modified as follows:
100//!
101//! ```text
102//! $ RUST_LOG="[model{name=kettle}]=warn" cargo run --release my_simulation
103//! [2001-01-01 00:00:06.000000000] WARN model{name="kettle"}: my_simulation: water is boiling
104//! [2001-01-01 00:01:36.000000000] WARN model{name="kettle"}: my_simulation: water is ready
105//! ```
106//!
107//! If the `model` span name collides with that of spans defined outside
108//! `nexosim`, the above filters can be made more specific using
109//! `nexosim[model]` instead of just `[model]`.
110//!
111//!
112//! # Customization
113//!
114//! The [`tracing-subscriber`][tracing_subscriber] crate allows for
115//! customization such as logging to files or formatting logs with JSON.
116//!
117//! Further customization is possible by implementing a
118//! [`tracing_subscriber::layer::Layer`] or a dedicated [`tracing::Subscriber`].
119
120use std::fmt;
121
122use tracing_subscriber::fmt::format::Writer;
123use tracing_subscriber::fmt::time::{FormatTime, SystemTime};
124
125use crate::executor::SIMULATION_CONTEXT;
126
127/// A timer that can be used in conjunction with the
128/// [`tracing-subscriber`][tracing_subscriber] crate to log events using the
129/// simulation time instead of (or on top of) the wall clock time.
130///
131/// See the [module-level documentation][crate::tracing] for more details.
132#[derive(Default, Debug)]
133pub struct SimulationTime<const VERBOSE: bool, T> {
134 sys_timer: T,
135}
136
137impl SimulationTime<false, SystemTime> {
138 /// Constructs a new simulation timer which falls back to the [`SystemTime`]
139 /// timer for events generated outside the simulator.
140 pub fn with_system_timer() -> Self {
141 Self::default()
142 }
143}
144
145impl SimulationTime<true, SystemTime> {
146 /// Constructs a new simulation timer which prepends a [`SystemTime`]
147 /// timestamp to all tracing events, as well as a simulation timestamp for
148 /// simulation events.
149 pub fn with_system_timer_always() -> Self {
150 Self::default()
151 }
152}
153
154impl<T: FormatTime> SimulationTime<false, T> {
155 /// Constructs a new simulation timer which falls back to the provided
156 /// timer for tracing events generated outside the simulator.
157 pub fn with_custom_timer(sys_timer: T) -> Self {
158 Self { sys_timer }
159 }
160}
161
162impl<T: FormatTime> SimulationTime<true, T> {
163 /// Constructs a new simulation timer which prepends a timestamp generated
164 /// with the provided timer to all tracing events, as well as a simulation
165 /// timestamp for simulation events.
166 pub fn with_custom_timer_always(sys_timer: T) -> Self {
167 Self { sys_timer }
168 }
169}
170
171impl<const VERBOSE: bool, T: FormatTime> FormatTime for SimulationTime<VERBOSE, T> {
172 fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
173 SIMULATION_CONTEXT
174 .map(|ctx| {
175 if VERBOSE {
176 self.sys_timer.format_time(w)?;
177 w.write_char(' ')?;
178 }
179 write!(w, "[{:.9}]", ctx.time_reader.try_read().unwrap())
180 })
181 .unwrap_or_else(|| self.sys_timer.format_time(w))
182 }
183}