What is defmt?
The defmt
library is described in the defmt
book as:
A highly efficient logging framework that targets resource-constrained devices, like microcontrollers.
It's important to note that, despite the most common use-case, defmt
doesn't target microcontrollers exclusively, and nor does it target Arm systems in particular. It is useful wherever you need a high efficiency logging framework.
As with so many things, what we call defmt
is actually many different things wearing a trenchcoat. Under the general umbrella of defmt
we have:
-
The Logging API for libraries to emit log messages:
info!("Received buffer {=[u8]} at {=u32:us}", buffer, rx_timestamp);
These calls are similar to, but not fully compatible with, the
std::fmt
machinery used inprintln!
and friends. In particular,defmt
supports=u32
and similar to specify the type of the value being printed, butstd::fmt
does not. We added this to save space because then the type of the value doesn't need to be serialised into the logging frame. -
The Logging Implementation, contained in the
defmt
crate. This includes macros which emit magically named symbols that intern the format strings and other metadata using The String Interner (5), then call into the registered globaldefmt
transport using The Logging ABI (3) to send messages using The Encoding Implementation (4). -
The Logging ABI: a stable mechanism for connecting a
defmt
transport to logging calls made in libraries (leaving the library independent of the whichever logging implementation the application author has chosen). This is done using theLogger
trait and theglobal_logger!
macro. -
The Encoding Implementation: A mechanism for emitting log messages as both interned strings (at compile time) and log frames (at run time). As this mechanism can change over time, it is identified with a Defmt Version value. A Defmt Version is a string, but currently each prior version has had a simple numeric value; the current Defmt Version is
"4"
. This version number unambiguously identifies both the The String Interner (5) and the The Wire Format (6), which then allows thedefmt
logs to be reconstituted.The Defmt Version produced by the
defmt
crate is indicated in the ELF symbol table by a symbol with a name like_defmt_version_ = X
. As ofdefmt-0.3.10
, that symbol is called_defmt_version_ = 4
to indicate Defmt Version 4. Note that it does not indicate the version number of thedefmt
crate. It would, in theory, be possible for one version of thedefmt
crate to offer multiple Defmt Versions, controlled with feature flags, although it is highly likely that only one could be selected at a time to avoid conflicts.Adding a new Defmt Version is not considered a breaking change, because of this mechanism to self-describe which version it is using, although it may require users to update any host tools (like
probe-rs
) to use a new enough version ofdefmt-decoder
. Dropping support withindefmt-decoder
for an older wire format is considered a breaking change to that crate. -
The String Interner: a mechanism for embedding
defmt
interned strings and metadata in such a way that they do not occupy space in the target's flash, but can be read by a host-side tool likedefmt-decoder
. In Defmt Version 4, the data is stored as JSON formatted symbol names within an ELF file, such that each is allocated a unique 'memory address' (which acts as the interned string's ID).These symbols are placed within a NOLOAD section and so do not occupy any storage space on the target. Here is an example of a symbol from Defmt Version 4, as viewed with
objdump
:00000002 g O .defmt 00000001 {"package":"dk","tag":"defmt_debug","data":"Initializing the board","disambiguator":"15307126232750674618","crate_name":"dk"}
We can see it lies at memory address
0x0000_0002
, thus giving it an ID of2
. The JSON encoded object tells of which package generated the message, the log level, and the string itself. -
The Wire Format: A mechanism for encoding
defmt
log messages intodefmt
log frames (the wire format). Any given Defmt Version may have multiple options for how this encoding is performed, which will be indicated using symbols within the.demft
section, like_defmt_encoding_ = rzcobs
. -
The Format Trait: a trait,
defmt::Format
, for letting user-defined data types encode themselves into the The Wire Format. This then allows them to be passed as arguments to thedefmt
logging macros in The Logging API (1). There is an associated derive-macro which can automatically implement this trait on anystruct
orenum
comprising only values that implement that trait (in a similar vein toderive(Debug)
forcore::fmt::Debug
). -
The Reference Logging Transport called
defmt-rtt
, which sends encodeddefmt
frames over Segger's Real Time Transport (RTT). RTT is an in-memory ringbuffer which is written to by the target's CPU and read from by a debug probe under the control of a host machine. RTT is usually used to send strings (like a serial port) but we use it to send encodeddefmt
frames. -
The Reference Logging Parser called
defmt-parser
, which can readdefmt
log frames (e.g. as received over RTT via a debug probe connected to an MCU that is usingdefmt-rtt
as its logging transport) and turn them into Rust structures. -
The Reference Decoder Library called
defmt-decoder
, which can read structures that representdefmt
log frames (e.g. as output bydefmt-parser
) and turn them into formatted Unicode strings. -
The Reference Parser Binary called
defmt-print
, which usesdefmt-decoder
to decode standard input, and prints formatted log messages to standard output.
There is a delicate balance between these components. As one example, it is important that a tool that uses defmt-decoder
(e.g. the popular probe-rs
programming tool) uses a version that knows about the various different Defmt Versions that it might encounter. In addition, is important that a defmt
transport like defmt-rtt
is compatible with the defmt
logging crate being used - even if you are using a driver crate that hasn't been updated for some time and was written to use an older version of the defmt
library. Because the logging library and the transport communicate through magically named macro-generated extern "C"
functions, we must limit users to a single instance of the defmt
crate in the dependency tree. This means major version bumps on the defmt
crate have ecosystem-splitting effects that we really want to avoid.
What does a 1.0 mean?
A 1.0 release is a commitment to stability, as demonstrated to great effect by the Rust Project itself.
The current Rust language is the result of a lot of iteration and experimentation. The process has worked out well for us: Rust today is both simpler and more powerful than we originally thought would be possible. But all that experimentation also made it difficult to maintain projects written in Rust, since the language and standard library were constantly changing.
The 1.0 release marks the end of that churn. This release is the official beginning of our commitment to stability, and as such it offers a firm foundation for building applications and libraries. From this point forward, breaking changes are largely out of scope (some minor caveats apply, such as compiler bugs).
When we started, defmt
was a novel idea and no-one was certain it would work out in practice. The response has been remarkable:
- There are currently 491 crates on crates.io which depend on
defmt
at the time of writing (mostly libraries usingdefmt
to emit logs, but also somedefmt
transports) - There are currently over 5,500 repositories on Github which depend on
defmt
(a mixture of libraries and binaries)
However, to get there, defmt
has had to change and adapt. The defmt
crate has had around 19 releases, getting as far as v0.3.10. This version implements Defmt Version 4, whilst our latest defmt-decoder
was v0.4.0, which can interpret Defmt Version 4 and Defmt Version 3.
Whilst we have been effectively stable since the v0.3 release, the v1.0 release of defmt
will mark an official end to any churn, and the beginning of our commitment to stability for the defmt
ecosystem.
The defmt 1.0 release
-
Libraries will be able to specify
defmt = "1"
in theirCargo.toml
file. Subsequent releases ofdefmt
will follow Cargo's guidelines on Semantic Versioning. There is currently no intention to ever produce adefmt
2.0 release. -
There will be a final 0.3-series release of
defmt
, which importsdefmt = "1"
and re-exports adefmt-0.3
compatible API (the so-called 'semver trick'). -
Any library currently using
defmt = "^0.3"
in theirCargo.toml
file can replace that line withdefmt = "1"
, and everything will continue to work, unless you are locked to a version of0.3
prior to the final 1.0-compatible version. -
A binary can link against both a library using
defmt = "^0.3"
and a library usingdefmt = "1"
(this was not possible for the defmt-0.2 to defmt-0.3 upgrade).Note however that any dependency locking to a specific earlier of defmt (e.g. using
defmt = "=0.3.10"
) will conflict with the use of defmt-1.0. -
The
defmt-decoder
will be updated to 1.0, which will be a breaking change for any binary or library using the existing version. These breaking changes will be documented, with common fixes identified. A PR will be produced forprobe-rs
to update todefmt-decoder
1.0. Subsequent releases ofdefmt-decoder
will will follow Cargo's guidelines on Semantic Versioning. -
The ABI between the
defmt
library and the transport implementations (likedefmt-rtt
) will be stabilised as-is and documented in thedefmt
book. -
The
defmt-rtt
transport will be updated to 1.0, but any application usingdefmt-rtt = "^0.3"
can be updated to usedefmt-rtt = "^1.0"
with no further changes required to the program. -
The list of Defmt Versions supported by
defmt-decoder
(and hencedefmt-print
) will be documented, including the period of time for which the Knurling Project is guaranteeing to support each of those formats with support and updates indefmt-decoder
. -
A mechanism will be documented whereby users can add their own custom Defmt Versions. A set of Defmt Versions will be reserved for private use, along the lines of the Unicode Private Use Area. This will allow users to fork the
defmt
crate anddefmt-decoder
crates for private use, confident that the production versions will never start producing output with a conflicting Defmt Version. Once fully developed, there is the option to upstream such changes, but where significant differences are to be found, we might want to optionally enable the new encoding to make upgrades easier for existing users.For example, a user might wish to change the wire format so that every defmt log frame includes a single byte to indicate the logging level, rather than that information only being available inside the interned metadata. They could temporarily fork the
defmt
anddefmt-decoder
crates and prototype these changes using a PUA Defmt Version (e.g. Version 1000). At a later time, this could be added to thedefmt
crate as part of, say Defmt Version 7, but as an optional feature enabled in much the same way that RZCOBS encoding is enabled currently (with a symbol called_defmt_encoding = rzcobs
). -
The
defmt-parser
library will remain an unstable, internal-only tool. Thedefmt-decoder
will be the stable mechanism to turn encoded binary data into Unicode text with optional ANSI escape-sequence-based formatting.
Next Steps
We have already published the release candidate crates defmt-1.0-rc.1
and defmt-0.3.100-rc.1
so you can review all the documentation online. We also have a git branch that you can use for testing - we highly recommend you do this, using a cargo patch:
[patch.crates-io]
defmt = { git = "https://github.com/knurling-rs/defmt", branch = "defmt-1.0", version = "0.3.100" }
Any crate in your tree using defmt = "0.3"
should automatically be upgraded to 0.3.100
, which means you are in fact using the latest 1.0
release.
The 1.0 crates proper should be published on or around the 27 January, but, anyone still using defmt = "^0.3"
won't get upgraded to 1.0 until we publish the semver-trick crate (defmt
0.3.100). That will happen on 05 February, and everyone will (in theory) seamlessly move over to this new world of defmt
1.0, automatically.
Please give the release candidate crates a trial, and if you observe any issues, please let us know in the issue tracker, and if you have questions about the defmt-1.0
release, you can join in our Github Discussion on the topic.
Onwards, to the future of defmt
!