# `defmt2log`
Keep writing real `defmt` in code that also runs on the host.
`defmt2log` lets a host binary decode those `defmt` frames and emit ordinary
[`log`](https://docs.rs/log) records, so you keep:
- full `defmt` format hints and syntax in the code
- `DEFMT_LOG` compile-time filtering
- normal host `log` tooling such as `RUST_LOG`, `env_logger`, and downstream
`log` sinks
Initialization modes:
- `init_from_current_exe()` for the normal host-binary case
- `init_from_elf_path(path)` for any ELF path with a merged `.defmt` section,
and also for the current executable
- `init_from_merged_elf_bytes(bytes)` for pre-merged ELF bytes
## Usage
```rust
fn main() {
env_logger::init();
defmt2log::init_from_current_exe().unwrap();
defmt::info!("word {=u32:#010x}", 0x1234u32);
}
```
- normal debug and release host binaries work as well as libtest unit
tests, examples, and integration tests; they need `defmt2log::init_from_*()`
- rustdoc doctest executables are worse: the bundled doctest rlib still
contains `.defmt.info.*`, but the final `rust_out` test executable keeps only
`.defmt.end` plus `_defmt_version_` / `_defmt_encoding_`, so there is no
table to decode at runtime; those split metadata sections are dead-stripped
from the final doctest executable
Recommended default:
- build with `DEFMT_LOG=info`
- run with `RUST_LOG=info`
- leave `log` compile-time max-level features alone
- source locations are loaded on a best-effort basis; if they cannot be loaded,
decoding still works and `defmt2log` warns once
- if `DEFMT_LOG` is unset or more restrictive than your callsites, normal
`defmt::{trace,debug,info,warn,error}!` output is compiled out entirely; in
that case `defmt2log` may still initialize successfully, but there is
nothing for it to decode
- `DEFMT_LOG=off` and no `defmt::println!()` removes the need for a `defmt` `#[global_logger]`
## Filters
- `DEFMT_LOG` is the compile-time filter for `defmt`
- `RUST_LOG` is the runtime filter for the host `log` sink
- `max_level_*` and `release_max_level_*` affect ordinary host `log` callsites,
not `defmt`
The important consequence is simple: `RUST_LOG` cannot bring back `defmt`
callsites that `DEFMT_LOG` compiled out.
Note that `defmt::println!()` is converted into `INFO` log level messages.
## Avoid
- `DEFMT_LOG=trace` with `RUST_LOG=warn` unless you intentionally want to pay
decode cost for logs the sink will hide
- using `max_level_*` features to control `defmt`
- using `init_from_merged_elf_bytes()` for a normal host executable; that API
is only for ELFs that already contain a merged `.defmt` section.
- expecting `init_from_elf_path(path)` to synthesize a table for an arbitrary
non-running host binary without a merged `.defmt` section
## Limitations
- no doctests yet
- `defmt2log` typically less efficient than pure `log`; the overhead is
the sum of the defmt overheads: serialization, deserialization, and formatting
- every compile-time-enabled `defmt` frame is decoded in-process
- `init_from_current_exe()` is Linux-oriented today:
the split-`.defmt.*` synthetic fallback depends on `/proc/self/maps`
- `init_from_elf_path()` and `init_from_merged_elf_bytes()` are the more
portable modes: they work when the input already has a merged `.defmt`
section
- native macOS current-executable support is not a supported path today
- host bitflags names require linker support that preserves `.defmt.end*`
metadata; without that, host decoders only see the bitflags format tag and
fall back to the raw numeric value
- a future host-side linker setup may preserve `.defmt.end*` and merge
`.defmt.*` into one real `.defmt` section so `defmt-decoder::Table::parse()`
can be used directly, but that is not a supported recipe yet
- With the synthesized table, the naive unmerged `DefmtSymbol::runtime_index: u16`
can collide
## Alternatives
- only `log::*`: simplest host setup, but you give up efficient `defmt` format
hints and `DEFMT_LOG` compile-time filtering.
- `defmt-or-log` or the `fmt.rs` trick: useful when one shared codebase must
compile against either backend, but the shared callsites have to stay within
the portable subset.
- `defmt-logger`: also aims at `defmt` -> `log` but it is old. `defmt2log` is built
around current `defmt-decoder` `log` interop and preserves the normal host
`log` pipeline end to end: `RUST_LOG` filtering, existing sinks, and other
downstream `log` machinery.
- external decoding such as `defmt-print`: keeps full `defmt`, but moves the
decode and process orchestration outside the program. `defmt2log` keeps the
same logging stream inside the host process, merges with other `log` sources,
and feeds ordinary `log` sinks directly.