Skip to main content

Logging messages

This page describes the different ways you can log messages to an MCAP file or the Foxglove app.

As described in Concepts, schemas are a way to define the structure of your data. Foxglove defines a number of schemas for common data types which work out of the box with visualization panels. The SDK includes these schemas and will serialize the messages for you.

You'll also likely want to log messages which don't fit into a Foxglove schema. In that case, you can define your own schemas or use schemaless logging as described below.

Logging messages with Foxglove schemas

This section describes logging with Foxglove's defined message schemas. For example, you can log a SceneUpdate message to render on a 3D panel or a Log message to the Log panel.

Here, we'll log a LocationFix message to the /location topic, which can be used by the Map panel.

Using the log! macro:

foxglove::log!(
"/location",
foxglove::schemas::LocationFix {
latitude: 37.7764,
longitude: -122.4216,
..Default::default()
}
);

Using a schema-specific Channel:

static CHANNEL: foxglove::LazyChannel<foxglove::schemas::LocationFix> =
foxglove::LazyChannel::new("/location");

CHANNEL.log(&foxglove::schemas::LocationFix {
latitude: 37.7764,
longitude: -122.4216,
..Default::default()
});

Logging custom data

This section describes logging data which doesn't fit into a Foxglove schema. We'll log custom messages to the /plot topic.

In Rust, you can derive the Encode trait to automatically serialize structs and enums.

#[derive(Debug, foxglove::Encode)]
struct DataPoint {
label: String,
value: u32,
}

foxglove::log!(
"/plot",
&DataPoint {
label: "y".to_string(),
value: 1,
});

You could also use channels as in the previous section, by constructing a LazyChannel<DataPoint>.

For a more complete example, see https://github.com/foxglove/foxglove-sdk/tree/main/rust/examples/mcap

In the next section, we'll have full control over the serialization of our data.

Logging pre-serialized data

In this example, we'll see how to log data which has been pre-serialized from custom protobuf definitions.

Suppose we have the following protobuf definition, and have generated the language-specific code for it following the proto2 or proto3 documentation.

package fruit;

message Apple {
optional string color = 1;
optional int32 diameter = 2;
}

We can construct a channel containing our generated schema, and log serialized protobuf messages to it.

One option for Rust is to generate our code using prost-build and then serialize with prost. Note that prost-build doesn't include the protobuf compiler, so you'll need to follow the instructions to do so.

In our Cargo build script, we'll also have prost-build generate file descriptors so that we can include the schema description in our channel (e.g., for MCAP output).

// build.rs
fn main() {
prost_build::Config::new()
.file_descriptor_set_path("src/apple.fdset")
.compile_protos(&["src/apple.proto"], &["src/"])
.unwrap();
}

Now we can create a protobuf channel and log messages to it.

use foxglove::{ChannelBuilder, Schema};
use prost::Message;

const APPLE_DESC: &[u8] = include_bytes!("apple.fd");

/// Information used to construct a `Channel` and encode our messages.
impl foxglove::Encode for fruit::Apple {
type Error = prost::EncodeError;

fn get_schema() -> Option<Schema> {
Some(Schema::new("fruit.Apple", "protobuf", APPLE_SCHEMA))
}

fn get_message_encoding() -> String {
"protobuf".to_string()
}

fn encode(&self, buf: &mut impl prost::bytes::BufMut) -> Result<(), Self::Error> {
Message::encode(self, buf)?;
Ok(())
}
}

// Set up a channel for our protobuf messages
let channel = ChannelBuilder::new("/fruit").build::<fruit::Apple>();

// Create and log a protobuf message
let msg = fruit::Apple {
color: Some("red".to_string()),
diameter: Some(10),
};
channel.log(&msg);