Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,10 @@ clippy:
cargo clippy --package aimdb-mqtt-connector --target thumbv7em-none-eabihf --no-default-features --features "embassy-runtime,embassy-tls,defmt" -- -D warnings
@printf "$(YELLOW) → Clippy on KNX connector (neutral + defmt)$(NC)\n"
cargo clippy --package aimdb-knx-connector --target thumbv7em-none-eabihf --no-default-features --features "connector,defmt" -- -D warnings
@printf "$(YELLOW) → Clippy on aimdb-core (no_std + defmt log destination)$(NC)\n"
cargo clippy --package aimdb-core --target thumbv7em-none-eabihf --no-default-features --features "alloc,connector-session,remote,defmt" -- -D warnings
@printf "$(YELLOW) → Clippy on Zenoh connector (embedded + defmt)$(NC)\n"
cargo clippy --package aimdb-zenoh-connector --target thumbv7em-none-eabihf --no-default-features --features "embedded,defmt" -- -D warnings
@printf "$(YELLOW) → Clippy on WebSocket connector$(NC)\n"
cargo clippy --package aimdb-websocket-connector --features "tokio-runtime,client" --all-targets -- -D warnings
@printf "$(YELLOW) → Clippy on UDS connector$(NC)\n"
Expand Down Expand Up @@ -558,6 +562,10 @@ test-embedded:
cargo check --package aimdb-core --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features alloc
@printf "$(YELLOW) → Checking aimdb-core (no_std + log destination) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-core --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "alloc,log"
@printf "$(YELLOW) → Checking aimdb-core (no_std + defmt log destination) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-core --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "alloc,defmt"
@printf "$(YELLOW) → Checking aimdb-core (full no_std AimX server + defmt log destination) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-core --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "alloc,connector-session,remote,defmt"
@printf "$(YELLOW) → Checking aimdb-embassy-adapter on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-embassy-adapter --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "embassy-runtime"
@printf "$(YELLOW) → Checking aimdb-embassy-adapter with network support on thumbv7em-none-eabihf target$(NC)\n"
Expand Down Expand Up @@ -597,6 +605,8 @@ test-embedded:
@printf "$(BLUE)✓ mountain-mqtt is the codec alone$(NC)\n"
@printf "$(YELLOW) → Checking aimdb-zenoh-connector (embedded backend) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-zenoh-connector --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "embedded"
@printf "$(YELLOW) → Checking aimdb-zenoh-connector (embedded backend + defmt) on thumbv7em-none-eabihf target$(NC)\n"
cargo check --package aimdb-zenoh-connector --target thumbv7em-none-eabihf --target-dir $(EMBEDDED_CHECK_TARGET_DIR) --no-default-features --features "embedded,defmt"
@printf "$(YELLOW) → Asserting no std-only Zenoh crates in the embedded Zenoh backend$(NC)\n"
@err=$$(mktemp); \
out=$$(cargo tree -p aimdb-zenoh-connector --target thumbv7em-none-eabihf --no-default-features --features "embedded" -e features,no-dev 2>$$err) || { \
Expand Down
1 change: 1 addition & 0 deletions aimdb-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pub mod typed_record;
pub use context::RuntimeContext;
pub use error::{ConfigError, DbError, DbErrorKind, DbResult};
pub use extensions::Extensions;
pub use log::{LogDebug, LogDisplay};

// Runtime capability surface: the runtime travels as `Arc<dyn RuntimeOps>`,
// the one trait an adapter implements.
Expand Down
97 changes: 79 additions & 18 deletions aimdb-core/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
//!
//! `log_trace!`/`log_debug!`/`log_info!`/`log_warn!`/`log_error!` forward to every enabled
//! *destination* — the `tracing` event macro under the `tracing` feature, the
//! `log` crate under the `log` feature — and otherwise expand to a no-op that
//! still borrows the arguments, so call sites compile identically (no
//! unused-variable warnings) under every feature combination. This replaces the
//! per-call-site `#[cfg(feature = "tracing")]` gates.
//! `log` crate under the `log` feature, `defmt` under the `defmt` feature — and
//! otherwise expand to a no-op that still borrows the arguments, so call sites
//! compile identically (no unused-variable warnings) under every feature
//! combination. This replaces the per-call-site `#[cfg(feature = "tracing")]`
//! gates.
//!
//! Notes:
//! - The no-op branch *borrows* (and therefore evaluates) the arguments — keep
//! them cheap (getters, lengths, references), never allocate in hot paths.
//! - `defmt` is intentionally not folded in: most call sites use `{:?}` with
//! types that do not implement `defmt::Format` (e.g. `DbError`, `String`,
//! `Vec<String>`). The few sites that mirror events to defmt (router.rs)
//! keep explicit `#[cfg(feature = "defmt")]` gates next to these macros.
//! - Under `defmt`, every argument must implement `defmt::Format` (the format
//! strings are shared: `defmt` reads `{}` and `{:?}` alike). Core's own
//! types do (#352); a crate logging its own types derives or implements
//! `Format` for them under its `defmt` feature, and wraps a foreign type in
//! [`LogDisplay`] / [`LogDebug`].
//!
//! # Mirroring the feature
//!
Expand All @@ -23,19 +25,30 @@
//! nowhere:
//!
//! ```toml
//! tracing = ["aimdb-core/tracing"] # no `dep:tracing`
//! log = ["aimdb-core/log"] # no `dep:log`
//! tracing = ["aimdb-core/tracing"] # no `dep:tracing`
//! log = ["aimdb-core/log"] # no `dep:log`
//! defmt = ["dep:defmt", "aimdb-core/defmt"] # `dep:defmt` required
//! ```
//!
//! Neither arm needs the dependency declared: both go through `$crate::__private`.
//! The *feature* is what cannot be dropped — a `#[cfg]` in a `#[macro_export]`ed
//! macro is resolved where it expands, so a crate without it would emit nothing.
//! `tracing` and `log` need no dependency declared: both arms go through
//! `$crate::__private`. `defmt` does: its macros expand to absolute `defmt::`
//! paths, so the calling crate needs `defmt` itself. The *feature* is what
//! cannot be dropped — a `#[cfg]` in a `#[macro_export]`ed macro is resolved
//! where it expands, so a crate without it would emit nothing.
//!
//! That is not a silent failure any more: an undeclared feature name makes
//! `unexpected_cfgs` fire at every call site, which is a warning in an ordinary
//! build and an error under `-D warnings`, as `make clippy` runs it. A dropped
//! mirror therefore breaks CI rather than quietly muting a crate.
//! `aimdb-sync/tests/log_facade.rs` additionally proves delivery end to end.
//!
//! A crate that never builds with `defmt` (a `std`-only connector) declares the
//! name instead, so the `defmt` arm's `#[cfg]` is known to the lint:
//!
//! ```toml
//! [lints.rust]
//! unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("defmt"))'] }
//! ```

#[macro_export]
macro_rules! log_trace {
Expand All @@ -44,7 +57,9 @@ macro_rules! log_trace {
$crate::__private::tracing::trace!($s $(, $x)*);
#[cfg(feature = "log")]
$crate::__private::log::trace!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log")))]
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log", feature = "defmt")))]
{ let _ = ($( & $x ),*); }
}};
}
Expand All @@ -56,7 +71,9 @@ macro_rules! log_debug {
$crate::__private::tracing::debug!($s $(, $x)*);
#[cfg(feature = "log")]
$crate::__private::log::debug!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log")))]
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log", feature = "defmt")))]
{ let _ = ($( & $x ),*); }
}};
}
Expand All @@ -68,7 +85,9 @@ macro_rules! log_info {
$crate::__private::tracing::info!($s $(, $x)*);
#[cfg(feature = "log")]
$crate::__private::log::info!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log")))]
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log", feature = "defmt")))]
{ let _ = ($( & $x ),*); }
}};
}
Expand All @@ -80,7 +99,9 @@ macro_rules! log_warn {
$crate::__private::tracing::warn!($s $(, $x)*);
#[cfg(feature = "log")]
$crate::__private::log::warn!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log")))]
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log", feature = "defmt")))]
{ let _ = ($( & $x ),*); }
}};
}
Expand All @@ -92,7 +113,47 @@ macro_rules! log_error {
$crate::__private::tracing::error!($s $(, $x)*);
#[cfg(feature = "log")]
$crate::__private::log::error!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log")))]
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "tracing", feature = "log", feature = "defmt")))]
{ let _ = ($( & $x ),*); }
}};
}

