Getting Started with Rust Error Tracking

Jan Michael Auer /

At the intersection of safety and performance, you’ll find Rust — Stack Overflow’s “most-loved programming language” for three years running. Rust provides the control needed to ensure optimal performance while offering relatively high abstractions at zero-cost.

Earlier this year, we introduced Sentry’s Rust SDK, which makes it easy for developers to:

  • Catch panics.
  • Easily log failures.
  • Catch errors from error-chain.
  • Log messages.
  • Record breadcrumbs.
  • Get useful context data, like OS and device information.

The Rust SDK provides you with as much error information as possible, as long as you ship your debug information in the executable. Below, you’ll find some helpful tips for getting the most out of the SDK.

Rust Exception in Sentry
a Rust exception in Sentry

Getting Started

Add sentry to your Cargo.toml, and activate debug information in release builds:

[dependencies]
sentry = "0.12"
    
[profile.release]
debug = true

Simple so far, right? Now, capture failures and panics with this:

extern crate sentry;
    
use sentry::integrations::panic::register_panic_handler;
use sentry::integrations::failure::capture_error;
    
fn main() {
     let _sentry = sentry::init("YOUR_DSN_HERE");
     register_panic_handler();
    
     if let Err(err) = your_potentially_failing_function() {
         println!("error: {}", err);
         capture_error(&err);
     }
}

Integrations

While the Rust SDK works out of the box, integrations work to expand its functionality.

Failures

Failure errors and Fail objects can be logged with the failure integration. This works really well if you use the failure::Error type or if you have failure::Fail objects that use the failure context internally to gain a backtrace.

use sentry::integrations::failure::capture_error;
    
let result = match function_that_might_fail() {
    Ok(result) => result,
    Err(err) => {
        capture_error(&err);
        return Err(err);
    }
};

If you’d like to capture fails, but not errors, use capture_fail.

Panics

A panic handler can be installed that will automatically dispatch all errors to Sentry that are caused by a panic. Panics are forwarded to the previously registered panic hook.

use sentry::integrations::panic::register_panic_handler;
register_panic_handler();

Logging

You can add support for automatic breadcrumb capturing from logs with env_logger. Call this crate’s init function instead of the one from env_logger, and pass None as logger:

sentry::integrations::env_logger::init(None, Default::default());

This parses the default RUST_LOG environment variable and configures both env_logger and this crate appropriately. Want to create your own logger? No problem. Forward it accordingly:

let mut log_builder = pretty_env_logger::formatted_builder().unwrap();
log_builder.parse("info,foo=debug");
sentry::integrations::env_logger::init(Some(log_builder.build()), Default::default());

You can also add support for automatic breadcrumb capturing from logs. The log crate is supported in two ways:

  • Events can be captured as breadcrumbs for later.
  • Error events can be logged as events to Sentry.

By default, anything above Info is recorded as breadcrumb, and anything above Error is captured as error event.

Due to how log systems in Rust work, this currently requires you to slightly change your log setup. This is an example with the pretty_env_logger crate:

let mut log_builder = pretty_env_logger::formatted_builder().unwrap();
log_builder.parse("info");  // or env::var("RUST_LOG")
let logger = log_builder.build();
let options = sentry::integrations::log::LoggerOptions {
    global_filter: Some(logger.filter()),
    ..Default::default()
};
sentry::integrations::log::init(Some(Box::new(logger)), options);

For loggers based on env_logger (like pretty_env_logger), you can also use the env_logger integration. Save yourself some time here — this integration will be much easier for you to use.

Advanced Usage

Want to receive more information about your app? The SDK provides an easy way to capture additional context information which will then attached to all captured events. This can be useful to identify authenticated users, record breadcrumbs while users are interacting with your software, or simply capture the name and version of a runtime that you’re interested in.

If some of this information is only valid during a certain operation, you can even temporarily push a Scope and configure it. Once your operation has completed, drop the scope, and you’re left with the state before pushing the scope:

use sentry::Hub;
    
Hub::get_current().with_scope(
  |scope| {
    // configure the scope first
    scope.set_transaction(Some("my_operation"));
    scope.set_tag("operation", "my");
   },
   || {
    // now run the operation
    my_operation();
   }
)

But how can this work with asynchronous code, such as futures, where multiple operations might overlap in time? No need to worry, we’ve got you covered here! Simply create a new Hub to capture exceptions. It automatically inherits the ambient scope but can be configured separately for asynchronous tasks. And since it’s both Send and Sync, you can easily move it between threads.

use sentry::Hub;
use sentry::integrations::failure::event_from_fail;
    
let hub = Arc::new(Hub::new_from_top(Hub::current()));
    
// optionally configure it next
hub.configure_scope(|scope| {
  scope.set_transaction(Some("my_operation"));
  scope.set_tag("operation", "my");
});
    
// spawn a future and use the hub
let future = first_operation_async()
  .and_then(second_operation_async)
  .map_err(move |fail| {
    hub.capture_event(event_from_fail(fail));
    fail
  });
      
tokio::spawn(future);

Looking for a bit more help? Our API docs are a great place to start. And if you have questions or feedback, post in our forum or issue tracker, or shout out to our support engineers. They’re here to help. And also to code. But mostly to help.