forked from ROMEO/nexosim
184 lines
6.7 KiB
Rust
184 lines
6.7 KiB
Rust
//! Support for structured logging.
|
|
//!
|
|
//! # Overview
|
|
//!
|
|
//! When the `tracing` feature is activated, each tracing event or span emitted
|
|
//! by a model is wrapped in a [`tracing::Span`] with the following metadata:
|
|
//!
|
|
//! - name: `model`,
|
|
//! - target: `nexosim`,
|
|
//! - verbosity level: [`Level::INFO`](tracing::Level::INFO),
|
|
//! - a unique field called `name`, associated to the model name provided in
|
|
//! [`SimInit::add_model`](crate::simulation::SimInit::add_model).
|
|
//!
|
|
//! The emission of `model` spans can be readily used for [event
|
|
//! filtering](#event-filtering-examples), using for instance the
|
|
//! [`tracing_subscriber::fmt`][mod@tracing_subscriber::fmt] subscriber. By
|
|
//! default, however, this subscriber will timestamp events with the wall clock
|
|
//! time. Because it is often desirable to log events using the simulation time
|
|
//! instead of (or on top of) the wall clock time, this module provides a custom
|
|
//! [`SimulationTime`] timer compatible with
|
|
//! [`tracing_subscriber::fmt`][mod@tracing_subscriber::fmt].
|
|
//!
|
|
//!
|
|
//! # Configuration
|
|
//!
|
|
//! Using the `tracing-subscriber` crate, simulation events can be logged to
|
|
//! standard output by placing the following call anywhere before
|
|
//! [`SimInit::init`](crate::simulation::SimInit::init):
|
|
//!
|
|
//! ```
|
|
//! tracing_subscriber::fmt::init();
|
|
//! ```
|
|
//!
|
|
//! Logging from a model is then a simple matter of using the `tracing` macros,
|
|
//! for instance:
|
|
//!
|
|
//! ```
|
|
//! use tracing::warn;
|
|
//!
|
|
//! pub struct MyModel { /* ... */ }
|
|
//!
|
|
//! impl MyModel {
|
|
//! pub fn some_input_port(&mut self, _some_parameter: i32) {
|
|
//! // ...
|
|
//! warn!("something happened inside the simulation");
|
|
//! // ...
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! However, this will stamp events with the system time rather than the
|
|
//! simulation time. To use simulation time instead, a dedicated timer can be
|
|
//! configured:
|
|
//!
|
|
//! ```
|
|
//! use nexosim::tracing::SimulationTime;
|
|
//!
|
|
//! tracing_subscriber::fmt()
|
|
//! .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
|
//! .with_timer(SimulationTime::with_system_timer())
|
|
//! .init();
|
|
//! ```
|
|
//!
|
|
//! This timer will automatically revert to system time stamping for tracing
|
|
//! events generated outside of simulation models, e.g.:
|
|
//!
|
|
//! ```text
|
|
//! [2001-02-03 04:05:06.789012345] WARN model{name="my_model"}: my_simulation: something happened inside the simulation
|
|
//! 2024-09-10T14:39:24.670921Z INFO my_simulation: something happened outside the simulation
|
|
//! ```
|
|
//!
|
|
//! Alternatively, `SimulationTime::with_system_timer_always()` can be used to
|
|
//! always prepend the system time even for simulation events:
|
|
//!
|
|
//! ```text
|
|
//! 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
|
|
//! 2024-09-10T14:39:24.670921Z INFO my_simulation: something happened outside the simulation
|
|
//! ```
|
|
//!
|
|
//!
|
|
//! # Event filtering examples
|
|
//!
|
|
//! Note that event filtering based on the `RUST_LOG` environment variable
|
|
//! requires the `env-filter` feature of the
|
|
//! [`tracing-subscriber`][tracing_subscriber] crate.
|
|
//!
|
|
//! The following `RUST_LOG` directive could be used to only let warnings and
|
|
//! errors pass through but still see model span information (which is emitted
|
|
//! as info):
|
|
//!
|
|
//! ```text
|
|
//! $ RUST_LOG="warn,[model]=info" cargo run --release my_simulation
|
|
//! [2001-01-01 00:00:06.000000000] WARN model{name="kettle"}: my_simulation: water is boiling
|
|
//! [2001-01-01 00:01:36.000000000] WARN model{name="timer"}: my_simulation: ring ring
|
|
//! [2001-01-01 00:01:36.000000000] WARN model{name="kettle"}: my_simulation: water is ready
|
|
//! ```
|
|
//!
|
|
//! In order to see warnings or errors for the `kettle` model only, this
|
|
//! directive could be modified as follows:
|
|
//!
|
|
//! ```text
|
|
//! $ RUST_LOG="[model{name=kettle}]=warn" cargo run --release my_simulation
|
|
//! [2001-01-01 00:00:06.000000000] WARN model{name="kettle"}: my_simulation: water is boiling
|
|
//! [2001-01-01 00:01:36.000000000] WARN model{name="kettle"}: my_simulation: water is ready
|
|
//! ```
|
|
//!
|
|
//! If the `model` span name collides with that of spans defined outside
|
|
//! `nexosim`, the above filters can be made more specific using
|
|
//! `nexosim[model]` instead of just `[model]`.
|
|
//!
|
|
//!
|
|
//! # Customization
|
|
//!
|
|
//! The [`tracing-subscriber`][tracing_subscriber] crate allows for
|
|
//! customization such as logging to files or formatting logs with JSON.
|
|
//!
|
|
//! Further customization is possible by implementing a
|
|
//! [`tracing_subscriber::layer::Layer`] or a dedicated [`tracing::Subscriber`].
|
|
|
|
use std::fmt;
|
|
|
|
use tracing_subscriber::fmt::format::Writer;
|
|
use tracing_subscriber::fmt::time::{FormatTime, SystemTime};
|
|
|
|
use crate::executor::SIMULATION_CONTEXT;
|
|
|
|
/// A timer that can be used in conjunction with the
|
|
/// [`tracing-subscriber`][tracing_subscriber] crate to log events using the
|
|
/// simulation time instead of (or on top of) the wall clock time.
|
|
///
|
|
/// See the [module-level documentation][crate::tracing] for more details.
|
|
#[derive(Default, Debug)]
|
|
pub struct SimulationTime<const VERBOSE: bool, T> {
|
|
sys_timer: T,
|
|
}
|
|
|
|
impl SimulationTime<false, SystemTime> {
|
|
/// Constructs a new simulation timer which falls back to the [`SystemTime`]
|
|
/// timer for events generated outside the simulator.
|
|
pub fn with_system_timer() -> Self {
|
|
Self::default()
|
|
}
|
|
}
|
|
|
|
impl SimulationTime<true, SystemTime> {
|
|
/// Constructs a new simulation timer which prepends a [`SystemTime`]
|
|
/// timestamp to all tracing events, as well as a simulation timestamp for
|
|
/// simulation events.
|
|
pub fn with_system_timer_always() -> Self {
|
|
Self::default()
|
|
}
|
|
}
|
|
|
|
impl<T: FormatTime> SimulationTime<false, T> {
|
|
/// Constructs a new simulation timer which falls back to the provided
|
|
/// timer for tracing events generated outside the simulator.
|
|
pub fn with_custom_timer(sys_timer: T) -> Self {
|
|
Self { sys_timer }
|
|
}
|
|
}
|
|
|
|
impl<T: FormatTime> SimulationTime<true, T> {
|
|
/// Constructs a new simulation timer which prepends a timestamp generated
|
|
/// with the provided timer to all tracing events, as well as a simulation
|
|
/// timestamp for simulation events.
|
|
pub fn with_custom_timer_always(sys_timer: T) -> Self {
|
|
Self { sys_timer }
|
|
}
|
|
}
|
|
|
|
impl<const VERBOSE: bool, T: FormatTime> FormatTime for SimulationTime<VERBOSE, T> {
|
|
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
|
|
SIMULATION_CONTEXT
|
|
.map(|ctx| {
|
|
if VERBOSE {
|
|
self.sys_timer.format_time(w)?;
|
|
w.write_char(' ')?;
|
|
}
|
|
write!(w, "[{:.9}]", ctx.time_reader.try_read().unwrap())
|
|
})
|
|
.unwrap_or_else(|| self.sys_timer.format_time(w))
|
|
}
|
|
}
|