Skip to main content

Best Practices

Follow these patterns and best practices for effective MCAP logging in Cerulion Core.

1. Use Arc for Sharing Recorders

Always wrap recorders in Arc to share them across multiple publishers/subscribers.
// ✅ Good: Share recorder across multiple publishers/subscribers
let recorder = Arc::new(McapRecorder::create("log.mcap", None, None)?);
publisher1.enable_mcap_logging(recorder.clone());
publisher2.enable_mcap_logging(recorder.clone());
// ❌ Bad: Can't share without Arc
let recorder = McapRecorder::create("log.mcap", None, None)?;
publisher1.enable_mcap_logging(recorder);  // Moves recorder
publisher2.enable_mcap_logging(recorder);  // Error: value moved
Using Arc allows multiple publishers/subscribers to write to the same MCAP file, ensuring all messages are captured in one synchronized recording.

2. Choose Appropriate Compression

Select compression based on your use case:
// ✅ Good: LZ4 for real-time logging
let recorder = Arc::new(
    McapRecorder::create("realtime_log.mcap", Some(Compression::Lz4), None)?
);

// ✅ Good: Zstandard for archival
let recorder = Arc::new(
    McapRecorder::create("archive.mcap", Some(Compression::Zstandard), None)?
);

// ✅ Good: No compression for high-frequency data
let recorder = Arc::new(
    McapRecorder::create("high_freq_log.mcap", None, None)?
);

Compression Decision Matrix

Use CaseRecommendedWhy
Real-time sensor data (>100 Hz)None or LZ4Minimize CPU overhead
Control commands (1-10 Hz)LZ4Good balance
Archival/analysisZstandardBest compression
Network-constrainedZstandardSmaller file transfer

3. Log Only What You Need

Be selective about which topics to log:
// ✅ Good: Log only specific topics
let recorder = Arc::new(McapRecorder::create("important_data.mcap", None, None)?);
important_publisher.enable_mcap_logging(recorder.clone());
critical_publisher.enable_mcap_logging(recorder.clone());
// Don't log debug/verbose topics
// ❌ Bad: Logging everything indiscriminately
let recorder = Arc::new(McapRecorder::create("everything.mcap", None, None)?);
debug_publisher.enable_mcap_logging(recorder.clone());
verbose_publisher.enable_mcap_logging(recorder.clone());
trace_publisher.enable_mcap_logging(recorder.clone());
// Results in huge files with mostly noise
Consider creating separate recorders for different categories: sensors, control, debug, etc. This makes it easier to manage and analyze recordings.

4. Handle File Errors Gracefully

Always handle recorder creation errors:
// ✅ Good: Handle file creation errors
match McapRecorder::create("log.mcap", None, None) {
    Ok(recorder) => {
        let recorder = Arc::new(recorder);
        publisher.enable_mcap_logging(recorder);
        println!("MCAP logging enabled");
    }
    Err(e) => {
        eprintln!("Failed to create MCAP recorder: {}", e);
        // Continue without logging - don't crash the application
    }
}
// ❌ Bad: Unwrap causes panic on error
let recorder = Arc::new(
    McapRecorder::create("log.mcap", None, None).unwrap()  // Panics if file can't be created
);

5. Keep Recorder in Scope

Ensure the recorder lives long enough to capture all messages:
// ✅ Good: Keep recorder in scope
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let recorder = Arc::new(McapRecorder::create("log.mcap", None, None)?);
    
    let mut publisher = Publisher::<SensorData>::create("sensors/data")?;
    publisher.enable_mcap_logging(recorder.clone());
    
    // Send messages
    for i in 0..100 {
        publisher.send(/* data */)?;
    }
    
    // Recorder closes when dropped at end of scope
    Ok(())
}
// ❌ Bad: Recorder dropped too early
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut publisher = Publisher::<SensorData>::create("sensors/data")?;
    
    {
        let recorder = Arc::new(McapRecorder::create("log.mcap", None, None)?);
        publisher.enable_mcap_logging(recorder.clone());
    }  // Recorder dropped here!
    
    // Messages sent here are NOT logged
    for i in 0..100 {
        publisher.send(/* data */)?;
    }
    
    Ok(())
}

