1mod fsck;
2pub mod logstats;
3
4#[cfg(feature = "mcap")]
5pub mod mcap_export;
6
7#[cfg(feature = "mcap")]
8pub mod serde_to_jsonschema;
9
10use bincode::Decode;
11use bincode::config::standard;
12use bincode::decode_from_std_read;
13use bincode::error::DecodeError;
14use clap::{Parser, Subcommand, ValueEnum};
15use cu29::UnifiedLogType;
16use cu29::prelude::*;
17use cu29_intern_strs::read_interned_strings;
18use fsck::check;
19#[cfg(feature = "mcap")]
20use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
21use logstats::{compute_logstats, write_logstats};
22use serde::Serialize;
23use std::fmt::{Display, Formatter};
24#[cfg(feature = "mcap")]
25use std::io::IsTerminal;
26use std::io::Read;
27use std::path::{Path, PathBuf};
28
29#[cfg(feature = "mcap")]
30pub use mcap_export::{
31 McapExportStats, PayloadSchemas, export_to_mcap, export_to_mcap_with_schemas, mcap_info,
32};
33
34#[cfg(feature = "mcap")]
35pub use serde_to_jsonschema::trace_type_to_jsonschema;
36
37#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
38pub enum ExportFormat {
39 Json,
40 Csv,
41}
42
43impl Display for ExportFormat {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 match self {
46 ExportFormat::Json => write!(f, "json"),
47 ExportFormat::Csv => write!(f, "csv"),
48 }
49 }
50}
51
52#[derive(Parser)]
54#[command(author, version, about)]
55pub struct LogReaderCli {
56 pub unifiedlog_base: PathBuf,
59
60 #[command(subcommand)]
61 pub command: Command,
62}
63
64#[derive(Subcommand)]
65pub enum Command {
66 ExtractTextLog { log_index: PathBuf },
68 ExtractCopperlists {
70 #[arg(short, long, default_value_t = ExportFormat::Json)]
71 export_format: ExportFormat,
72 },
73 Fsck {
75 #[arg(short, long, action = clap::ArgAction::Count)]
76 verbose: u8,
77 #[arg(long)]
79 dump_runtime_lifecycle: bool,
80 },
81 LogStats {
83 #[arg(short, long, default_value = "cu29_logstats.json")]
85 output: PathBuf,
86 #[arg(long, default_value = "copperconfig.ron")]
88 config: PathBuf,
89 #[arg(long)]
91 mission: Option<String>,
92 },
93 #[cfg(feature = "mcap")]
95 ExportMcap {
96 #[arg(short, long)]
98 output: PathBuf,
99 #[arg(long)]
101 progress: bool,
102 #[arg(long)]
104 quiet: bool,
105 },
106 #[cfg(feature = "mcap")]
108 McapInfo {
109 mcap_file: PathBuf,
111 #[arg(short, long)]
113 schemas: bool,
114 #[arg(short = 'n', long, default_value_t = 0)]
116 sample_messages: usize,
117 },
118}
119
120fn write_json_pretty<T: Serialize + ?Sized>(value: &T) -> CuResult<()> {
121 serde_json::to_writer_pretty(std::io::stdout(), value)
122 .map_err(|e| CuError::new_with_cause("Failed to write JSON output", e))
123}
124
125fn write_json<T: Serialize + ?Sized>(value: &T) -> CuResult<()> {
126 serde_json::to_writer(std::io::stdout(), value)
127 .map_err(|e| CuError::new_with_cause("Failed to write JSON output", e))
128}
129
130fn build_read_logger(unifiedlog_base: &Path) -> CuResult<UnifiedLoggerRead> {
131 let logger = UnifiedLoggerBuilder::new()
132 .file_base_name(unifiedlog_base)
133 .build()
134 .map_err(|e| CuError::new_with_cause("Failed to create logger", e))?;
135 match logger {
136 UnifiedLogger::Read(dl) => Ok(dl),
137 UnifiedLogger::Write(_) => Err(CuError::from(
138 "Expected read-only unified logger in export CLI",
139 )),
140 }
141}
142
143#[cfg(feature = "mcap")]
148pub fn run_cli<P>() -> CuResult<()>
149where
150 P: CopperListTuple + CuPayloadRawBytes + mcap_export::PayloadSchemas,
151{
152 run_cli_inner::<P>()
153}
154
155#[cfg(not(feature = "mcap"))]
158pub fn run_cli<P>() -> CuResult<()>
159where
160 P: CopperListTuple + CuPayloadRawBytes,
161{
162 run_cli_inner::<P>()
163}
164
165#[cfg(feature = "mcap")]
166fn run_cli_inner<P>() -> CuResult<()>
167where
168 P: CopperListTuple + CuPayloadRawBytes + mcap_export::PayloadSchemas,
169{
170 let args = LogReaderCli::parse();
171 let unifiedlog_base = args.unifiedlog_base;
172
173 let mut dl = build_read_logger(&unifiedlog_base)?;
174
175 match args.command {
176 Command::ExtractTextLog { log_index } => {
177 let reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::StructuredLogLine);
178 textlog_dump(reader, &log_index)?;
179 }
180 Command::ExtractCopperlists { export_format } => {
181 println!("Extracting copperlists with format: {export_format}");
182 let mut reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::CopperList);
183 let iter = copperlists_reader::<P>(&mut reader);
184
185 match export_format {
186 ExportFormat::Json => {
187 for entry in iter {
188 write_json_pretty(&entry)?;
189 }
190 }
191 ExportFormat::Csv => {
192 let mut first = true;
193 for origin in P::get_all_task_ids() {
194 if !first {
195 print!(", ");
196 } else {
197 print!("id, ");
198 }
199 print!("{origin}_time, {origin}_tov, {origin},");
200 first = false;
201 }
202 println!();
203 for entry in iter {
204 let mut first = true;
205 for msg in entry.cumsgs() {
206 if let Some(payload) = msg.payload() {
207 if !first {
208 print!(", ");
209 } else {
210 print!("{}, ", entry.id);
211 }
212 let metadata = msg.metadata();
213 print!("{}, {}, ", metadata.process_time(), msg.tov());
214 write_json(payload)?; first = false;
216 }
217 }
218 println!();
219 }
220 }
221 }
222 }
223 Command::Fsck {
224 verbose,
225 dump_runtime_lifecycle,
226 } => {
227 if let Some(value) = check::<P>(&mut dl, verbose, dump_runtime_lifecycle) {
228 return value;
229 }
230 }
231 Command::LogStats {
232 output,
233 config,
234 mission,
235 } => {
236 run_logstats::<P>(dl, output, config, mission)?;
237 }
238 #[cfg(feature = "mcap")]
239 Command::ExportMcap {
240 output,
241 progress,
242 quiet,
243 } => {
244 println!("Exporting copperlists to MCAP format: {}", output.display());
245
246 let show_progress = should_show_progress(progress, quiet);
247 let total_bytes = if show_progress {
248 Some(copperlist_total_bytes(&unifiedlog_base)?)
249 } else {
250 None
251 };
252
253 let reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::CopperList);
254
255 let stats = if let Some(total_bytes) = total_bytes {
258 let progress_bar = make_progress_bar(total_bytes);
259 let reader = ProgressReader::new(reader, progress_bar.clone());
260 let result = export_to_mcap_impl::<P>(reader, &output);
261 progress_bar.finish_and_clear();
262 result?
263 } else {
264 export_to_mcap_impl::<P>(reader, &output)?
265 };
266 println!("{stats}");
267 }
268 #[cfg(feature = "mcap")]
269 Command::McapInfo {
270 mcap_file,
271 schemas,
272 sample_messages,
273 } => {
274 mcap_info(&mcap_file, schemas, sample_messages)?;
275 }
276 }
277
278 Ok(())
279}
280
281#[cfg(not(feature = "mcap"))]
282fn run_cli_inner<P>() -> CuResult<()>
283where
284 P: CopperListTuple + CuPayloadRawBytes,
285{
286 let args = LogReaderCli::parse();
287 let unifiedlog_base = args.unifiedlog_base;
288
289 let mut dl = build_read_logger(&unifiedlog_base)?;
290
291 match args.command {
292 Command::ExtractTextLog { log_index } => {
293 let reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::StructuredLogLine);
294 textlog_dump(reader, &log_index)?;
295 }
296 Command::ExtractCopperlists { export_format } => {
297 println!("Extracting copperlists with format: {export_format}");
298 let mut reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::CopperList);
299 let iter = copperlists_reader::<P>(&mut reader);
300
301 match export_format {
302 ExportFormat::Json => {
303 for entry in iter {
304 write_json_pretty(&entry)?;
305 }
306 }
307 ExportFormat::Csv => {
308 let mut first = true;
309 for origin in P::get_all_task_ids() {
310 if !first {
311 print!(", ");
312 } else {
313 print!("id, ");
314 }
315 print!("{origin}_time, {origin}_tov, {origin},");
316 first = false;
317 }
318 println!();
319 for entry in iter {
320 let mut first = true;
321 for msg in entry.cumsgs() {
322 if let Some(payload) = msg.payload() {
323 if !first {
324 print!(", ");
325 } else {
326 print!("{}, ", entry.id);
327 }
328 let metadata = msg.metadata();
329 print!("{}, {}, ", metadata.process_time(), msg.tov());
330 write_json(payload)?;
331 first = false;
332 }
333 }
334 println!();
335 }
336 }
337 }
338 }
339 Command::Fsck {
340 verbose,
341 dump_runtime_lifecycle,
342 } => {
343 if let Some(value) = check::<P>(&mut dl, verbose, dump_runtime_lifecycle) {
344 return value;
345 }
346 }
347 Command::LogStats {
348 output,
349 config,
350 mission,
351 } => {
352 run_logstats::<P>(dl, output, config, mission)?;
353 }
354 }
355
356 Ok(())
357}
358
359fn run_logstats<P>(
360 dl: UnifiedLoggerRead,
361 output: PathBuf,
362 config: PathBuf,
363 mission: Option<String>,
364) -> CuResult<()>
365where
366 P: CopperListTuple + CuPayloadRawBytes,
367{
368 let config_path = config
369 .to_str()
370 .ok_or_else(|| CuError::from("Config path is not valid UTF-8"))?;
371 let cfg = read_configuration(config_path)
372 .map_err(|e| CuError::new_with_cause("Failed to read configuration", e))?;
373 let reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::CopperList);
374 let stats = compute_logstats::<P>(reader, &cfg, mission.as_deref())?;
375 write_logstats(&stats, &output)
376}
377
378#[cfg(feature = "mcap")]
382fn export_to_mcap_impl<P>(src: impl Read, output: &Path) -> CuResult<McapExportStats>
383where
384 P: CopperListTuple + mcap_export::PayloadSchemas,
385{
386 mcap_export::export_to_mcap::<P, _>(src, output)
387}
388
389#[cfg(feature = "mcap")]
390struct ProgressReader<R> {
391 inner: R,
392 progress: ProgressBar,
393}
394
395#[cfg(feature = "mcap")]
396impl<R> ProgressReader<R> {
397 fn new(inner: R, progress: ProgressBar) -> Self {
398 Self { inner, progress }
399 }
400}
401
402#[cfg(feature = "mcap")]
403impl<R: Read> Read for ProgressReader<R> {
404 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
405 let read = self.inner.read(buf)?;
406 if read > 0 {
407 self.progress.inc(read as u64);
408 }
409 Ok(read)
410 }
411}
412
413#[cfg(feature = "mcap")]
414fn make_progress_bar(total_bytes: u64) -> ProgressBar {
415 let progress_bar = ProgressBar::new(total_bytes);
416 progress_bar.set_draw_target(ProgressDrawTarget::stderr_with_hz(5));
417
418 let style = ProgressStyle::with_template(
419 "[{elapsed_precise}] {bar:40} {bytes}/{total_bytes} ({bytes_per_sec}, ETA {eta})",
420 )
421 .unwrap_or_else(|_| ProgressStyle::default_bar());
422
423 progress_bar.set_style(style.progress_chars("=>-"));
424 progress_bar
425}
426
427#[cfg(feature = "mcap")]
428fn should_show_progress(force_progress: bool, quiet: bool) -> bool {
429 !quiet && (force_progress || std::io::stderr().is_terminal())
430}
431
432#[cfg(feature = "mcap")]
433fn copperlist_total_bytes(log_base: &Path) -> CuResult<u64> {
434 let mut reader = UnifiedLoggerRead::new(log_base)
435 .map_err(|e| CuError::new_with_cause("Failed to open log for progress estimation", e))?;
436 reader
437 .scan_section_bytes(UnifiedLogType::CopperList)
438 .map_err(|e| CuError::new_with_cause("Failed to scan log for progress estimation", e))
439}
440
441fn read_next_entry<T: Decode<()>>(src: &mut impl Read) -> Option<T> {
442 let entry = decode_from_std_read::<T, _, _>(src, standard());
443 match entry {
444 Ok(entry) => Some(entry),
445 Err(DecodeError::UnexpectedEnd { .. }) => None,
446 Err(DecodeError::Io { inner, additional }) => {
447 if inner.kind() == std::io::ErrorKind::UnexpectedEof {
448 None
449 } else {
450 println!("Error {inner:?} additional:{additional}");
451 None
452 }
453 }
454 Err(e) => {
455 println!("Error {e:?}");
456 None
457 }
458 }
459}
460
461pub fn copperlists_reader<P: CopperListTuple>(
464 mut src: impl Read,
465) -> impl Iterator<Item = CopperList<P>> {
466 std::iter::from_fn(move || read_next_entry::<CopperList<P>>(&mut src))
467}
468
469pub fn keyframes_reader(mut src: impl Read) -> impl Iterator<Item = KeyFrame> {
471 std::iter::from_fn(move || read_next_entry::<KeyFrame>(&mut src))
472}
473
474pub fn structlog_reader(mut src: impl Read) -> impl Iterator<Item = CuResult<CuLogEntry>> {
475 std::iter::from_fn(move || {
476 let entry = decode_from_std_read::<CuLogEntry, _, _>(&mut src, standard());
477
478 match entry {
479 Err(DecodeError::UnexpectedEnd { .. }) => None,
480 Err(DecodeError::Io {
481 inner,
482 additional: _,
483 }) => {
484 if inner.kind() == std::io::ErrorKind::UnexpectedEof {
485 None
486 } else {
487 Some(Err(CuError::new_with_cause("Error reading log", inner)))
488 }
489 }
490 Err(e) => Some(Err(CuError::new_with_cause("Error reading log", e))),
491 Ok(entry) => {
492 if entry.msg_index == 0 {
493 None
494 } else {
495 Some(Ok(entry))
496 }
497 }
498 }
499 })
500}
501
502pub fn textlog_dump(src: impl Read, index: &Path) -> CuResult<()> {
507 let all_strings = read_interned_strings(index).map_err(|e| {
508 CuError::new_with_cause(
509 "Failed to read interned strings from index",
510 std::io::Error::other(e),
511 )
512 })?;
513
514 for result in structlog_reader(src) {
515 match result {
516 Ok(entry) => match rebuild_logline(&all_strings, &entry) {
517 Ok(line) => println!("{line}"),
518 Err(e) => println!("Failed to rebuild log line: {e:?}"),
519 },
520 Err(e) => return Err(e),
521 }
522 }
523
524 Ok(())
525}
526
527#[cfg(all(feature = "python", not(target_os = "macos")))]
529mod python {
530 use bincode::config::standard;
531 use bincode::decode_from_std_read;
532 use bincode::error::DecodeError;
533 use cu29::prelude::*;
534 use cu29_intern_strs::read_interned_strings;
535 use pyo3::exceptions::PyIOError;
536 use pyo3::prelude::*;
537 use pyo3::types::{PyDelta, PyDict, PyList};
538 use std::io::Read;
539 use std::path::Path;
540
541 #[pyclass]
542 pub struct PyLogIterator {
543 reader: Box<dyn Read + Send + Sync>,
544 }
545
546 #[pymethods]
547 impl PyLogIterator {
548 fn __iter__(slf: PyRefMut<Self>) -> PyRefMut<Self> {
549 slf
550 }
551
552 fn __next__(mut slf: PyRefMut<Self>) -> Option<PyResult<PyCuLogEntry>> {
553 match decode_from_std_read::<CuLogEntry, _, _>(&mut slf.reader, standard()) {
554 Ok(entry) => {
555 if entry.msg_index == 0 {
556 None
557 } else {
558 Some(Ok(PyCuLogEntry { inner: entry }))
559 }
560 }
561 Err(DecodeError::UnexpectedEnd { .. }) => None,
562 Err(DecodeError::Io { inner, .. })
563 if inner.kind() == std::io::ErrorKind::UnexpectedEof =>
564 {
565 None
566 }
567 Err(e) => Some(Err(PyIOError::new_err(e.to_string()))),
568 }
569 }
570 }
571
572 #[pyfunction]
576 pub fn struct_log_iterator_bare(
577 bare_struct_src_path: &str,
578 index_path: &str,
579 ) -> PyResult<(PyLogIterator, Vec<String>)> {
580 let file = std::fs::File::open(bare_struct_src_path)
581 .map_err(|e| PyIOError::new_err(e.to_string()))?;
582 let all_strings = read_interned_strings(Path::new(index_path))
583 .map_err(|e| PyIOError::new_err(e.to_string()))?;
584 Ok((
585 PyLogIterator {
586 reader: Box::new(file),
587 },
588 all_strings,
589 ))
590 }
591 #[pyfunction]
595 pub fn struct_log_iterator_unified(
596 unified_src_path: &str,
597 index_path: &str,
598 ) -> PyResult<(PyLogIterator, Vec<String>)> {
599 let all_strings = read_interned_strings(Path::new(index_path))
600 .map_err(|e| PyIOError::new_err(e.to_string()))?;
601
602 let logger = UnifiedLoggerBuilder::new()
603 .file_base_name(Path::new(unified_src_path))
604 .build()
605 .map_err(|e| PyIOError::new_err(e.to_string()))?;
606 let dl = match logger {
607 UnifiedLogger::Read(dl) => dl,
608 UnifiedLogger::Write(_) => {
609 return Err(PyIOError::new_err(
610 "Expected read-only unified logger for Python export",
611 ));
612 }
613 };
614
615 let reader = UnifiedLoggerIOReader::new(dl, UnifiedLogType::StructuredLogLine);
616 Ok((
617 PyLogIterator {
618 reader: Box::new(reader),
619 },
620 all_strings,
621 ))
622 }
623
624 #[pyclass]
626 pub struct PyCuLogEntry {
627 pub inner: CuLogEntry,
628 }
629
630 #[pymethods]
631 impl PyCuLogEntry {
632 pub fn ts<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyDelta>> {
634 let nanoseconds: u64 = self.inner.time.into();
635
636 let days = (nanoseconds / 86_400_000_000_000) as i32;
638 let seconds = (nanoseconds / 1_000_000_000) as i32;
639 let microseconds = ((nanoseconds % 1_000_000_000) / 1_000) as i32;
640
641 PyDelta::new(py, days, seconds, microseconds, false)
642 }
643
644 pub fn msg_index(&self) -> u32 {
646 self.inner.msg_index
647 }
648
649 pub fn paramname_indexes(&self) -> Vec<u32> {
651 self.inner.paramname_indexes.iter().copied().collect()
652 }
653
654 pub fn params(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
656 self.inner
657 .params
658 .iter()
659 .map(|value| value_to_py(value, py))
660 .collect()
661 }
662 }
663
664 #[pymodule(name = "libcu29_export")]
666 fn cu29_export(m: &Bound<'_, PyModule>) -> PyResult<()> {
667 m.add_class::<PyCuLogEntry>()?;
668 m.add_class::<PyLogIterator>()?;
669 m.add_function(wrap_pyfunction!(struct_log_iterator_bare, m)?)?;
670 m.add_function(wrap_pyfunction!(struct_log_iterator_unified, m)?)?;
671 Ok(())
672 }
673
674 fn value_to_py(value: &cu29::prelude::Value, py: Python<'_>) -> PyResult<Py<PyAny>> {
675 match value {
676 Value::String(s) => Ok(s.into_pyobject(py)?.into()),
677 Value::U64(u) => Ok(u.into_pyobject(py)?.into()),
678 Value::I64(i) => Ok(i.into_pyobject(py)?.into()),
679 Value::F64(f) => Ok(f.into_pyobject(py)?.into()),
680 Value::Bool(b) => Ok(b.into_pyobject(py)?.to_owned().into()),
681 Value::CuTime(t) => Ok(t.0.into_pyobject(py)?.into()),
682 Value::Bytes(b) => Ok(b.into_pyobject(py)?.into()),
683 Value::Char(c) => Ok(c.into_pyobject(py)?.into()),
684 Value::I8(i) => Ok(i.into_pyobject(py)?.into()),
685 Value::U8(u) => Ok(u.into_pyobject(py)?.into()),
686 Value::I16(i) => Ok(i.into_pyobject(py)?.into()),
687 Value::U16(u) => Ok(u.into_pyobject(py)?.into()),
688 Value::I32(i) => Ok(i.into_pyobject(py)?.into()),
689 Value::U32(u) => Ok(u.into_pyobject(py)?.into()),
690 Value::Map(m) => {
691 let dict = PyDict::new(py);
692 for (k, v) in m.iter() {
693 dict.set_item(value_to_py(k, py)?, value_to_py(v, py)?)?;
694 }
695 Ok(dict.into_pyobject(py)?.into())
696 }
697 Value::F32(f) => Ok(f.into_pyobject(py)?.into()),
698 Value::Option(o) => match o.as_ref() {
699 Some(value) => value_to_py(value, py),
700 None => Ok(py.None()),
701 },
702 Value::Unit => Ok(py.None()),
703 Value::Newtype(v) => value_to_py(v, py),
704 Value::Seq(s) => {
705 let items: Vec<Py<PyAny>> = s
706 .iter()
707 .map(|value| value_to_py(value, py))
708 .collect::<PyResult<_>>()?;
709 let list = PyList::new(py, items)?;
710 Ok(list.into_pyobject(py)?.into())
711 }
712 }
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719 use bincode::{Decode, Encode, encode_into_slice};
720 use serde::Deserialize;
721 use std::env;
722 use std::fs;
723 use std::io::Cursor;
724 use std::path::PathBuf;
725 use std::sync::{Arc, Mutex};
726 use tempfile::{TempDir, tempdir};
727
728 fn copy_stringindex_to_temp(tmpdir: &TempDir) -> PathBuf {
729 let fake_out_dir = tmpdir.path().join("build").join("out").join("dir");
731 fs::create_dir_all(&fake_out_dir).unwrap();
732 unsafe {
734 env::set_var("LOG_INDEX_DIR", &fake_out_dir);
735 }
736
737 let _ = cu29_intern_strs::intern_string("unused to start counter");
739 let _ = cu29_intern_strs::intern_string("Just a String {}");
740 let _ = cu29_intern_strs::intern_string("Just a String (low level) {}");
741 let _ = cu29_intern_strs::intern_string("Just a String (end to end) {}");
742
743 let index_dir = cu29_intern_strs::default_log_index_dir();
744 cu29_intern_strs::read_interned_strings(&index_dir).unwrap();
745 index_dir
746 }
747
748 #[test]
749 fn test_extract_low_level_cu29_log() {
750 let temp_dir = TempDir::new().unwrap();
751 let temp_path = copy_stringindex_to_temp(&temp_dir);
752 let entry = CuLogEntry::new(3, CuLogLevel::Info);
753 let bytes = bincode::encode_to_vec(&entry, standard()).unwrap();
754 let reader = Cursor::new(bytes.as_slice());
755 textlog_dump(reader, temp_path.as_path()).unwrap();
756 }
757
758 #[test]
759 fn end_to_end_datalogger_and_structlog_test() {
760 let dir = tempdir().expect("Failed to create temp dir");
761 let path = dir
762 .path()
763 .join("end_to_end_datalogger_and_structlog_test.copper");
764 {
765 let UnifiedLogger::Write(logger) = UnifiedLoggerBuilder::new()
767 .write(true)
768 .create(true)
769 .file_base_name(&path)
770 .preallocated_size(100000)
771 .build()
772 .expect("Failed to create logger")
773 else {
774 panic!("Failed to create logger")
775 };
776 let data_logger = Arc::new(Mutex::new(logger));
777 let stream = stream_write(data_logger.clone(), UnifiedLogType::StructuredLogLine, 1024)
778 .expect("Failed to create stream");
779 let rt = LoggerRuntime::init(RobotClock::default(), stream, None::<NullLog>);
780
781 let mut entry = CuLogEntry::new(4, CuLogLevel::Info); entry.add_param(0, Value::String("Parameter for the log line".into()));
783 log(&mut entry).expect("Failed to log");
784 let mut entry = CuLogEntry::new(2, CuLogLevel::Info); entry.add_param(0, Value::String("Parameter for the log line".into()));
786 log(&mut entry).expect("Failed to log");
787
788 drop(rt);
790 }
791 let UnifiedLogger::Read(logger) = UnifiedLoggerBuilder::new()
793 .file_base_name(
794 &dir.path()
795 .join("end_to_end_datalogger_and_structlog_test.copper"),
796 )
797 .build()
798 .expect("Failed to create logger")
799 else {
800 panic!("Failed to create logger")
801 };
802 let reader = UnifiedLoggerIOReader::new(logger, UnifiedLogType::StructuredLogLine);
803 let temp_dir = TempDir::new().unwrap();
804 textlog_dump(
805 reader,
806 Path::new(copy_stringindex_to_temp(&temp_dir).as_path()),
807 )
808 .expect("Failed to dump log");
809 }
810
811 #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, Encode, Decode, Default)]
813 struct MyMsgs((u8, i32, f32));
814
815 impl ErasedCuStampedDataSet for MyMsgs {
816 fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
817 Vec::new()
818 }
819 }
820
821 impl MatchingTasks for MyMsgs {
822 fn get_all_task_ids() -> &'static [&'static str] {
823 &[]
824 }
825 }
826
827 #[test]
829 fn test_copperlists_dump() {
830 let mut data = vec![0u8; 10000];
831 let mypls: [MyMsgs; 4] = [
832 MyMsgs((1, 2, 3.0)),
833 MyMsgs((2, 3, 4.0)),
834 MyMsgs((3, 4, 5.0)),
835 MyMsgs((4, 5, 6.0)),
836 ];
837
838 let mut offset: usize = 0;
839 for pl in mypls.iter() {
840 let cl = CopperList::<MyMsgs>::new(1, *pl);
841 offset +=
842 encode_into_slice(&cl, &mut data.as_mut_slice()[offset..], standard()).unwrap();
843 }
844
845 let reader = Cursor::new(data);
846
847 let mut iter = copperlists_reader::<MyMsgs>(reader);
848 assert_eq!(iter.next().unwrap().msgs, MyMsgs((1, 2, 3.0)));
849 assert_eq!(iter.next().unwrap().msgs, MyMsgs((2, 3, 4.0)));
850 assert_eq!(iter.next().unwrap().msgs, MyMsgs((3, 4, 5.0)));
851 assert_eq!(iter.next().unwrap().msgs, MyMsgs((4, 5, 6.0)));
852 }
853}