/// Logs `T` through its `Display` impl in every destination, `defmt` included
/// (via `defmt::Display2Format`).
///
/// For a foreign type that does not implement `defmt::Format` in a `log_*!`
/// call: `log_warn!("send failed: {}", LogDisplay(&e))`. The `tracing` and
/// `log` output is unchanged; under `defmt` the text is formatted on the
/// device, not interned, so keep it to error paths.
pub struct LogDisplay<'a, T: ?Sized>(pub &'a T);

impl<T: core::fmt::Display + ?Sized> core::fmt::Display for LogDisplay<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}

#[cfg(feature = "defmt")]
impl<T: core::fmt::Display + ?Sized> defmt::Format for LogDisplay<'_, T> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{}", defmt::Display2Format(self))
}
}

/// Like [`LogDisplay`], through `Debug` (`{:?}`; `defmt::Debug2Format`).
pub struct LogDebug<'a, T: ?Sized>(pub &'a T);

impl<T: core::fmt::Debug + ?Sized> core::fmt::Debug for LogDebug<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}

#[cfg(feature = "defmt")]
impl<T: core::fmt::Debug + ?Sized> defmt::Format for LogDebug<'_, T> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{}", defmt::Debug2Format(self))
}
}
3 changes: 2 additions & 1 deletion aimdb-knx-connector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ critical-section-std-impl = []
# where it expands, so without them this crate would emit nothing.
tracing = ["aimdb-core/tracing"]
log = ["aimdb-core/log"]
defmt = ["aimdb-core/defmt", "knx-pico/defmt"]
defmt = ["dep:defmt", "defmt/ip_in_core", "aimdb-core/defmt", "knx-pico/defmt"]

