Crate delog

source ·
Expand description

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.


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.


  • 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.


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.


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.


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.



  • An example deferred logger with example flusher.
  • Convenient Display and other traits for binary data.
  • The default, minimal renderer, and some helper functions.


  • Generate a deferred logger with specified capacity and flushing mechanism.
  • Generate logging macros that can be gated by library.
  • Compactly format byte arrays and slices as hexadecimals.
  • More compactly format byte arrays and slices as hexadecimals.



  • An enum representing the available verbosity levels of the logger.
  • An enum representing the available verbosity level filters of the logger.


  • Semi-abstract characterization of the deferred loggers that the delog! macro produces.
  • A way to pass on logs, user supplied.
  • A way to format logs, user supplied.
  • Trait for either state or statistics of loggers.
  • Fallible, panic-free version of the log::Log trait.
  • TryLog with some usage statistics on top.


  • The core “read from circular buffer” method. Marked unsafe to discourage use!
  • The core “write to circular buffer” method. Marked unsafe to discourage use!
  • Returns a reference to the logger (as TryLogWithStatistics implementation)
  • The fallible “write to circular buffer” method. Marked unsafe to discourage use!