1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! # Compile-time configurable deferred logging (for `printf()`-debugging *aka* tracing)
//!
//! This is an implementation of the `log::Log` trait, suitable for use
//! in both embedded and desktop environments.
//!
//! It has two main goals:
//! - logs are stored in a circular static memory buffer, so that logging is "zero-cost in the inner
//! loop" (apart from the formatting), with deferred actual I/O later via flushing.
//! - compile-time log level settings for applications with multiple library components;
//! inactive log levels of libraries are completely compiled out.
//!
//! Moreover, setting the kill switch feature flag `knock-it-off`, any and all traces of logging
//! are removed from the final binary.
//!
//! ## Usage and defaults
//!
//! > *"Global settings subtractive (default all), local settings additive (default none) but with kill-switch."*
//!
//! From `log`, we inherit:
//! - static global filters, default `LevelFilter::Trace` (i.e., everything), set via `delog` or
//! `log` feature flags (multiple settings result in the most restrictive filter)
//! - dynamic global filter, initialized in the "init"/"init_default" constructors of the
//! macro-generated structs implementing our `Delogger` trait. This can be changed by calls to
//! the global `set_max_level` function in `log`.
//!
//! Libraries that use the logging macros from `log` are governed by the more restrictive of these two settings.
//!
//! On the other hand, a library that uses the `delog::generate_macros!()` macro gains macros `info!`, `warn!`, etc.,
//! which, by default, do nothing, and are completely optimized out.
//!
//! If such a libray itself defines a feature `log-all` that is active, then logging calls with these macros are passed through
//! and goverend by the global filters. Such a library can also define a feature such as
//! `log-info`, which activates *exactly* the info-level logs; it is up to the library to define
//! logic such as "log-info implies log-warn". There is a kill-switch: if the library defines a
//! feature `log-none`, and some intermediate library activates one of the additive `log-*`
//! features, setting `log-none` completely turns off logging for this library.
//!
//! ## Background
//!
//! Compared to existing approaches such as `ufmt`, `cortex-m-funnel` and `defmt`,
//! we pursue different values and requirements, namely:
//!
//! - **compatibility with the standard `core::fmt` traits and the standard `log` library API**.
//!   This means that, while libraries may "upgrade" their logging capabilities by using `delog`
//!   as drop-in replacement for their logging calls (see below), any existing library that already
//!   uses `log` is compatible. This, for our use cases, is a huge win, as opposed to using up "weirdness
//!   budget" and requiring custom tooling for something that is often trivial, throw-away, simple logging.
//! - it follows that one can easily drop a `trace!("{:?}", &suspicious_object)` call at any time for
//!   any object that has a (possibly automatically derived) `Debug` trait implementation – without
//!   passing around structures and keeping on top of lifetimes.
//! - deferred logging: This is a typical "shared memory" logger, calls to `info!` etc.
//!   are not directly sent to their final output, but instead are stored in a circular buffer
//!   that is "drained" by calling `flush` on the logger at a convenient moment, for instance
//!   in an idle loop.
//! - immediate mode logging: Sometimes one wants to bypass the deferred flushing of logs,
//!   this is possible using either the little-known `target` argument to `info!` and friends
//!   with "!" as parameter, or using the additional `immediate_info!` and friends macros.
//! - ability to set log levels *per library, at compile-time*. This can be easily retro-fitted
//!   on existing `log`-based libraries, by adding the requisite features in `Cargo.toml` and
//!   replacing `log` with `delog` (see `gate-tests` for examples of this).
//! - the outcome is that one can leave useful logging calls in the library code, only to activate
//!   them in targeted ways at build time, exactly as needed.
//! - helper macros to easily output binary byte arrays and slices in hexadecimal representations,
//!   which wrap the data in newtypes with custom `fmt::UpperHex` etc. implementations.
//!
//! **Non-goals**:
//!
//! - ultimate speed or code size: Our intention are "normal" logs, not the use of logging/tracing to
//!   for stream binary data to the host. While admittedly the `core::fmt`-ing facilities are not as
//!   efficient as one might hope, in our use cases we have sufficient flash and RAM to use these (and
//!   some hope that, someday, eventually, maybe, the formatting machinery will be revisited and
//!   improved at the root level, namely the language itself.)
//!
//! That said, we believe there is opportunity to extend `delog` in the `defmt` direction by
//! using, e.g., the `fmt::Binary` trait, newtypes and sentinel values to embed raw binary
//! representations of data in time-critical situations without formatting, deferring
//! the extraction and actual formatting to some host-side mechanism.
//!
//! ## Features
//! The `flushers` and `semihosting` features mostly exist to share code within the examples,
//! including the `example` feature. Without them, dependencies are quite minimal, and compilation fast.
//!
//! The `fallible` and `immediate` features (default on) activate the `try_*!` and `*_now!` macros, respectively.
//!
//! ## Warning
//! The current circular buffer implementation (v0.1.0) is definitely unsound on desktop.
//! For embedded use, atomics are required (so no Cortex-M0/M1, and no plans to support non-atomic
//! platforms, which are likely to also be too resource-constrained to support the bloat inherent
//! in `core::fmt`). While we think the implemented circular buffer algorithm works for the "nested interrupt"
//! setup of NVICs, it has not been tested much.
//! The hope is that the worst case scenario is some slightly messed up log outputs.
//!
//! ## Outlook
//! We plan to iterate towards a v0.2.0 soon, making use of a separate "flusher" for the
//! "immediate" logging path. For instance, when logging via serial-over-USB, one might want immediate
//! logs to pend a separate RTIC interrupt handler that blocks until the logs are pushed and read
//! (allowing one to debug the boot process of a firmware), or one might want to just write to RTT
//! (or even semihosting xD) for these, during development.
//!

