stlog/lib.rs
1//! Ultra lightweight logging framework for resource constrained devices
2//!
3//! 
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}