Repository navigation
[FEAT] Core log macros reach defmt: forward log_*! to defmt and drop the hand-written mirrors #352
Description
Is your feature request related to a problem? Please describe.
On an Embassy device, core's own log lines never reach the probe log. aimdb-core/src/log.rs forwards the log_*! macros to tracing and log only. defmt was left out on purpose, because many call sites use {:?} on types that don't implement defmt::Format. Places that matter on a device mirror by hand instead (#[cfg(feature = "defmt")] defmt::warn!(…) next to the log_warn!).
That has two costs:
- Events are invisible on the device. The session engine has no mirrors at all. While testing "[BUG] AimX over serial: every other reconnect gets no events until the board is reset" #328 and [BUG] session::serve spins on a listener whose accept keeps failing, and starves every open connection #351 on a NUCLEO-H563ZI, the session reset ("new handshake … ending N subscription(s)"), refused and ended subscriptions, and
serve's accept-failure summary ("accept failed …", "still failing: N attempts") could only be seen by their effect. - Every message exists twice. There are 30 hand-written
defmt::…!mirrors (21 inaimdb-mqtt-connector, 5 inaimdb-embassy-adapter, 4 inaimdb-core). They drift, and new log lines get no mirror. That's how the session engine ended up without one.
Describe the solution you'd like
log_trace! … log_error! also forward to defmt under core's defmt feature, as a third destination next to tracing and log. No new logging layer and no change to call sites' format strings.
A check against defmt 1.0.1 (thumbv7em, cargo check) shows that defmt accepts the existing syntax, including {:?} as a display hint, and with defmt's alloc feature it formats String and Vec<String>. So the work is in the argument types, not the call sites.
Scope (measured by parsing every macro call)
| Crate | log_*! calls |
including {:?} |
builds with defmt |
|---|---|---|---|
aimdb-core |
61 | 17 | yes |
aimdb-mqtt-connector |
14 | 1 | yes |
aimdb-knx-connector |
7 | 1 | yes |
aimdb-serial-connector |
1 | 0 | yes |
aimdb-zenoh-connector, aimdb-sync, aimdb-uds-connector |
31 | 1 | no (std only, unaffected) |
Plan: a short PR series, each reviewed separately.
defmt::Formatfor logged argument types, under thedefmtfeature:cfg_attr(feature = "defmt", derive(defmt::Format))onTransportError,CodecError,AuthError,RpcError, the framer error insession/io.rs, and the error types logged by the MQTT and KNX connectors;DbErrorgets a hand-written impl. Itsstd-only variants carryio::Error/serde_json::Error, which don't implementFormat, and docs.rs builds withall-features = true(Cargo.toml:186), sostdanddefmtare on together there and a derive would break that build;- core's
defmtfeature enablesdefmt/alloc.
- The macro branch, plus CI:
#[cfg(feature = "defmt")] defmt::…!in eachlog_*!macro, with thelog.rsmodule docs updated. Directthumbv7emchecks withdefmtforaimdb-core(alloc,connector-session,defmt),aimdb-mqtt-connectorandaimdb-knx-connector. Today core withdefmtis only compiled through the serial connector, so a missingFormatimpl would surface late. - Remove the hand-written mirrors that the macros now cover (MQTT connector, Embassy adapter, core), keeping only those that log something the
log_*!line doesn't.
Contract change: the macros are #[macro_export]ed. With defmt on, their arguments must implement defmt::Format. Normal (tracing/log) builds are unaffected. This needs a changelog entry.
On the device:
- Format strings are interned, so they cost almost no flash.
debug/tracelines are filtered at compile time viaDEFMT_LOG, so they cost nothing at the defaultinfolevel.- Firmware built with
DEFMT_LOG=debugwill show about 60 more core lines than today.
Verification
- The new
thumbv7emchecks. - Live on a NUCLEO-H563ZI: two
aimdb watchclients one after the other over serial show the session reset; awatchon a record without a free subscriber slot shows the ended subscription; a TCP server on port 0 showsserve's accept-failure summary ([BUG] session::serve spins on a listener whose accept keeps failing, and starves every open connection #351).
Describe alternatives you've considered
- Hand-written mirrors in the session engine only (the first plan for this issue): small, but it adds to the duplication above and leaves every other core log line invisible on devices.
- Typed events (
SessionEvent, …) instead of text: valuable beyond logging (tests, metrics, introspection), but a separate design question, tracked in its own issue. It doesn't depend on this one.
Additional context
- Builds on [BUG] session::serve spins on a listener whose accept keeps failing, and starves every open connection #351 (
serve's accept-failure logging, PR fix(session): a failing listener no longer stalls serve (#351) #356). Start after fix(session): a failing listener no longer stalls serve (#351) #356 is merged. - Related: "[BUG] AimX over serial: every other reconnect gets no events until the board is reset" #328.
🤖 Generated with Claude Code
Activity
[-][FEAT] Session engine: mirror its operational events to defmt, as the router does[/-][+][FEAT] Core log macros reach defmt: forward log_*! to defmt and drop the hand-written mirrors[/+]docs(#352): changelogs for the defmt log destination
feat(core): core log lines reach defmt (#352) (#361)