17 releases (8 breaking)
new 0.9.0 | Jun 12, 2025 |
---|---|
0.8.0 | May 30, 2025 |
0.4.1 | Mar 24, 2025 |
#185 in Compression
1,554 downloads per month
555KB
12K
SLoC
Foxglove
The official Foxglove SDK.
This crate provides support for integrating with the Foxglove platform. It can be used to log events to local MCAP files or a local visualization server that communicates with the Foxglove app.
Get Started
See docs.rs.
Supported Rust Versions
The current MSRV (minimum supported rust version) is 1.83.0.
lib.rs
:
The official Foxglove SDK.
This crate provides support for integrating with the Foxglove platform. It can be used to log events to local MCAP files or a local visualization server that communicates with the Foxglove app.
Getting started
To record messages, you need at least one sink. In this example, we create an MCAP file sink,
and log a Log
message on a topic called /log
. We write one log
message and close the file.
use foxglove::schemas::Log;
use foxglove::{log, McapWriter};
// Create a new MCAP file named 'test.mcap'.
let mcap = McapWriter::new()
.create_new_buffered_file("test.mcap")
.expect("create failed");
log!(
"/log",
Log {
message: "Hello, Foxglove!".to_string(),
..Default::default()
}
);
// Flush and close the MCAP file.
mcap.close().expect("close failed");
Concepts
Context
A Context
is the binding between channels and sinks. Each channel and sink belongs to
exactly one context. Sinks receive advertisements about channels on the context, and can
optionally subscribe to receive logged messages on those channels.
When the context goes out of scope, its corresponding channels and sinks will be disconnected from one another, and logging will stop. Attempts to log further messages on the channels will elicit throttled warning messages.
Since many applications only need a single context, the SDK provides a static default context for convenience. This default context is the one used in the example above. If we wanted to use an explicit context instead, we'd write:
use foxglove::schemas::Log;
use foxglove::Context;
// Create a new context.
let ctx = Context::new();
// Create a new MCAP file named 'test.mcap'.
let mcap = ctx
.mcap_writer()
.create_new_buffered_file("test.mcap")
.expect("create failed");
// Create a new channel for the topic "/log" for `Log` messages.
let channel = ctx.channel_builder("/log").build();
channel.log(&Log {
message: "Hello, Foxglove!".to_string(),
..Default::default()
});
// Flush and close the MCAP file.
mcap.close().expect("close failed");
Channels
A Channel
gives a way to log related messages which have the same type, or Schema
. Each
channel is instantiated with a unique "topic", or name, which is typically prefixed by a /
. If
you're familiar with MCAP, it's the same concept as an MCAP channel.
A channel is always associated with exactly one Context
throughout its lifecycle. The
channel remains attached to the context until it is either explicitly closed with
Channel::close
, or the context is dropped. Attempting to log a message on a closed channel
will elicit a throttled warning.
In the example above, log!
creates a Channel<Log>
behind the scenes on
the first call. The example could be equivalently written as:
use foxglove::schemas::Log;
use foxglove::{Channel, McapWriter};
// Create a new MCAP file named 'test.mcap'.
let mcap = McapWriter::new()
.create_new_buffered_file("test.mcap")
.expect("create failed");
// Create a new channel for the topic "/log" for `Log` messages.
let channel = Channel::new("/log");
channel.log(&Log {
message: "Hello, Foxglove!".to_string(),
..Default::default()
});
// Flush and close the MCAP file.
mcap.close().expect("close failed");
log!
can be mixed and matched with manually created channels in the default Context
, as
long as the types are exactly the same.
Well-known types
The SDK provides structs for well-known schemas. These can be used in conjunction
with Channel
for type-safe logging, which ensures at compile time that messages logged to a
channel all share a common schema.
Custom data
You can also define your own custom data types by implementing the Encode
trait.
The easiest way to do this is to enable the derive
feature and derive the Encode
trait,
which will generate a schema and allow you to log your struct to a channel. This currently uses
protobuf encoding.
#[derive(foxglove::Encode)]
struct Custom<'a> {
msg: &'a str,
count: u32,
}
let channel = foxglove::Channel::new("/custom");
channel.log(&Custom {
msg: "custom",
count: 42,
});
If you'd like to use JSON encoding for integration with particular tooling, you can enable the
schemars
feature, which will provide a blanket Encode
implementation for types that
implement Serialize
and JsonSchema
.
Lazy Channels
A common pattern is to create the channels once as static variables, and then use them
throughout the application. But because channels do not have a const initializer, they must be
initialized lazily. LazyChannel
and LazyRawChannel
provide a convenient way to do this.
Be careful when using this pattern. The channel will not be advertised to sinks until it is
initialized, which is guaranteed to happen when the channel is first used. If you need to ensure
the channel is initialized before using it, you can use LazyChannel::init
.
In this example, we create two lazy channels on the default context:
use foxglove::schemas::SceneUpdate;
use foxglove::{LazyChannel, LazyRawChannel};
static BOXES: LazyChannel<SceneUpdate> = LazyChannel::new("/boxes");
static MSG: LazyRawChannel = LazyRawChannel::new("/msg", "json");
It is also possible to bind lazy channels to an explicit LazyContext
:
use foxglove::schemas::SceneUpdate;
use foxglove::{LazyChannel, LazyContext, LazyRawChannel};
static CTX: LazyContext = LazyContext::new();
static BOXES: LazyChannel<SceneUpdate> = CTX.channel("/boxes");
static MSG: LazyRawChannel = CTX.raw_channel("/msg", "json");
Sinks
A "sink" is a destination for logged messages. If you do not configure a sink, log messages will simply be dropped without being recorded. You can configure multiple sinks, and you can create or destroy them dynamically at runtime.
A sink is typically associated with exactly one Context
throughout its lifecycle. Details
about the how the sink is registered and unregistered from the context are sink-specific.
MCAP file
Use [McapWriter::new()
] to register a new MCAP writer. As long as the handle remains in scope,
events will be logged to the MCAP file. When the handle is closed or dropped, the sink will be
unregistered from the Context
, and the file will be finalized and flushed.
let mcap = foxglove::McapWriter::new()
.create_new_buffered_file("test.mcap")
.expect("create failed");
You can override the MCAP writer's configuration using McapWriter::with_options
. See
WriteOptions
for more detail about these parameters:
let options = mcap::WriteOptions::default()
.chunk_size(Some(1024 * 1024))
.compression(Some(mcap::Compression::Lz4));
let mcap = foxglove::McapWriter::with_options(options)
.create_new_buffered_file("test.mcap")
.expect("create failed");
Live visualization server
You can use the SDK to publish messages to the Foxglove app.
Note: this requires the live_visualization
feature, which is enabled by default.
Use WebSocketServer::new
to create a new live visualization server. By default, the server
listens on 127.0.0.1:8765
. Once the server is configured, call WebSocketServer::start
to
start the server, and begin accepting websocket connections from the Foxglove app.
Each client that connects to the websocket server is its own independent sink. The sink is
dynamically added to the Context
associated with the server when the client connects, and
removed from the context when the client disconnects.
See the "Connect" documentation for how to connect the Foxglove app to your running server.
Note that the server remains running until the process exits, even if the handle is dropped. Use
stop
to shut down the server explicitly.
let server = foxglove::WebSocketServer::new()
.name("Wall-E")
.bind("127.0.0.1", 9999)
.start()
.await
.expect("Failed to start visualization server");
// Log stuff here.
server.stop();
Feature flags
The Foxglove SDK defines the following feature flags:
chrono
: enables chrono conversions forDuration
andTimestamp
.derive
: enables the use of#[derive(Encode)]
to derive theEncode
trait for logging custom structs. Enabled by default.live_visualization
: enables the live visualization server and client, and adds dependencies on tokio. Enabled by default.lz4
: enables support for the LZ4 compression algorithm for mcap files. Enabled by default.schemars
: provides a blanket implementation of theEncode
trait for types that implementSerialize
andJsonSchema
.unstable
: features which are under active development and likely to change in an upcoming version.zstd
: enables support for the zstd compression algorithm for mcap files. Enabled by default.
If you do not require live visualization features, you can disable that flag to reduce the compiled size of the SDK.
Requirements
With the live_visualization
feature (enabled by default), the Foxglove SDK depends on tokio
as its async runtime. See WebSocketServer
for more information. Refer to the tokio
documentation for more information about how to configure your application to use tokio.
Dependencies
~5–16MB
~173K SLoC