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_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.
global_logger interface comprises two traits,
Logger, and one attribute,
Write trait specifies how the data is put on the wire.
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.
Logger specifies how to acquire and release a handle to a global logger.
See the API documentation for more details about the safety requirements of the acquire-release mechanism.
#[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.
This struct must implement the
It's recommended that this struct is kept private.
Only a single
#[global_logger] struct can appear in the dependency graph of an application.
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
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.
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:
- 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