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

Download history 75/week @ 2025-02-26 67/week @ 2025-03-05 234/week @ 2025-03-19 62/week @ 2025-03-26 103/week @ 2025-04-02 89/week @ 2025-04-09 379/week @ 2025-04-16 133/week @ 2025-04-23 142/week @ 2025-04-30 134/week @ 2025-05-07 40/week @ 2025-05-14 9/week @ 2025-05-21 160/week @ 2025-05-28 266/week @ 2025-06-04 1031/week @ 2025-06-11

1,554 downloads per month

MIT license

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 for Duration and Timestamp.
  • derive: enables the use of #[derive(Encode)] to derive the Encode 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 the Encode trait for types that implement Serialize and JsonSchema.
  • 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