#[global_logger]
Applications that, directly or transitively, use any of
defmt
logging macros need to define a#[global_logger]
or include one in their dependency graph.
This is similar to how the alloc
crate depends on a #[global_allocator]
.
The global_logger
defines how data is moved from the device, where the application runs, to the host, where logs will be formatted and displayed.
global_logger
is transport agnostic: you can use a serial interface, serial over USB, RTT, semihosting, Ethernet, 6LoWPAN, etc. to transfer the data.
The global_logger
interface comprises the trait Logger
and the #[global_logger]
attribute.
The Logger
trait
Logger
specifies how to acquire and release a handle to a global logger, as well as how the data is put on the wire.
#![allow(unused)] fn main() { extern crate defmt; struct Logger; unsafe impl defmt::Logger for Logger { fn acquire() { // ... } unsafe fn flush() { // ... } unsafe fn release() { // ... } unsafe fn write(bytes: &[u8]) { // ... } } }
The write
method is not allowed to fail.
Buffering, rather than waiting on I/O, is recommended.
If using buffering write
should not overwrite old data as this can corrupt log frames and most printers cannot deal with incomplete log frames.
See the API documentation for more details about the safety requirements of the acquire-release mechanism.
The #[global_logger]
attribute
#[global_logger]
specifies which Logger
implementation will be used by the application.
#[global_logger]
must be used on a unit struct, a struct with no fields, which must implement the Logger
trait.
It's recommended that this struct is kept private.
#![allow(unused)] fn main() { extern crate defmt; #[defmt::global_logger] struct Logger; unsafe impl defmt::Logger for Logger { // ... fn acquire() {} unsafe fn flush() {} unsafe fn release() {} unsafe fn write(bytes: &[u8]) {} } }
⚠️ Only a single
#[global_logger]
struct can appear in the dependency graph of an application.Therefore the
global_logger
should be selected at the top of the dependency graph, that is in the application crate.
There are two general ways to implement a global_logger
.
Single logging channel
The first form uses a single channel.
This means that all execution contexts (i.e. threads OR main
+ interrupt handlers) use the same logging channel.
In an application that uses interrupts this means that acquire
must disable interrupts and release
must re-enable interrupts.
This synchronizes access to the single channel, from contexts running at different priority levels.
defmt-semihosting
is an example of this single logging channel approach.
Multiple logging channels
The other approach uses multiple logging channels: e.g. one for each priority level in an application that uses interrupts. With this approach logging can be made lock-free: interrupts are not disabled while logging data. This approach requires channel multiplexing in the transport layer. RTT, for example, natively supports multiple channels so this is not an issue, but other transports, like ITM, will require that each log frame to be tagged with the channel it belongs to (e.g. one logging channel = ITM channel).
The trade-offs of using more channels are:
- Lock-freedom
- higher memory usage on the target, for buffering
- lower overall throughput, as either different channels need to be polled from the host or the log frames need to be tagged with the channel they belong to