[dependencies]
defmt = { workspace = true, optional = true }
aimdb-core = { version = "2.0.0", path = "../aimdb-core", default-features = false }

# aimdb-dev fork: upstream 0.3.0 panics on an npdu_length=1 telegram. A patch
Expand Down
9 changes: 7 additions & 2 deletions aimdb-mqtt-connector/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use aimdb_core::connector::ConnectorUrl;
use aimdb_core::{log_debug, log_error, log_info};
use aimdb_core::{InboundDispatch, OutboundRoutes};
use aimdb_core::{LogDebug, LogDisplay};
use rumqttc::{AsyncClient, Event, EventLoop, MqttOptions, Packet};
use std::future::Future;
use std::pin::Pin;
Expand Down Expand Up @@ -212,7 +213,11 @@ async fn run_event_loop(mut event_loop: EventLoop, inbound: InboundDispatch, _br
}
Ok(_) => {}
Err(_e) => {
log_error!("MQTT event loop error for {}: {:?}", _broker_key, _e);
log_error!(
"MQTT event loop error for {}: {:?}",
_broker_key,
LogDebug(&_e)
);
tokio::time::sleep(Duration::from_secs(5)).await;
}
}
Expand Down Expand Up @@ -255,7 +260,7 @@ async fn run_publish_loop(
log_error!(
"MQTT publish on route '{}' failed: {}",
outbound.routes()[id].default_topic,
_e
LogDisplay(&_e)
);
outbound.reject(id);
}
Expand Down
5 changes: 5 additions & 0 deletions aimdb-sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ libc = { version = "0.2", default-features = false, optional = true }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

# `aimdb-core`'s log macros carry a `#[cfg(feature = "defmt")]` arm that
# expands here; this crate never builds with defmt, so it only names it.
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("defmt"))'] }
5 changes: 5 additions & 0 deletions aimdb-uds-connector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
# For the crate-level doc example (a mirrored record needs a JSON shape)
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# `aimdb-core`'s log macros carry a `#[cfg(feature = "defmt")]` arm that
# expands here; this crate never builds with defmt, so it only names it.
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("defmt"))'] }
3 changes: 3 additions & 0 deletions aimdb-zenoh-connector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ log = ["aimdb-core/log"]
# Reserved for the `zenoh-nostd` backend (`zenoh://` only), once zenoh-nostd is
# on crates.io; today the `no_std` parts such as `ZenohGrammar`.
embedded = ["aimdb-core/alloc"]
# The log lines reach defmt (core's `log_*!` macros), for the embedded build.
defmt = ["dep:defmt", "aimdb-core/defmt"]

