Skip to content

feat(core): add a log destination an FFI layer can install (design 050) - #237

Merged
lxsaah merged 2 commits into
mainfrom
feat/logging-rework
Aug 28, 2026
Merged

feat(core): add a log destination an FFI layer can install (design 050)#237
lxsaah merged 2 commits into
mainfrom
feat/logging-rework

Conversation

@lxsaah

@lxsaah lxsaah commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The log_* facade had exactly one kind of destination: a process-global
tracing::Subscriber. That left both FFI doors — weather-station-py and
weather-station-cpp — installing the host application's global subscriber
on its behalf, which is the trespass those doors otherwise refuse. It also
produced a real defect: a tracing::Layer has nowhere to put the caller's
void *user_data, so the C++ header kept a static SinkHolder, wrote it
non-atomically while the runtime thread read it, and (being last-wins over a
first-wins C layer) silently replaced the first caller's sink while returning
false to say it had not.

Add an optional, non-default log feature whose only effect is a second arm
in each of the four macros, plus a #[doc(hidden)] __private re-export of the
log crate so a facade user needs the feature but not the dependency. A
log::Log impl is an ordinary value, so the context pointer travels with the
callback, and set_logger decides first-wins once, in Rust, for every binding.

No new public API, no new unsafe. With the feature off — the default, and
every MCU build — the expansion is what it was. tracing stays the default and
the recommendation for Rust consumers; both may be on at once.

aimdb-sync mirrors the feature, which is required rather than cosmetic: the
macros are #[macro_export]ed, so #[cfg(feature = "log")] inside them is
resolved against the expanding crate, and aimdb-core/log alone would leave
its ten call sites unrouted. aimdb-sync/tests/log_facade.rs guards that.

Two things the design doc understated, now written down where they will be
read (crate docs, §4/§5.1 of the design):

  • tracing's dispatcher has a reentrancy guard and log has none, so a
    destination that logs back into aimdb recurses without bound rather than
    dropping an event.
  • "both features on" is not the same as "the process wanted both": with
    tracing-subscriber's default tracing-log feature, a process that
    installed only a subscriber sees every event twice.

Tests cover acceptance criteria 1-7. log::set_logger is once per process, so
each criterion that installs a logger owns its own integration-test binary.
Criterion 4 uses a hand-written counting tracing::Subscriber rather than
tracing-subscriber, since not needing it is the point.

Steps 2 and 3 of the sequencing are the FFI doors themselves, which live
outside this repository.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01QgaHbjCPgkNS6Y34jxqVYH

claude added 2 commits August 27, 2026 20:14
…050)

The `log_*` facade had exactly one kind of destination: a process-global
`tracing::Subscriber`. That left both FFI doors — `weather-station-py` and
`weather-station-cpp` — installing the host application's global subscriber
on its behalf, which is the trespass those doors otherwise refuse. It also
produced a real defect: a `tracing::Layer` has nowhere to put the caller's
`void *user_data`, so the C++ header kept a static `SinkHolder`, wrote it
non-atomically while the runtime thread read it, and (being last-wins over a
first-wins C layer) silently replaced the first caller's sink while returning
`false` to say it had not.

Add an optional, non-default `log` feature whose only effect is a second arm
in each of the four macros, plus a `#[doc(hidden)] __private` re-export of the
`log` crate so a facade user needs the feature but not the dependency. A
`log::Log` impl is an ordinary value, so the context pointer travels with the
callback, and `set_logger` decides first-wins once, in Rust, for every binding.

No new public API, no new `unsafe`. With the feature off — the default, and
every MCU build — the expansion is what it was. `tracing` stays the default and
the recommendation for Rust consumers; both may be on at once.

`aimdb-sync` mirrors the feature, which is required rather than cosmetic: the
macros are `#[macro_export]`ed, so `#[cfg(feature = "log")]` inside them is
resolved against the expanding crate, and `aimdb-core/log` alone would leave
its ten call sites unrouted. `aimdb-sync/tests/log_facade.rs` guards that.

Two things the design doc understated, now written down where they will be
read (crate docs, §4/§5.1 of the design):

- `tracing`'s dispatcher has a reentrancy guard and `log` has none, so a
  destination that logs back into aimdb recurses without bound rather than
  dropping an event.
- "both features on" is not the same as "the process wanted both": with
  `tracing-subscriber`'s default `tracing-log` feature, a process that
  installed only a subscriber sees every event twice.

Tests cover acceptance criteria 1-7. `log::set_logger` is once per process, so
each criterion that installs a logger owns its own integration-test binary.
Criterion 4 uses a hand-written counting `tracing::Subscriber` rather than
`tracing-subscriber`, since not needing it is the point.

Steps 2 and 3 of the sequencing are the FFI doors themselves, which live
outside this repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgaHbjCPgkNS6Y34jxqVYH
The crate docs had grown to 106 lines for one optional feature, and the tests
were explaining themselves twice. Keep the parts a reader cannot reconstruct —
the five destination guarantees, and the fact that the feature must be mirrored
by every crate expanding the facade — and leave filtering, duplicate delivery
and the direct-`tracing::` connectors to the design doc, which already covers
them at length.

No behaviour or test change: 118 comment lines out, none of the code touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgaHbjCPgkNS6Y34jxqVYH
@lxsaah
lxsaah merged commit 878486b into main Aug 28, 2026
11 checks passed
@lxsaah
lxsaah deleted the feat/logging-rework branch August 28, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants