1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// Copyright 2026 Ethan Wu
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
//! A tiny logging facade that forwards to `defmt`, `tracing`, both, or
//! neither, chosen by Cargo features.
//!
//! Consortium spans `no_std` firmware (which logs with `defmt`) and `std`
//! host-side tooling such as the config codegen (which logs with `tracing`).
//! This crate lets a single call site — `consortium_log::trace!(...)` — compile
//! against whichever backend the consuming crate turns on:
//!
//! - `defmt` routes each macro to the matching `defmt` macro.
//! - `tracing` routes each macro to the matching `tracing` macro. `tracing`
//! also enables [`alloc`](#features) because the `tracing` dependency needs
//! it.
//! - With both enabled, every event is emitted to both backends.
//! - With neither enabled, the macros expand to nothing.
//!
//! # Format syntax
//!
//! The portable subset is a format string plus positional arguments —
//! `trace!("channel {} lowered", name)` — or Rust-style implicit captures —
//! `trace!("channel {name} lowered")`. Implicit captures are normalized to
//! indexed arguments before being forwarded, because `defmt` does not parse
//! them itself. Each argument must implement the traits both backends require
//! (`defmt::Format` for `defmt`, `core::fmt::Display`/`Debug` for `tracing`).
//! Backend-specific extensions (`defmt`'s `{=u8}` type hints, `tracing`'s
//! structured fields) are only valid when that backend is the sole one enabled.
//!
//! Log arguments must be side-effect free: when no backend is enabled the
//! macros expand to nothing and the arguments are never evaluated.
//!
//! # Features
//!
//! - `defmt` — forward to the `defmt` logging macros. No direct `defmt`
//! dependency is needed at the call site: this crate re-exports `defmt` (the
//! re-export exists only when the feature is on, so it is not linked here)
//! and the dispatch macro binds it into scope for the expansion.
//! - `tracing` — forward to the `tracing` logging macros (implies `alloc`).
//! `tracing`'s macros are `$crate`-hygienic, so no direct dependency is
//! needed beyond this crate.
//! - `alloc` — marker capability required by `tracing`; enabling it alone pulls
//! no backend.
//!
//! Neither backend leaks its *name* into the call site, so enabling one on this
//! crate alone resolves. That matters under Cargo feature unification: one
//! workspace member turning on `consortium-log/defmt` switches the facade for
//! *every* member sharing the build, including ones whose own `defmt` feature
//! is off and which therefore have no `defmt` in their extern prelude.
//!
//! Names are only half of it, though. With the `defmt` backend live, every
//! logged argument needs a `defmt::Format` impl, and a crate that gates its
//! impls on its *own* `defmt` feature will not have emitted them — foreign
//! types such as `anyhow::Error` cannot have them at all. Gate those impls with
//! [`when_defmt!`](when_defmt) so they track the backend, and, for the types
//! that can never implement it, keep `consortium-log/defmt` out of any `default`
//! feature so a host build never switches the backend on by accident.
pub use expand_captures as __expand_captures;
// Each backend is re-exported twice, because the two re-exports do different
// jobs and neither substitutes for the other.
//
// The macros are aliased *by name* (`__defmt_info`, …) because that is the
// reliable way to make them resolvable through `$crate::…` from a downstream
// crate: a bare crate re-export does not reliably expose a `#[macro_export]`
// macro by path across crates.
//
// The crate itself is re-exported because naming the macro is only half of it —
// the macro's *expansion* also has to resolve. See [`defmt`].
/// Re-export of the `defmt` crate, so the facade can supply the name itself.
///
/// Aliasing the level macros is not sufficient on its own. `defmt`'s
/// proc-macros emit *bare* `defmt::export::…` paths (not `::defmt::…`, and not
/// `$crate`-relative — a proc-macro cannot synthesize `$crate`), and those
/// tokens carry call-site hygiene, so they resolve in the module that wrote the
/// log call. A crate with `consortium-log/defmt` enabled but no `defmt` of its
/// own has no such name, which is the feature-unification failure this
/// re-export exists to prevent. [`__defmt`] binds it as `defmt` in the block it
/// expands into; that `use` shares call-site hygiene with the emitted paths, so
/// they resolve to it.
///
/// Downstream crates should reach `defmt` items through this re-export rather
/// than through a direct dependency. A crate that writes its own
/// `defmt::Format` impls still needs a direct `defmt` dependency for the trait
/// path, gated on its own `defmt` feature, so the impls track the same feature
/// that selects the backend.
pub use defmt;
pub use ;
pub use ;
// Backend dispatch. `#[cfg]` *inside* an exported macro's body is resolved
// against the *calling* crate's features, not this crate's, so the selection
// must happen where it can see this crate's features: on the definitions of the
// `__defmt`/`__tracing` dispatch macros, gated once each. When a backend is off,
// its dispatch is the no-op arm. The public level macros carry no `#[cfg]`.
/// Routes a level to the aliased `defmt` macro, or nothing when `defmt` is off.
///
/// Each arm expands to a block that first binds the crate's [`defmt`]
/// re-export as `defmt`,
/// which is what lets the bare `defmt::export::…` paths in the expansion
/// resolve without a `defmt` dependency at the call site. The binding is scoped
/// to the block, so it neither collides with nor shadows a `defmt` the calling
/// crate already has in scope.
/// Emits the items it wraps only when this crate's `defmt` backend is enabled.
///
/// The item-position counterpart to the level macros, and gated the same way:
/// on the *macro definition*, so the items track `consortium-log`'s `defmt`
/// feature rather than the calling crate's.
///
/// That distinction is the point. A `defmt::Format` impl gated on the calling
/// crate's own `defmt` feature goes missing exactly when feature unification
/// turns the backend on from elsewhere — the log call sites then expand to
/// `defmt` code that demands `Format` impls which were never emitted. Gating
/// the impls on the backend instead keeps the two in step no matter which
/// member enabled it.
///
/// Name `defmt` items through the [`defmt`] re-export, so the block needs no
/// direct dependency:
///
/// ```ignore
/// consortium_log::when_defmt! {
/// impl consortium_log::defmt::Format for Chan {
/// fn format(&self, f: consortium_log::defmt::Formatter<'_>) {
/// // `defmt::write!` expands to bare `defmt::…` paths, so bind the
/// // re-export in the body they expand into.
/// use consortium_log::defmt;
/// defmt::write!(f, "Chan({=u8})", self.0)
/// }
/// }
/// }
/// ```
/// Drops the items it wraps, because this crate's `defmt` backend is disabled.
/// Routes a level to the aliased `tracing` macro, or nothing when it is off.
// When both backends are disabled, still expand the portable format arguments
// so variables referenced only by a log call count as used. The false branch
// ensures argument expressions are never evaluated at runtime.
/// Logs at the `trace` level to every enabled backend. See the
/// [crate docs](crate) for the portable format syntax.
/// Logs at the `debug` level to every enabled backend.
/// Logs at the `info` level to every enabled backend.
/// Logs at the `warn` level to every enabled backend.
/// Logs at the `error` level to every enabled backend.
// This function is never run: it keeps implicit-capture expansion type-checked
// for the no-backend, tracing, defmt, and combined feature configurations
// without requiring a linked defmt global logger in tests.
// Exercised with the `tracing` backend only: a `defmt`-enabled test binary would
// need a linked `#[defmt::global_logger]`, which a plain unit test cannot supply.