[dependencies]
defmt = { workspace = true, optional = true }
aimdb-core = { version = "2.0.0", path = "../aimdb-core", default-features = false }
zenoh = { version = "1.10.1", default-features = false, features = [
"transport_tcp",
Expand Down
9 changes: 5 additions & 4 deletions aimdb-zenoh-connector/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::sync::Arc;
use std::time::Duration;
use std::vec::Vec;

use aimdb_core::LogDisplay;
use aimdb_core::{
log_debug, log_error, log_info, log_warn, AimDb, DbError, DbResult, InboundDispatch,
OutboundRoutes, RouteInfo, TopicFilter, TopicGrammar, TopicPattern, MAX_CAPTURES,
Expand Down Expand Up @@ -171,7 +172,7 @@ impl ZenohParts {
{
Ok(publisher) => publishers.push(Some(publisher)),
Err(_e) => {
log_error!("Zenoh: cannot declare a publisher: {}", _e);
log_error!("Zenoh: cannot declare a publisher: {}", LogDisplay(&_e));
publishers.push(None);
}
}
Expand Down Expand Up @@ -200,7 +201,7 @@ impl ZenohParts {
log_error!(
"Zenoh: put on route '{}' failed: {}",
outbound.routes()[id].default_topic,
_e
LogDisplay(&_e)
);
outbound.reject(id);
}
Expand Down Expand Up @@ -238,7 +239,7 @@ pub(crate) async fn open(config: zenoh::Config) -> Session {
}
}
};
log_info!("Zenoh: session {} open", session.zid());
log_info!("Zenoh: session {} open", LogDisplay(&session.zid()));
session
}

Expand Down Expand Up @@ -323,7 +324,7 @@ async fn subscribe(
subscribers.push(subscriber);
earlier.push(Arc::from(compiled));
}
Err(_e) => log_error!("Zenoh: cannot declare a subscriber: {}", _e),
Err(_e) => log_error!("Zenoh: cannot declare a subscriber: {}", LogDisplay(&_e)),
}
}
subscribers
Expand Down
12 changes: 8 additions & 4 deletions aimdb-zenoh-connector/src/ros2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::vec::Vec;

use aimdb_core::connector::ConnectorBuilder;
use aimdb_core::LogDisplay;
use aimdb_core::{
log_error, log_warn, AimDb, ExactGrammar, InboundDispatch, OutboundRoutes, RouteInfo,
};
Expand Down Expand Up @@ -487,7 +488,10 @@ impl Ros2Parts {
for token in tokens {
match session.liveliness().declare_token(token).await {
Ok(token) => live.push(token),
Err(_e) => log_error!("ROS 2: cannot declare a liveliness token: {}", _e),
Err(_e) => log_error!(
"ROS 2: cannot declare a liveliness token: {}",
LogDisplay(&_e)
),
}
}

Expand All @@ -506,7 +510,7 @@ impl Ros2Parts {
.await;
match declared {
Ok(subscriber) => subscribers.push(subscriber),
Err(_e) => log_error!("ROS 2: cannot declare a subscriber: {}", _e),
Err(_e) => log_error!("ROS 2: cannot declare a subscriber: {}", LogDisplay(&_e)),
}
}

Expand All @@ -523,7 +527,7 @@ impl Ros2Parts {
log_error!(
"ROS 2: cannot declare a publisher on '{}': {}",
publisher.link.topic,
_e
LogDisplay(&_e)
);
senders.push(None);
}
Expand Down Expand Up @@ -551,7 +555,7 @@ impl Ros2Parts {
log_error!(
"ROS 2: put on '{}' failed: {}",
publishers[id].link.topic,
_e
LogDisplay(&_e)
);
outbound.reject(id);
}
Expand Down
Loading