stlog/
lib.rs

1//! Ultra lightweight logging framework for resource constrained devices
2//!
3//! ![stlog running on a Cortex-M microcontroller](https://i.imgur.com/rPxmAlZ.jpg)
4//!
5//! **[See stlog in action!](https://streamable.com/nmlx7)**
6//!
7//! # Features
8//!
9//! - `O(1)` execution time. Logging a message of arbitrary size is done in a constant number of
10//! instructions.
11//!
12//! - `O(0)` memory usage. The messages are NOT stored in the target device memory (`.rodata`).
13//!
14//! - Supports different logging levels: error, warning, info, debug and trace, in decreasing level
15//! of severity. By default, the `dev` profile logs debug, and more severe, messages and the
16//! `release` profile logs info, and more severe, messages, but this can changed using the Cargo
17//! features of this crate.
18//!
19//! - Provides a global logging mode
20//!
21//! # Non-features
22//!
23//! - `printf` style or any other kind of formatting
24//!
25//! # MSRV
26//!
27//! This crate is guaranteed to compile on stable Rust 1.31 and up. It might compile on older
28//! versions but that may change in any new patch release.
29//!
30//! # Known limitations
31//!
32//! - The current implementation only supports 256 different log strings. This restriction may be
33//! lifted in the future.
34//!
35//! - The string should not contain the character `@`. Any text that follows this character will be
36//! discarded.
37//!
38//! - The exact same string can't be used in two or more macro invocations. Enabling the `spanned`
39//! Cargo feature removes this limitation.
40//!
41//! ``` ignore
42//! use stlog::{error, info};
43//!
44//! fn foo() {
45//!     info!("Hello!");
46//! }
47//!
48//! fn good() {
49//!     foo();
50//!     foo();
51//! }
52//!
53//! fn bad() {
54//!     info!("Hey!");
55//!     info!("Hey!"); //~ ERROR symbol `Hey!` is already defined
56//! }
57//!
58//! fn also_bad() {
59//!     info!("Bye!");
60//!     error!("Bye!"); //~ ERROR symbol `Bye!` is already defined
61//! }
62//! ```
63//!
64//! # Requirements
65//!
66//! The target application must be linked using the `stlog.x` linker script provided by this crate.
67//! The easiest way to do this is to append the `-C link-arg` to the other rustc flags using a Cargo
68//! configuration file (`.cargo/config`).
69//!
70//! ``` toml
71//! [target.thumbv7m-none-eabi]
72//! rustflags = [
73//!     "-C", "link-arg=-Tstlog.x",
74//!     # ..
75//! ]
76//! ```
77//!
78//! To decode the logs on the host you'll need version v0.2.x of the [`stcat`] tool.
79//!
80//! [`stcat`]: https://crates.io/crates/stcat
81//!
82//! # Examples
83//!
84//! ## Local logger
85//!
86//! - Device side
87//!
88//! ```
89//! use stlog::{info, warn, Log};
90//!
91//! struct Logger {
92//!     // ..
93//! #   _0: (),
94//! }
95//!
96//! impl Log for Logger {
97//!     // ..
98//! #   type Error = ();
99//! #
100//! #   fn log(&mut self, _: u8) -> Result<(), ()> {
101//! #       Ok(())
102//! #   }
103//! }
104//!
105//! fn main() {
106//!     let mut logger = Logger {
107//!         // ..
108//! #       _0: (),
109//!     };
110//!
111//!     info!(logger, "Hello, world!");
112//!     warn!(logger, "The quick brown fox jumps over the lazy dog");
113//! }
114//! ```
115//!
116//! - Host side
117//!
118//! Assuming that the device is `log`ging through the `/dev/ttyUSB0` interface.
119//!
120//! ``` text
121//! $ flash-and-run /path/to/device/binary
122//!
123//! $ cat /dev/ttyUSB0 | stcat -e /path/to/device/binary
124//! Sept 22 13:00:00.000 INFO Hello, world!
125//! Sept 22 13:00:00.001 WARN The quick brown fox jumps over the lazy dog
126//! ```
127//!
128//! ## Global logger
129//!
130//! If the first argument is omitted from the logging macros then logging will be done through the
131//! global logger. The global logger must be selected using the `global_logger` attribute *in the
132//! top crate*.
133//!
134//! ``` ignore
135//! use stlog::{info, GlobalLog};
136//!
137//! struct Logger;
138//!
139//! impl GlobalLog for Logger { .. }
140//!
141//! #[global_logger]
142//! static LOGGER: Logger = Logger;
143//!
144//! fn main() {
145//!     info!("Hello");
146//! }
147//!
148//! #[interrupt]
149//! fn SomeInterrupt() {
150//!     info!("World");
151//! }
152//! ```
153//!
154//! # Cargo features
155//!
156//! ## `spanned`
157//!
158//! Enabling this feature adds variants of the macros, that include span information, under the
159//! `spanned` module. For example, `spanned::info!("Hello")` will log the string `"Hello, loc:
160//! src/main.rs:12"`, where `src/main.rs:12` is the location of the macro invocation.
161//!
162//! This feature depends on unstable `proc_macro` features and requires a nightly compiler.
163//!
164//! ## `[release-]max-level-{off,error,warning,info,debug,trace}`
165//!
166//! These features can be used to enable / disable logging levels at compile time.
167//!
168//! - `max-level-off` will disable all levels
169//! - `max-level-error` enables the error level and disables everything else
170//! - `max-level-warning` enables the error and warning levels and disables everything else
171//! - `max-level-info` enables the error, warning and info levels and disables everything else
172//! - `max-level-debug` enables everything but the trace level
173//! - `max-level-trace` enables all levels
174//!
175//! The `release-` prefixed features affect the release profile, while the other features only
176//! affect the dev profile.
177//!
178//! If none of these features are enabled the release profile enables the error, warning and info
179//! levels, and the dev profile additionally enables the debug level.
180//!
181//! # Troubleshooting
182//!
183//! ## Didn't pass `-Tstlog.x` to the linker
184//!
185//! Symptom: you'll get an error when linking the target application or when calling `stcat`.
186//!
187//! ``` text
188//! $ cargo build
189//! error: linking with `rust-lld` failed: exit code: 1
190//!   |
191//!   = note: "rust-lld" (..)
192//!   = note: rust-lld: error: no memory region specified for section '.stlog.info'
193//!           rust-lld: error: no memory region specified for section '.stlog.error'
194//!
195//! $ stcat -e /path/to/binary logfile
196//! error: symbol `__stlog_error_start__` not found
197//! ```
198//!
199//! Pass `-Tstlog.x` to the linker as explained in the requirements section.
200//!
201//! ## Didn't set a `global_logger`
202//!
203//! Symptom: you'll get an error when linking the program
204//!
205//! ``` text
206//! $ cargo build
207//! error: linking with `rust-lld` failed: exit code: 1
208//!   |
209//!   = note: "rust-lld" (..)
210//!   = note: rust-lld: error: undefined symbol: stlog::GLOBAL_LOGGER
211//! ```
212
213#![deny(rust_2018_compatibility)]
214#![deny(rust_2018_idioms)]
215#![deny(warnings)]
216#![no_std]
217
218pub use stlog_macros::global_logger;
219use void::Void;
220
221#[cfg(feature = "spanned")]
222pub mod spanned;
223
224/// A logger that does nothing
225pub struct NullLogger;
226
227impl GlobalLog for NullLogger {
228    fn log(&self, _: u8) {}
229}
230
231impl Log for NullLogger {
232    type Error = Void;
233
234    fn log(&mut self, _: u8) -> Result<(), Void> {
235        Ok(())
236    }
237}
238
239/// A global version of the [`Log`](trait.Log) trait
240///
241/// This is very similar to [`Log`](trait.Log) except that the implementor must ensure that this
242/// method is synchronized with other invocations of itself that could occur concurrently. Also,
243/// note that there the return type is `()` and not `Result` so errors must be handled by the `log`
244/// method.
245pub trait GlobalLog: Sync {
246    fn log(&self, address: u8);
247}
248
249/// A logger that encodes messages using a symbol table
250///
251/// # Contract
252///
253/// The implementation of the `log` method MUST send its argument as a single byte through some
254/// interface.
255pub trait Log {
256    /// Error type of the log operation
257    type Error;
258
259    /// Sends the `address` of the symbol through some interface
260    fn log(&mut self, address: u8) -> Result<(), Self::Error>;
261}
262
263/// Logs the given string literal at the ERROR log level
264///
265/// `$logger` must be an expression whose type implements the [`Log`](trait.Log.html) trait.
266///
267/// If `$logger` is omitted the global logger will be used.
268#[macro_export]
269macro_rules! error {
270    ($logger:expr, $string:expr) => {{
271        if $crate::max_level() as u8 >= $crate::Level::Error as u8 {
272            #[export_name = $string]
273            #[link_section = ".stlog.error"]
274            static SYMBOL: u8 = 0;
275
276            $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8)
277        } else {
278            Ok(())
279        }
280    }};
281
282    ($string:expr) => {
283        unsafe {
284            if $crate::max_level() as u8 >= $crate::Level::Error as u8 {
285                extern "Rust" {
286                    #[link_name = "stlog::GLOBAL_LOGGER"]
287                    static LOGGER: &'static $crate::GlobalLog;
288                }
289
290                #[export_name = $string]
291                #[link_section = ".stlog.error"]
292                static SYMBOL: u8 = 0;
293
294                $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8)
295            }
296        }
297    };
298}
299
300/// Logs the given string literal at the WARNING log level
301///
302/// For more details see the [`error!`](macro.error.html) macro.
303#[macro_export]
304macro_rules! warn {
305    ($logger:expr, $string:expr) => {{
306        if $crate::max_level() as u8 >= $crate::Level::Warn as u8 {
307            #[export_name = $string]
308            #[link_section = ".stlog.warn"]
309            static SYMBOL: u8 = 0;
310
311            $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8)
312        } else {
313            Ok(())
314        }
315    }};
316
317    ($string:expr) => {
318        unsafe {
319            if $crate::max_level() as u8 >= $crate::Level::Warn as u8 {
320                extern "Rust" {
321                    #[link_name = "stlog::GLOBAL_LOGGER"]
322                    static LOGGER: &'static $crate::GlobalLog;
323                }
324
325                #[export_name = $string]
326                #[link_section = ".stlog.warn"]
327                static SYMBOL: u8 = 0;
328
329                $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8)
330            }
331        }
332    };
333}
334
335/// Logs the given string literal at the INFO log level
336///
337/// For more details see the [`error!`](macro.error.html) macro.
338#[macro_export]
339macro_rules! info {
340    ($logger:expr, $string:expr) => {{
341        if $crate::max_level() as u8 >= $crate::Level::Info as u8 {
342            #[export_name = $string]
343            #[link_section = ".stlog.info"]
344            static SYMBOL: u8 = 0;
345
346            $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8)
347        } else {
348            Ok(())
349        }
350    }};
351
352    ($string:expr) => {
353        unsafe {
354            if $crate::max_level() as u8 >= $crate::Level::Info as u8 {
355                extern "Rust" {
356                    #[link_name = "stlog::GLOBAL_LOGGER"]
357                    static LOGGER: &'static $crate::GlobalLog;
358                }
359
360                #[export_name = $string]
361                #[link_section = ".stlog.info"]
362                static SYMBOL: u8 = 0;
363
364                $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8)
365            }
366        }
367    };
368}
369
370/// Logs the given string literal at the DEBUG log level
371///
372/// For more details see the [`error!`](macro.error.html) macro.
373#[macro_export]
374macro_rules! debug {
375    ($log:expr, $string:expr) => {{
376        if $crate::max_level() as u8 >= $crate::Level::Debug as u8 {
377            #[export_name = $string]
378            #[link_section = ".stlog.debug"]
379            static SYMBOL: u8 = 0;
380
381            $crate::Log::log(&mut $log, &SYMBOL as *const u8 as usize as u8)
382        } else {
383            Ok(())
384        }
385    }};
386
387    ($string:expr) => {
388        unsafe {
389            if $crate::max_level() as u8 >= $crate::Level::Debug as u8 {
390                extern "Rust" {
391                    #[link_name = "stlog::GLOBAL_LOGGER"]
392                    static LOGGER: &'static $crate::GlobalLog;
393                }
394
395                #[export_name = $string]
396                #[link_section = ".stlog.debug"]
397                static SYMBOL: u8 = 0;
398
399                $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8)
400            }
401        }
402    };
403}
404
405/// Logs the given string literal at the TRACE log level
406///
407/// For more details see the [`error!`](macro.error.html) macro.
408#[macro_export]
409macro_rules! trace {
410    ($logger:expr, $string:expr) => {{
411        if $crate::max_level() as u8 >= $crate::Level::Trace as u8 {
412            #[export_name = $string]
413            #[link_section = ".stlog.trace"]
414            static SYMBOL: u8 = 0;
415
416            $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8)
417        } else {
418            Ok(())
419        }
420    }};
421
422    ($string:expr) => {
423        unsafe {
424            if $crate::max_level() as u8 >= $crate::Level::Trace as u8 {
425                extern "Rust" {
426                    #[link_name = "stlog::GLOBAL_LOGGER"]
427                    static LOGGER: &'static $crate::GlobalLog;
428                }
429
430                #[export_name = $string]
431                #[link_section = ".stlog.trace"]
432                static SYMBOL: u8 = 0;
433
434                $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8)
435            }
436        }
437    };
438}
439
440#[doc(hidden)]
441pub enum Level {
442    Off = 0,
443    Error = 1,
444    Warn = 2,
445    Info = 3,
446    Debug = 4,
447    Trace = 5,
448}
449
450#[doc(hidden)]
451#[inline(always)]
452pub fn max_level() -> Level {
453    match () {
454        #[cfg(debug_assertions)]
455        () => {
456            #[cfg(feature = "max-level-off")]
457            return Level::Off;
458
459            #[cfg(feature = "max-level-error")]
460            return Level::Error;
461
462            #[cfg(feature = "max-level-warn")]
463            return Level::Warn;
464
465            #[cfg(feature = "max-level-info")]
466            return Level::Info;
467
468            #[cfg(feature = "max-level-debug")]
469            return Level::Debug;
470
471            #[cfg(feature = "max-level-trace")]
472            return Level::Trace;
473
474            #[allow(unreachable_code)]
475            Level::Debug
476        }
477        #[cfg(not(debug_assertions))]
478        () => {
479            #[cfg(feature = "release-max-level-off")]
480            return Level::Off;
481
482            #[cfg(feature = "release-max-level-error")]
483            return Level::Error;
484
485            #[cfg(feature = "release-max-level-warn")]
486            return Level::Warn;
487
488            #[cfg(feature = "release-max-level-info")]
489            return Level::Info;
490
491            #[cfg(feature = "release-max-level-debug")]
492            return Level::Debug;
493
494            #[cfg(feature = "release-max-level-trace")]
495            return Level::Trace;
496
497            #[allow(unreachable_code)]
498            Level::Info
499        }
500    }
501}