#![deny(missing_docs)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]

use core::fmt;

pub use log;
pub use log::{Level, LevelFilter, Record};

#[cfg(feature = "example")]
pub mod example;

pub mod hex;

mod macros;
mod logger;
pub mod render;

pub use logger::{Delogger, Statistics, State, TryLog, TryLogWithStatistics, dequeue, enqueue, try_enqueue};

/// A way to pass on logs, user supplied.
///
/// In embedded, this is intended to pend an interrupt
/// to send the logs off via (USB) serial, semihosting, or similar.
///
/// On PC, typical implemenation will just println! or eprintln!
pub trait Flusher: core::fmt::Debug + Send {
    /// Implementor must handle passed log `&str` in some hopefully useful way.
    fn flush(&self, logs: &str);
}

/// A way to format logs, user supplied.
pub trait Renderer: Send + Sync {
    /// Implementor must render record into `buf`, returning the slice containing the rendered
    /// record.
    fn render<'a>(&self, buf: &'a mut [u8], record: &log::Record) -> &'a [u8];
}

static mut LOGGER: Option<&'static dyn logger::TryLogWithStatistics> = None;

/// Returns a reference to the logger (as `TryLogWithStatistics` implementation)
pub fn logger() -> &'static mut Option<&'static dyn logger::TryLogWithStatistics> {
    unsafe { &mut LOGGER }
}

// WARNING: this is not part of the crate's public API and is subject to change at any time.
// Taken from `log` crate, mutatis mutandis.
// The methods are here and not in `macro` to avoid making the latter public.
#[doc(hidden)]
pub fn __private_api_try_log(
    args: fmt::Arguments,
    level: log::Level,
    &(target, module_path, file, line): &(&str, &'static str, &'static str, u32),
) -> core::result::Result<(), ()> {
    crate::logger().ok_or(())?.try_log(
        &log::Record::builder()
            .args(args)
            .level(level)
            .target(target)
            .module_path_static(Some(module_path))
            .file_static(Some(file))
            .line(Some(line))
            .build(),
    )
}

// WARNING: this is not part of the crate's public API and is subject to change at any time.
#[doc(hidden)]
pub fn __private_api_try_log_lit(
    message: &str,
    level: log::Level,
    &(target, module_path, file, line): &(&str, &'static str, &'static str, u32),
) -> core::result::Result<(), ()> {
    crate::logger().ok_or(())?.try_log(
        &log::Record::builder()
            .args(format_args!("{}", message))
            .level(level)
            .target(target)
            .module_path_static(Some(module_path))
            .file_static(Some(file))
            .line(Some(line))
            .build(),
    )
}