imxrt_log/log.rs
1//! Text-based logging frontend.
2//!
3//! The module provides a [`log`](https://crates.io/crates/log) implementation that
4//! transfers data using any supported backend.
5//!
6//! Strings are formatted and serialized to a buffer. Compile and runtime filters prevent formatting
7//! and serialization into the buffer. When it's time to copy the data into the circular buffer, the
8//! implementation takes a short critical section.
9//!
10//! See [`LoggingConfig`] to learn more about the runtime filters.
11//! See the `log` package documentation to learn about static filters.
12
13mod filters;
14mod frontend;
15
16pub use filters::Filter;
17use filters::Filters;
18
19use crate::{Poller, BUFFER};
20
21#[cfg(feature = "lpuart")]
22use imxrt_hal::{dma::channel::Channel, lpuart::Lpuart};
23
24/// Logging configuration
25///
26/// Use this to specify certain configurations of the logging
27/// system. By default, the max log level is the log level set at
28/// compile time. See the [compile time filters](https://docs.rs/log/0.4/log/#compile-time-filters)
29/// section for more information. The frontend also enables logging for all targets.
30/// Set the `filters` collection to specify log targets of interest.
31///
32/// If the default configuration is good for you, use `Default::default()`.
33///
34/// ```
35/// use imxrt_log::log::{Filter, LoggingConfig};
36///
37/// const I2C_LOGGING: Filter = ("i2c", None);
38/// const SPI_LOGGING: Filter = ("spi", Some(log::LevelFilter::Warn));
39/// const MOTOR_LOGGING: Filter = ("motor", Some(log::LevelFilter::Trace));
40///
41/// let config = LoggingConfig {
42/// max_level: log::LevelFilter::Debug,
43/// filters: &[
44/// I2C_LOGGING,
45/// SPI_LOGGING,
46/// MOTOR_LOGGING,
47/// ]
48/// };
49/// ```
50pub struct LoggingConfig {
51 /// The max log level for *all* logging
52 ///
53 /// This is the static max level. You may
54 /// override this to bypass the statically-assigned
55 /// max level
56 pub max_level: ::log::LevelFilter,
57 /// A list of filtered targets to log.
58 ///
59 /// If set to an empty slice (default), the logger performs no
60 /// filtering. Otherwise, the frontend filters the specified targets by
61 /// the accompanying log level. See [`Filter`](type.Filter.html) for
62 /// more information.
63 pub filters: &'static [Filter],
64}
65
66impl LoggingConfig {
67 /// Create a default logging config.
68 ///
69 /// Unlike `default()`, this works in `const` contexts.
70 pub const fn new() -> Self {
71 LoggingConfig {
72 max_level: ::log::STATIC_MAX_LEVEL,
73 filters: &[],
74 }
75 }
76}
77
78impl Default for LoggingConfig {
79 fn default() -> LoggingConfig {
80 Self::new()
81 }
82}
83
84/// Initialize a USB logger with the `log` frontend and custom configurations.
85///
86/// See the crate-level documentation to understand how the USB device backend works.
87#[cfg(feature = "usbd")]
88pub fn usbd_with_config<P: imxrt_usbd::Peripherals>(
89 peripherals: P,
90 interrupts: super::Interrupts,
91 frontend_config: &LoggingConfig,
92 backend_config: &crate::UsbdConfig,
93) -> Result<Poller, crate::AlreadySetError<P>> {
94 let (producer, consumer) = match BUFFER.try_split() {
95 Ok((prod, cons)) => (prod, cons),
96 Err(_) => return Err(crate::AlreadySetError::new(peripherals)),
97 };
98
99 // Safety: both can only be called once. We use try_split
100 // (above) to meet that requirement. If that method is called
101 // more than once, subsequent calls are an error.
102 critical_section::with(|_| unsafe {
103 if frontend::init(producer, frontend_config).is_err() {
104 return Err(crate::AlreadySetError::new(peripherals));
105 }
106 crate::usbd::init(peripherals, interrupts, consumer, backend_config);
107 Ok(Poller::new(crate::usbd::VTABLE))
108 })
109}
110
111/// Initialize a USB logger with the `log` frontend.
112///
113/// This function uses default configurations for the frontend and backend.
114/// See the crate-level documentation to understand how the USB device backend works.
115#[cfg(feature = "usbd")]
116pub fn usbd<P: imxrt_usbd::Peripherals>(
117 peripherals: P,
118 interrupts: super::Interrupts,
119) -> Result<Poller, crate::AlreadySetError<P>> {
120 usbd_with_config(
121 peripherals,
122 interrupts,
123 &LoggingConfig::default(),
124 &crate::UsbdConfigBuilder::new().build(),
125 )
126}
127
128/// Initialize a LPUART & DMA logger with the `log` frontend and custom configurations.
129///
130/// See the crate-level documentation to understand how the LPUART backend works.
131#[cfg(feature = "lpuart")]
132pub fn lpuart_with_config<P, const LPUART: u8>(
133 lpuart: Lpuart<P, LPUART>,
134 dma_channel: Channel,
135 interrupts: crate::Interrupts,
136 frontend_config: &LoggingConfig,
137) -> Result<Poller, crate::AlreadySetError<(Lpuart<P, LPUART>, Channel)>> {
138 let (producer, consumer) = match BUFFER.try_split() {
139 Ok((prod, cons)) => (prod, cons),
140 Err(_) => return Err(crate::AlreadySetError::new((lpuart, dma_channel))),
141 };
142
143 // Safety: all of this can only happen once. We use try_split
144 // to meet that requirement.
145 critical_section::with(|_| unsafe {
146 if frontend::init(producer, frontend_config).is_err() {
147 return Err(crate::AlreadySetError::new((lpuart, dma_channel)));
148 }
149 crate::lpuart::init(lpuart, dma_channel, consumer, interrupts);
150 Ok(Poller::new(crate::lpuart::VTABLE))
151 })
152}
153
154/// Initialize a LPUART & DMA logger with the `log` frontend.
155///
156/// This function uses default configurations for the frontend.
157/// See the crate-level documentation to understand how the LPUART backend works.
158#[cfg(feature = "lpuart")]
159pub fn lpuart<P, const LPUART: u8>(
160 lpuart: Lpuart<P, LPUART>,
161 dma_channel: Channel,
162 interrupts: crate::Interrupts,
163) -> Result<Poller, crate::AlreadySetError<(Lpuart<P, LPUART>, Channel)>> {
164 lpuart_with_config(lpuart, dma_channel, interrupts, &LoggingConfig::default())
165}