Complete Example

Here’s a complete production-ready example with best practices:
use cerulion_core::prelude::*;
use std::sync::Arc;
use std::time::Duration;

#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct SensorData {
    temperature: f32,
    pressure: f32,
    timestamp: u64,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create MCAP recorder with error handling
    let recorder = match McapRecorder::create("sensor_log.mcap", Some(Compression::Lz4), None) {
        Ok(rec) => {
            println!("MCAP logging enabled: sensor_log.mcap");
            Arc::new(rec)
        }
        Err(e) => {
            eprintln!("Warning: Failed to create MCAP recorder: {}", e);
            eprintln!("Continuing without logging...");
            return Ok(());  // Continue without logging
        }
    };
    
    // Create publishers
    let mut temp_publisher = Publisher::<SensorData>::create("sensors/temperature")?;
    let mut pressure_publisher = Publisher::<SensorData>::create("sensors/pressure")?;
    
    // Enable MCAP logging on important topics only
    temp_publisher.enable_mcap_logging(recorder.clone());
    pressure_publisher.enable_mcap_logging(recorder.clone());
    
    // Send messages (automatically logged)
    for i in 0..100 {
        let data = SensorData {
            temperature: 20.0 + (i as f32) * 0.1,
            pressure: 1013.25 + (i as f32) * 0.01,
            timestamp: i as u64,
        };
        
        temp_publisher.send(data)?;
        pressure_publisher.send(data)?;
        
        if i % 10 == 0 {
            println!("Logged {} messages...", i * 2);
        }
        
        std::thread::sleep(Duration::from_millis(100));
    }
    
    println!("Recording complete: 200 messages logged");
    println!("View with: mcap info sensor_log.mcap");
    
    // Recorder automatically closes when dropped
    Ok(())
}

Performance Considerations

High-Frequency Logging

For high-frequency messages (>100 Hz), consider:
// Use no compression for minimal overhead
let recorder = Arc::new(
    McapRecorder::create("high_freq.mcap", None, None)?
);

// Or use LZ4 for moderate compression
let recorder = Arc::new(
    McapRecorder::create("high_freq.mcap", Some(Compression::Lz4), None)?
);

Large Message Sizes

For large messages (>100 KB), consider:
// Use compression to reduce file size
let recorder = Arc::new(
    McapRecorder::create("large_msgs.mcap", Some(Compression::Zstandard), None)?
);

Memory Usage

MCAP logging uses a background thread with buffering:
  • Buffer size: Configurable (default optimized for typical use)
  • Memory overhead: ~1-2 MB per recorder
  • Write latency: Non-blocking, messages queued

Organizing Recordings

By Topic Category

// Separate recorders for different categories
let sensor_recorder = Arc::new(
    McapRecorder::create("sensors.mcap", Some(Compression::Lz4), None)?
);
let control_recorder = Arc::new(
    McapRecorder::create("control.mcap", Some(Compression::Lz4), None)?
);

sensor_pub1.enable_mcap_logging(sensor_recorder.clone());
sensor_pub2.enable_mcap_logging(sensor_recorder.clone());
control_pub.enable_mcap_logging(control_recorder.clone());

By Time Period

use std::time::{Duration, Instant};

// Time-limited recording sessions
let start = Instant::now();
recorder.record_from_subscriber(&subscriber, "sensors/data", || {
    start.elapsed() < Duration::from_secs(60)  // 1-minute recordings
})?;

By File Size

// Create new file every hour or when file size exceeds limit
// (Manual rotation - auto-rotation not yet implemented)
let recorder = Arc::new(
    McapRecorder::create(&format!("log_{}.mcap", timestamp), None, None)?
);

Next Steps