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.
- Rust
- Python
- C++
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()
});
Using the log
function:
foxglove.log(foxglove.schemas.LocationFix(latitude=37.7764, longitude=-122.4216)
Using a schema-specific Channel
:
channel = foxglove.channels.LocationFixChannel("/location")
channel.logfoxglove.schemas.LocationFix(latitude=37.7764, longitude=-122.4216)
auto channel = foxglove::schemas::LocationFixChannel::create("/location").value();
foxglove::schemas::LocationFix location;
location.latitude = 37.7764;
location.longitude = -122.4216;
channel.log(location);
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.
- Rust
- Python
- C++
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 Python, you can log simple data like dicts, which will be serialized as JSON.
foxglove.log(
"/plot",
{
"label": "y",
"value": 1,
}
)
You can also use channels as in the previous section.
In C++, you can log custom message schemas without specifying a schema using JSON or MsgPack.
auto channel = foxglove::RawChannel::create("/plot", "json").value();
std::string msg = "{\"label\": \"y\", \"value\": 1}";
channel.log(reinterpret_cast<const std::byte *>(msg.data()), msg.size());
For a more complex example, see https://github.com/foxglove/foxglove-sdk/tree/main/cpp/examples/auto-serialize
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.
- Rust
- Python
- C++
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);
import fruit_pb2 # our generated code
from foxglove import Channel, Schema
from google.protobuf import descriptor_pb2
# Set up a channel for our protobuf messages
proto_fds = descriptor_pb2.FileDescriptorSet()
fruit_pb2.DESCRIPTOR.CopyToProto(proto_fds.file.add())
apple_descriptor = fruit_pb2.Apple.DESCRIPTOR
channel = Channel(
topic="/fruit",
message_encoding="protobuf",
schema=Schema(
name=f"{apple_descriptor.file.package}.{apple_descriptor.name}",
encoding="protobuf",
data=proto_fds.SerializeToString(),
),
)
# Create and log a protobuf message
apple = fruit_pb2.Apple()
apple.color = "red"
apple.diameter = 10
channel.log(apple.SerializeToString())
For a more complete example, see https://github.com/foxglove/foxglove-sdk/tree/main/python/foxglove-sdk-examples/custom-protobuf
#include <apple.pb.h>
#include <google/protobuf/descriptor.pb.h>
// Set up a channel for our protobuf messages
auto descriptor = fruit::Apple::descriptor();
auto *file_descriptor = descriptor->file();
google::protobuf::FileDescriptorSet file_descriptor_set;
file_descriptor->CopyTo(file_descriptor_set.add_file());
std::string serialized_descriptor = file_descriptor_set.SerializeAsString();
foxglove::Schema schema;
schema.encoding = "protobuf";
schema.name = descriptor->full_name();
schema.data = reinterpret_cast<const std::byte *>(serialized_descriptor.data());
schema.data_len = serialized_descriptor.size();
auto channel = foxglove::RawChannel::create("/fruit", "protobuf", std::move(schema)).value();
// Create and log a protobuf message
fruit::Apple apple;
apple.set_color("red");
apple.set_diameter(10);
std::string msg = apple.SerializeAsString();
channel.log(reinterpret_cast<const std::byte *>(msg.data()), msg.size());