feat(mavlink): adopt the kaptan-native dialect

This commit is contained in:
Onur Özkan committed 2026-08-24 00:56:06 +03:00
1 parent 1c90870634
commit 5f91465e66
87 files changed
+7733 -6850

No files matched your search

+26 -41
View File
@@ -6,7 +6,7 @@ use core::{
sync::atomic::{AtomicBool, Ordering},
};
use crate::{Level, LogFlags, LogRecord, LogText};
use crate::{Level, LogRecord, LogText};
const GLOBAL_INGRESS_CAPACITY: usize = 2;
static GLOBAL_INGRESS: LogIngress<GLOBAL_INGRESS_CAPACITY> = LogIngress::new();
@@ -33,11 +33,11 @@ impl<const N: usize> LogIngress<N> {
}
}
fn publish(&self, level: Level, flags: LogFlags, arguments: fmt::Arguments<'_>) {
fn publish(&self, level: Level, arguments: fmt::Arguments<'_>) {
let message = arguments
.as_str()
.map_or_else(|| LogText::from_arguments(arguments), LogText::from_message);
let record = LogRecord::new(level, flags, message);
let record = LogRecord::new(level, message);
let _ = self.with_state(|entries, head, len| {
if *len < N {
entries[(*head + *len) % N] = Some(record);
@@ -110,8 +110,8 @@ impl<const N: usize> LogIngress<N> {
/// Publishes one macro event to the fixed global ingress.
#[doc(hidden)]
#[inline]
pub fn publish(level: Level, flags: LogFlags, arguments: fmt::Arguments<'_>) {
GLOBAL_INGRESS.publish(level, flags, arguments);
pub fn publish(level: Level, arguments: fmt::Arguments<'_>) {
GLOBAL_INGRESS.publish(level, arguments);
}
/// Visits and removes the oldest pending log event.
@@ -128,11 +128,16 @@ mod tests {
use core::sync::atomic::Ordering;
use super::{LogIngress, drain_one};
use crate::{Level, LogFlags};
use crate::{Level, LogRecord};
use std::sync::Mutex;
static GLOBAL_INGRESS_TEST: Mutex<()> = Mutex::new(());
fn message(record: &LogRecord) -> &str {
let text = record.message();
core::str::from_utf8(&text.bytes()[..usize::from(text.byte_length())]).unwrap()
}
#[test]
fn every_macro_produces_the_same_log_record_kind() {
let _guard = GLOBAL_INGRESS_TEST
@@ -144,7 +149,7 @@ mod tests {
assert_eq!(drain_one(|record| record.level()), Some(Level::Info));
assert_eq!(
drain_one(|record| {
assert_eq!(record.message().as_str(), "transfer failed");
assert_eq!(message(record), "transfer failed");
record.level()
}),
Some(Level::Error)
@@ -154,29 +159,21 @@ mod tests {
#[test]
fn full_mixed_ingress_keeps_critical_records_and_evicts_best_effort() {
let ingress = LogIngress::<2>::new();
ingress.publish(Level::Info, LogFlags::NONE, core::format_args!("advance"));
ingress.publish(Level::Info, core::format_args!("advance"));
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "advance")),
ingress.drain_one(|record| assert_eq!(message(record), "advance")),
Some(())
);
ingress.publish(Level::Info, LogFlags::NONE, core::format_args!("optional"));
ingress.publish(
Level::Error,
LogFlags::NONE,
core::format_args!("first error"),
);
ingress.publish(
Level::Error,
LogFlags::NONE,
core::format_args!("second error"),
);
ingress.publish(Level::Info, core::format_args!("optional"));
ingress.publish(Level::Error, core::format_args!("first error"));
ingress.publish(Level::Error, core::format_args!("second error"));
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "first error")),
ingress.drain_one(|record| assert_eq!(message(record), "first error")),
Some(())
);
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "second error")),
ingress.drain_one(|record| assert_eq!(message(record), "second error")),
Some(())
);
assert!(ingress.drain_one(|_| ()).is_none());
@@ -185,28 +182,16 @@ mod tests {
#[test]
fn full_critical_ingress_keeps_the_earlier_records() {
let ingress = LogIngress::<2>::new();
ingress.publish(
Level::Error,
LogFlags::NONE,
core::format_args!("first error"),
);
ingress.publish(
Level::Error,
LogFlags::NONE,
core::format_args!("second error"),
);
ingress.publish(
Level::Error,
LogFlags::NONE,
core::format_args!("discarded error"),
);
ingress.publish(Level::Error, core::format_args!("first error"));
ingress.publish(Level::Error, core::format_args!("second error"));
ingress.publish(Level::Error, core::format_args!("discarded error"));
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "first error")),
ingress.drain_one(|record| assert_eq!(message(record), "first error")),
Some(())
);
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "second error")),
ingress.drain_one(|record| assert_eq!(message(record), "second error")),
Some(())
);
assert!(ingress.drain_one(|_| ()).is_none());
@@ -215,7 +200,7 @@ mod tests {
#[test]
fn contended_drain_keeps_the_record_for_a_later_attempt() {
let ingress = LogIngress::<1>::new();
ingress.publish(Level::Info, LogFlags::NONE, core::format_args!("pending"));
ingress.publish(Level::Info, core::format_args!("pending"));
ingress.busy.store(true, Ordering::Relaxed);
let mut called = false;
@@ -224,7 +209,7 @@ mod tests {
ingress.busy.store(false, Ordering::Relaxed);
assert_eq!(
ingress.drain_one(|record| assert_eq!(record.message().as_str(), "pending")),
ingress.drain_one(|record| assert_eq!(message(record), "pending")),
Some(())
);
}
+14 -53
View File
@@ -1,21 +1,6 @@
//! Bounded priority handling for log records.
use core::fmt;
use crate::{DeliveryClass, Level, LevelFilter, LogFlags, LogRecord, LogText};
/// Outcome from one bounded log admission attempt.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublishOutcome {
/// The record was retained.
Queued,
/// The configured level filter excluded the record.
Filtered,
/// A best-effort record was replaced by newer optional information.
ReplacedOldest,
/// The critical lane kept its earlier record.
RejectedFull,
}
use crate::{DeliveryClass, LevelFilter, LogRecord};
/// Result returned after attempting lower-transport admission.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -42,19 +27,17 @@ impl<const N: usize> Lane<N> {
}
}
fn publish(&mut self, record: LogRecord, replace_oldest: bool) -> PublishOutcome {
fn publish(&mut self, record: LogRecord, replace_oldest: bool) {
if self.len == N {
if replace_oldest {
self.entries[self.head] = Some(record);
self.head = (self.head + 1) % N;
return PublishOutcome::ReplacedOldest;
}
return PublishOutcome::RejectedFull;
return;
}
let index = (self.head + self.len) % N;
self.entries[index] = Some(record);
self.len += 1;
PublishOutcome::Queued
}
fn peek(&self) -> Option<LogRecord> {
@@ -89,30 +72,10 @@ impl<const CRITICAL: usize, const BEST_EFFORT: usize> LogSinkQueue<CRITICAL, BES
}
}
/// Immutable sink policy.
pub const fn filter(&self) -> LevelFilter {
self.filter
}
/// Formats and retains one log event.
pub fn publish_arguments(
&mut self,
level: Level,
flags: LogFlags,
arguments: fmt::Arguments<'_>,
) -> PublishOutcome {
self.publish_text(level, flags, LogText::from_arguments(arguments))
}
/// Retains one preformatted log event.
pub fn publish_text(&mut self, level: Level, flags: LogFlags, text: LogText) -> PublishOutcome {
self.publish_record(LogRecord::new(level, flags, text))
}
/// Applies this sink's filter and retains one record.
pub fn publish_record(&mut self, record: LogRecord) -> PublishOutcome {
pub fn publish_record(&mut self, record: LogRecord) {
if !self.filter.allows(record.level()) {
return PublishOutcome::Filtered;
return;
}
match record.delivery() {
DeliveryClass::Critical => self.critical.publish(record, false),
@@ -145,21 +108,19 @@ impl<const CRITICAL: usize, const BEST_EFFORT: usize> LogSinkQueue<CRITICAL, BES
#[cfg(test)]
mod tests {
use super::{LogSinkQueue, PublishOutcome, TransportAdmission};
use crate::{DeliveryClass, Level, LevelFilter, LogFlags, LogText};
use super::{LogSinkQueue, TransportAdmission};
use crate::{DeliveryClass, Level, LevelFilter, LogRecord, LogText};
#[test]
fn filter_and_priority_apply_only_to_logs() {
let mut queue = LogSinkQueue::<1, 1>::new(LevelFilter::Info);
assert_eq!(
queue.publish_text(Level::Debug, LogFlags::NONE, LogText::from_message("debug")),
PublishOutcome::Filtered
);
assert_eq!(
queue.publish_text(Level::Error, LogFlags::NONE, LogText::from_message("error")),
PublishOutcome::Queued
);
assert_eq!(queue.peek().unwrap().delivery(), DeliveryClass::Critical);
queue.publish_record(LogRecord::new(Level::Debug, LogText::from_message("debug")));
assert!(queue.peek().is_none());
queue.publish_record(LogRecord::new(Level::Error, LogText::from_message("error")));
assert!(matches!(
queue.peek().unwrap().delivery(),
DeliveryClass::Critical
));
assert_eq!(
queue.submit_next(|_| (TransportAdmission::Accepted, ())),
Some(())
+32 -21
View File
@@ -1,19 +1,19 @@
//! Compact conventional log levels and filtering.
/// Severity of one emitted log event.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum Level {
/// A runtime operation failed.
Error = 1,
/// Recoverable warning.
Warn = 2,
/// Fine-grained diagnostic output.
Trace = 1,
/// Diagnostic output.
Debug = 2,
/// Informational operation.
Info = 3,
/// Diagnostic output.
Debug = 4,
/// Fine-grained diagnostic output.
Trace = 5,
/// Recoverable warning.
Warn = 4,
/// A runtime operation failed.
Error = 5,
}
impl Level {
@@ -29,28 +29,28 @@ impl Level {
}
}
/// Conventional maximum log level, including the disabled state.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, kaptan_config_derive::ConfigEnum)]
/// Minimum retained log severity, including the disabled state.
#[derive(Clone, Copy, Debug, Eq, PartialEq, kaptan_config_derive::ConfigEnum)]
#[repr(u8)]
pub enum LevelFilter {
/// Disable the sink.
Off = 0,
/// Accept error events only.
Error = 1,
/// Accept errors and warnings.
Warn = 2,
/// Accept through informational events.
Info = 3,
/// Accept through debug events.
Debug = 4,
/// Accept every event, including trace.
Trace = 5,
Trace = 1,
/// Accept debug through error events.
Debug = 2,
/// Accept informational through error events.
Info = 3,
/// Accept warning and error events.
Warn = 4,
/// Accept error events only.
Error = 5,
}
impl LevelFilter {
/// Returns true when this filter accepts the event.
pub const fn allows(self, level: Level) -> bool {
level as u8 <= self as u8
!matches!(self, Self::Off) && level as u8 >= self as u8
}
}
@@ -64,6 +64,17 @@ mod tests {
fn levels_are_one_byte_and_filter_conventionally() {
assert_eq!(size_of::<Level>(), 1);
assert_eq!(size_of::<LevelFilter>(), 1);
assert_eq!(LevelFilter::Off as u8, 0);
assert_eq!(LevelFilter::Trace as u8, 1);
assert_eq!(LevelFilter::Debug as u8, 2);
assert_eq!(LevelFilter::Info as u8, 3);
assert_eq!(LevelFilter::Warn as u8, 4);
assert_eq!(LevelFilter::Error as u8, 5);
assert_eq!(Level::Trace as u8, 1);
assert_eq!(Level::Debug as u8, 2);
assert_eq!(Level::Info as u8, 3);
assert_eq!(Level::Warn as u8, 4);
assert_eq!(Level::Error as u8, 5);
assert!(!LevelFilter::Off.allows(Level::Error));
assert!(LevelFilter::Info.allows(Level::Error));
assert!(LevelFilter::Info.allows(Level::Info));
+13 -33
View File
@@ -14,16 +14,16 @@ extern crate self as kaptan_log;
#[cfg(test)]
extern crate std;
pub mod facade;
mod facade;
mod hub;
pub mod level;
pub mod report;
pub mod text;
mod level;
mod report;
mod text;
pub use hub::{LogSinkQueue, PublishOutcome, TransportAdmission};
pub use hub::{LogSinkQueue, TransportAdmission};
pub use level::{Level, LevelFilter};
pub use report::{DeliveryClass, LogFlags, LogRecord};
pub use text::{LogText, MAXIMUM_LOG_TEXT_BYTES};
pub use report::{DeliveryClass, LogRecord};
pub use text::LogText;
pub use facade::drain_one;
#[doc(hidden)]
@@ -33,11 +33,7 @@ pub use facade::publish as __publish;
#[macro_export]
macro_rules! debug {
($($argument:tt)+) => {
$crate::__publish(
$crate::Level::Debug,
$crate::LogFlags::NONE,
core::format_args!($($argument)+),
)
$crate::__publish($crate::Level::Debug, core::format_args!($($argument)+))
};
}
@@ -45,11 +41,7 @@ macro_rules! debug {
#[macro_export]
macro_rules! info {
($($argument:tt)+) => {
$crate::__publish(
$crate::Level::Info,
$crate::LogFlags::NONE,
core::format_args!($($argument)+),
)
$crate::__publish($crate::Level::Info, core::format_args!($($argument)+))
};
}
@@ -57,11 +49,7 @@ macro_rules! info {
#[macro_export]
macro_rules! warn {
($($argument:tt)+) => {
$crate::__publish(
$crate::Level::Warn,
$crate::LogFlags::NONE,
core::format_args!($($argument)+),
)
$crate::__publish($crate::Level::Warn, core::format_args!($($argument)+))
};
}
@@ -69,22 +57,14 @@ macro_rules! warn {
#[macro_export]
macro_rules! error {
($($argument:tt)+) => {
$crate::__publish(
$crate::Level::Error,
$crate::LogFlags::NONE,
core::format_args!($($argument)+),
)
$crate::__publish($crate::Level::Error, core::format_args!($($argument)+))
};
}
/// Emits a trace event, represented as MAVLink Debug plus the trace flag.
/// Emits a trace event.
#[macro_export]
macro_rules! trace {
($($argument:tt)+) => {
$crate::__publish(
$crate::Level::Trace,
$crate::LogFlags::NONE,
core::format_args!($($argument)+),
)
$crate::__publish($crate::Level::Trace, core::format_args!($($argument)+))
};
}
+2 -38
View File
@@ -2,30 +2,7 @@
use crate::{Level, LogText};
/// Flags belonging exclusively to a log record.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct LogFlags(u8);
impl LogFlags {
/// No optional flags.
pub const NONE: Self = Self(0);
/// Message text exceeded its fixed capacity.
pub const TRUNCATED: Self = Self(1 << 1);
/// Raw transport bitmask.
pub const fn bits(self) -> u8 {
self.0
}
/// Adds a bit.
pub const fn with(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
/// Internal transport priority selected from a conventional log level.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DeliveryClass {
/// Error-level logs.
Critical,
@@ -37,21 +14,13 @@ pub enum DeliveryClass {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LogRecord {
level: Level,
flags: LogFlags,
message: LogText,
}
impl LogRecord {
/// Creates one bounded log record at the supplied level.
pub fn new(level: Level, mut flags: LogFlags, message: LogText) -> Self {
if message.is_truncated() {
flags = flags.with(LogFlags::TRUNCATED);
}
Self {
level,
flags,
message,
}
pub(crate) const fn new(level: Level, message: LogText) -> Self {
Self { level, message }
}
/// Conventional log level.
@@ -59,11 +28,6 @@ impl LogRecord {
self.level
}
/// Log-specific flag bitmask.
pub const fn flags(&self) -> LogFlags {
self.flags
}
/// Fixed human-readable message.
pub const fn message(&self) -> &LogText {
&self.message
+10 -25
View File
@@ -3,7 +3,7 @@
use core::fmt::{self, Write};
/// Maximum bytes retained in one log message.
pub const MAXIMUM_LOG_TEXT_BYTES: usize = 160;
pub(crate) const MAXIMUM_LOG_TEXT_BYTES: usize = 160;
/// Transport-neutral bounded log message.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -18,8 +18,7 @@ pub struct LogText {
}
impl LogText {
/// Creates empty fixed text storage.
pub const fn new() -> Self {
const fn new() -> Self {
Self {
bytes: [0; MAXIMUM_LOG_TEXT_BYTES],
length: 0,
@@ -28,14 +27,14 @@ impl LogText {
}
/// Formats one message without allocation.
pub fn from_arguments(arguments: fmt::Arguments<'_>) -> Self {
pub(crate) fn from_arguments(arguments: fmt::Arguments<'_>) -> Self {
let mut text = Self::new();
let _ = fmt::write(&mut text, arguments);
text
}
/// Copies UTF-8 text without splitting a code point at the fixed boundary.
pub fn from_message(message: &str) -> Self {
pub(crate) fn from_message(message: &str) -> Self {
let mut text = Self::new();
let _ = text.write_str(message);
text
@@ -47,32 +46,14 @@ impl LogText {
}
/// Number of meaningful UTF-8 bytes.
pub const fn len(&self) -> u8 {
pub const fn byte_length(&self) -> u8 {
self.length
}
/// Whether this message contains no text.
pub const fn is_empty(&self) -> bool {
self.length == 0
}
/// Whether the input exceeded fixed storage.
pub const fn is_truncated(&self) -> bool {
self.truncated != 0
}
/// Returns the valid UTF-8 prefix.
pub fn as_str(&self) -> &str {
// `write_str` copies only complete code points from an input `&str`.
// SAFETY: every populated byte belongs to that valid UTF-8 prefix.
unsafe { core::str::from_utf8_unchecked(&self.bytes[..usize::from(self.length)]) }
}
}
impl Default for LogText {
fn default() -> Self {
Self::new()
}
}
impl fmt::Write for LogText {
@@ -108,6 +89,10 @@ mod tests {
let input = "x".repeat(MAXIMUM_LOG_TEXT_BYTES - 1) + "é";
let text = LogText::from_message(&input);
assert!(text.is_truncated());
assert_eq!(text.as_str().len(), MAXIMUM_LOG_TEXT_BYTES - 1);
let retained = &text.bytes()[..usize::from(text.byte_length())];
assert_eq!(
core::str::from_utf8(retained).unwrap().len(),
MAXIMUM_LOG_TEXT_BYTES - 1
);
}
}