1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4extern crate core;
5
6#[doc(hidden)]
7pub mod byte_log;
8#[cfg(feature = "std")]
9pub mod memmap;
10pub mod noop;
11
12#[cfg(feature = "std")]
13mod compat {
14 pub use crate::memmap::LogPosition;
16 pub use crate::memmap::MmapUnifiedLogger as UnifiedLogger;
17 pub use crate::memmap::MmapUnifiedLoggerBuilder as UnifiedLoggerBuilder;
18 pub use crate::memmap::MmapUnifiedLoggerRead as UnifiedLoggerRead;
19 pub use crate::memmap::MmapUnifiedLoggerWrite as UnifiedLoggerWrite;
20 pub use crate::memmap::UnifiedLoggerIOReader;
21}
22
23#[cfg(feature = "std")]
24pub use compat::*;
25pub use noop::{NoopLogger, NoopSectionStorage};
26
27use alloc::string::ToString;
28#[cfg(not(feature = "std"))]
29use alloc::sync::Arc;
30use alloc::vec::Vec;
31use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
32#[cfg(not(feature = "std"))]
33use spin::Mutex;
34#[cfg(feature = "std")]
35use std::sync::{Arc, Mutex};
36
37use bincode::error::EncodeError;
38use bincode::{Decode, Encode};
39use cu29_traits::{CuError, CuResult, UnifiedLogType, WriteStream};
40
41#[allow(dead_code)]
43pub const MAIN_MAGIC: [u8; 4] = [0xB4, 0xA5, 0x50, 0xFF]; pub const SECTION_MAGIC: [u8; 2] = [0xFA, 0x57]; pub const UNIFIED_LOG_FORMAT_VERSION: u8 = 2;
57
58pub const SECTION_HEADER_COMPACT_SIZE: u16 = 512; #[derive(Encode, Decode, Debug, Clone)]
62pub struct MainHeader {
63 pub magic: [u8; 4],
64 pub format_version: u8,
65 pub page_size: u16,
66 pub metadata_offset: u64,
67 pub sections_begin: u64,
68 pub sections_end: u64,
69 pub head_section: u64,
70 pub tail_section: u64,
71 pub clean_close: bool,
72}
73
74impl Display for MainHeader {
75 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
76 write!(
77 f,
78 " format_version -> {}\n alignment -> {}\n metadata -> {}\n data -> {}..{}\n head -> {}\n tail -> {}\n clean_close -> {}",
79 self.format_version,
80 self.page_size,
81 self.metadata_offset,
82 self.sections_begin,
83 self.sections_end,
84 self.head_section,
85 self.tail_section,
86 self.clean_close
87 )
88 }
89}
90
91#[derive(Encode, Decode, Debug, Clone, Copy, Default, PartialEq, Eq)]
93pub struct SectionContext {
94 pub run_id: u64,
95 pub instance_id: u32,
96 pub mission_index: u32,
97}
98
99#[derive(Encode, Decode, Debug, Clone)]
101pub struct SectionHeader {
102 pub magic: [u8; 2],
103 pub block_size: u16,
104 pub entry_type: UnifiedLogType,
105 pub allocated: u64,
106 pub next_section: u64,
107 pub used: u32,
108 pub is_open: bool,
109 pub context: SectionContext,
110}
111
112impl Display for SectionHeader {
113 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
114 write!(
115 f,
116 " type -> {:?}\n use -> {} / {} (open: {})\n next -> {}\n context -> {:?}",
117 self.entry_type,
118 self.used,
119 self.allocated,
120 self.is_open,
121 self.next_section,
122 self.context
123 )
124 }
125}
126
127impl Default for SectionHeader {
128 fn default() -> Self {
129 Self {
130 magic: SECTION_MAGIC,
131 block_size: SECTION_HEADER_COMPACT_SIZE,
132 entry_type: UnifiedLogType::Empty,
133 allocated: 0,
134 next_section: 0,
135 used: 0,
136 is_open: true,
137 context: SectionContext::default(),
138 }
139 }
140}
141
142#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq)]
144pub struct ApplicationMetadata {
145 pub app_type: alloc::string::String,
146 pub app_name: alloc::string::String,
147 pub app_version: alloc::string::String,
148 pub git_commit: Option<alloc::string::String>,
149 pub git_dirty: Option<bool>,
150 pub subsystem_id: Option<alloc::string::String>,
151 pub subsystem_code: u16,
152 pub effective_config_ron: alloc::string::String,
153 pub missions: Vec<alloc::string::String>,
154 pub catalog_offset: u64,
155}
156
157#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
159pub enum CapacityPolicy {
160 #[default]
161 Grow,
162 OverwriteOldest,
163 StopWhenFull,
164}
165
166pub enum AllocatedSection<S: SectionStorage> {
167 NoMoreSpace,
168 Section(SectionHandle<S>),
169}
170
171pub trait SectionStorage: Send + Sync {
173 fn initialize<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError>;
175 fn post_update_header<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError>;
177 fn append<E: Encode>(&mut self, entry: &E) -> Result<usize, EncodeError>;
179 fn is_sealed(&self) -> bool {
181 false
182 }
183 fn flush(&mut self) -> CuResult<usize>;
185}
186
187#[derive(Default)]
190pub struct SectionHandle<S: SectionStorage> {
191 header: SectionHeader, storage: S,
193}
194
195impl<S: SectionStorage> SectionHandle<S> {
196 pub fn create(header: SectionHeader, mut storage: S) -> CuResult<Self> {
197 let _ = storage.initialize(&header).map_err(|e| e.to_string())?;
199 Ok(Self { header, storage })
200 }
201
202 pub fn mark_closed(&mut self) {
203 self.header.is_open = false;
204 }
205 pub fn append<E: Encode>(&mut self, entry: E) -> Result<usize, EncodeError> {
206 self.storage.append(&entry)
207 }
208
209 pub fn get_storage(&self) -> &S {
210 &self.storage
211 }
212
213 pub fn get_storage_mut(&mut self) -> &mut S {
214 &mut self.storage
215 }
216
217 pub fn post_update_header(&mut self) -> Result<usize, EncodeError> {
218 self.storage.post_update_header(&self.header)
219 }
220}
221
222pub struct UnifiedLogStatus {
225 pub total_used_space: usize,
227 pub total_allocated_space: usize,
229}
230
231#[derive(Encode, Decode, Debug, Clone)]
233pub struct EndOfLogMarker {
234 pub temporary: bool,
235}
236
237pub trait UnifiedLogWrite<S: SectionStorage>: Send + Sync {
241 fn add_section(
247 &mut self,
248 entry_type: UnifiedLogType,
249 requested_section_size: usize,
250 ) -> CuResult<SectionHandle<S>>;
251
252 fn flush_section(&mut self, section: &mut SectionHandle<S>);
254
255 fn try_flush_section(&mut self, section: &mut SectionHandle<S>) -> CuResult<()> {
257 self.flush_section(section);
258 Ok(())
259 }
260
261 #[doc(hidden)]
263 fn seal_metadata<C: Encode>(
264 &mut self,
265 _metadata: &ApplicationMetadata,
266 _catalog: Option<&C>,
267 ) -> CuResult<()> {
268 Err(CuError::from(
269 "Logger does not support application metadata",
270 ))
271 }
272 #[doc(hidden)]
274 fn construction_context(
275 &mut self,
276 instance_id: u32,
277 mission_index: u32,
278 ) -> CuResult<SectionContext> {
279 Ok(SectionContext {
280 run_id: 0,
281 instance_id,
282 mission_index,
283 })
284 }
285 #[doc(hidden)]
287 fn add_section_with_context(
288 &mut self,
289 kind: UnifiedLogType,
290 size: usize,
291 _context: SectionContext,
292 ) -> CuResult<SectionHandle<S>> {
293 self.add_section(kind, size)
294 }
295 fn status(&self) -> UnifiedLogStatus;
297}
298
299pub trait UnifiedLogRead {
301 fn read_next_section_type(&mut self, datalogtype: UnifiedLogType) -> CuResult<Option<Vec<u8>>>;
304
305 fn raw_read_section(&mut self) -> CuResult<(SectionHeader, Vec<u8>)>;
309}
310
311pub fn stream_write<E: Encode, S: SectionStorage>(
313 logger: Arc<Mutex<impl UnifiedLogWrite<S>>>,
314 entry_type: UnifiedLogType,
315 minimum_allocation_amount: usize,
316) -> CuResult<impl WriteStream<E>> {
317 LogStream::new(entry_type, logger, minimum_allocation_amount)
318}
319
320#[doc(hidden)]
322pub fn stream_write_context<E: Encode, S: SectionStorage>(
323 logger: Arc<Mutex<impl UnifiedLogWrite<S>>>,
324 entry_type: UnifiedLogType,
325 size: usize,
326 context: SectionContext,
327) -> CuResult<impl WriteStream<E>> {
328 LogStream::with_context(entry_type, logger, size, context)
329}
330
331pub struct LogStream<S: SectionStorage, L: UnifiedLogWrite<S>> {
333 entry_type: UnifiedLogType,
334 parent_logger: Arc<Mutex<L>>,
335 current_section: SectionHandle<S>,
336 current_position: usize,
337 minimum_allocation_amount: usize,
338 last_log_bytes: usize,
339 context: SectionContext,
340}
341
342impl<S: SectionStorage, L: UnifiedLogWrite<S>> LogStream<S, L> {
343 pub fn new(
345 entry_type: UnifiedLogType,
346 parent_logger: Arc<Mutex<L>>,
347 minimum_allocation_amount: usize,
348 ) -> CuResult<Self> {
349 Self::with_context(
350 entry_type,
351 parent_logger,
352 minimum_allocation_amount,
353 SectionContext::default(),
354 )
355 }
356
357 #[doc(hidden)]
358 pub fn with_context(
359 entry_type: UnifiedLogType,
360 parent_logger: Arc<Mutex<L>>,
361 minimum_allocation_amount: usize,
362 context: SectionContext,
363 ) -> CuResult<Self> {
364 #[cfg(feature = "std")]
365 let section = parent_logger
366 .lock()
367 .map_err(|e| {
368 CuError::from("Could not lock a section at LogStream creation")
369 .add_cause(e.to_string().as_str())
370 })?
371 .add_section_with_context(entry_type, minimum_allocation_amount, context)?;
372
373 #[cfg(not(feature = "std"))]
374 let section = parent_logger.lock().add_section_with_context(
375 entry_type,
376 minimum_allocation_amount,
377 context,
378 )?;
379
380 Ok(Self {
381 entry_type,
382 parent_logger,
383 current_section: section,
384 current_position: 0,
385 minimum_allocation_amount,
386 last_log_bytes: 0,
387 context,
388 })
389 }
390}
391
392impl<S: SectionStorage, L: UnifiedLogWrite<S>> Debug for LogStream<S, L> {
393 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
394 write!(
395 f,
396 "MmapStream {{ entry_type: {:?}, current_position: {}, minimum_allocation_amount: {} }}",
397 self.entry_type, self.current_position, self.minimum_allocation_amount
398 )
399 }
400}
401
402impl<E: Encode, S: SectionStorage, L: UnifiedLogWrite<S>> WriteStream<E> for LogStream<S, L> {
403 fn log(&mut self, obj: &E) -> CuResult<()> {
404 let sealed = self.current_section.storage.is_sealed();
405 let result = if sealed {
406 Err(EncodeError::UnexpectedEnd)
407 } else {
408 self.current_section.append(obj)
409 };
410 match result {
411 Ok(nb_bytes) => {
412 self.current_position += nb_bytes;
413 self.current_section.header.used += nb_bytes as u32;
414 self.last_log_bytes = nb_bytes;
415 if self.entry_type == UnifiedLogType::RuntimeLifecycle {
417 self.current_section.storage.flush()?;
418 }
419 Ok(())
420 }
421 Err(e) => match e {
422 EncodeError::UnexpectedEnd => {
423 if !sealed && self.current_section.header.used == 0 {
424 return Err(CuError::from("Entry exceeds an empty section"));
425 }
426 #[cfg(feature = "std")]
427 let logger_guard = self.parent_logger.lock();
428
429 #[cfg(not(feature = "std"))]
430 let mut logger_guard = self.parent_logger.lock();
431
432 #[cfg(feature = "std")]
433 let mut logger_guard =
434 match logger_guard {
435 Ok(g) => g,
436 Err(_) => return Err(
437 "Logger mutex poisoned while reporting EncodeError::UnexpectedEnd"
438 .into(),
439 ), };
441
442 logger_guard.try_flush_section(&mut self.current_section)?;
443 self.current_section = logger_guard.add_section_with_context(
444 self.entry_type,
445 self.minimum_allocation_amount,
446 self.context,
447 )?;
448
449 let result = self
450 .current_section
451 .append(obj)
452 .map_err(|e| {
453 CuError::from(
454 "Failed to encode object in a newly minted section. Unrecoverable failure.",
455 )
456 .add_cause(e.to_string().as_str())
457 })?; self.current_position += result;
460 self.current_section.header.used += result as u32;
461 self.last_log_bytes = result;
462 if self.entry_type == UnifiedLogType::RuntimeLifecycle {
463 self.current_section.storage.flush()?;
464 }
465 Ok(())
466 }
467 _ => {
468 let err =
469 <&str as Into<CuError>>::into("Unexpected error while encoding object.")
470 .add_cause(e.to_string().as_str());
471 Err(err)
472 }
473 },
474 }
475 }
476
477 fn flush(&mut self) -> CuResult<()> {
478 #[cfg(feature = "std")]
479 let mut logger = self
480 .parent_logger
481 .lock()
482 .map_err(|_| CuError::from("Logger mutex poisoned"))?;
483 #[cfg(not(feature = "std"))]
484 let mut logger = self.parent_logger.lock();
485 logger.try_flush_section(&mut self.current_section)
486 }
487
488 fn last_log_bytes(&self) -> Option<usize> {
489 Some(self.last_log_bytes)
490 }
491}
492
493impl<S: SectionStorage, L: UnifiedLogWrite<S>> Drop for LogStream<S, L> {
494 fn drop(&mut self) {
495 #[cfg(feature = "std")]
496 match self.parent_logger.lock() {
497 Ok(mut logger_guard) => {
498 logger_guard.flush_section(&mut self.current_section);
499 }
500 Err(_) => {
501 if !std::thread::panicking() {
503 eprintln!("⚠️ MmapStream::drop: logger mutex poisoned");
504 }
505 }
506 }
507
508 #[cfg(not(feature = "std"))]
509 {
510 let mut logger_guard = self.parent_logger.lock();
511 logger_guard.flush_section(&mut self.current_section);
512 }
513 }
514}