forked from getsentry/sentry-rust
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tracing): Add tracing-subscriber integration (getsentry#329)
This allows to record tracing events either as breadcrumbs or events, the same way sentry-log does.
- Loading branch information
Showing
11 changed files
with
429 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,5 +10,6 @@ members = [ | |
"sentry-log", | ||
"sentry-panic", | ||
"sentry-slog", | ||
"sentry-tracing", | ||
"sentry-types", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "sentry-tracing" | ||
version = "0.22.0" | ||
authors = ["Sentry <[email protected]>"] | ||
license = "Apache-2.0" | ||
readme = "README.md" | ||
repository = "https://github.com/getsentry/sentry-rust" | ||
homepage = "https://sentry.io/welcome/" | ||
description = """ | ||
Sentry integration for tracing and tracing-subscriber crates. | ||
""" | ||
edition = "2018" | ||
|
||
[dependencies] | ||
sentry-core = { version = "0.22.0", path = "../sentry-core" } | ||
tracing-core = "0.1" | ||
tracing-subscriber = "0.2" | ||
|
||
[dev-dependencies] | ||
log = "0.4" | ||
sentry = { version = "0.22.0", path = "../sentry", default-features = false, features = ["test"] } | ||
tracing = "0.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<p align="center"> | ||
<a href="https://sentry.io" target="_blank" align="center"> | ||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280"> | ||
</a> | ||
</p> | ||
|
||
# Sentry Rust SDK: sentry-tracing | ||
|
||
Adds support for automatic Breadcrumb and Event capturing from tracing events, | ||
similar to the `sentry-log` crate. | ||
|
||
The `tracing` crate is supported in two ways. First, events can be captured as | ||
breadcrumbs for later. Secondly, error events can be captured as events to | ||
Sentry. By default, anything above `Info` is recorded as breadcrumb and | ||
anything above `Error` is captured as error event. | ||
|
||
By using this crate in combination with `tracing-subscriber` and its `log` | ||
integration, `sentry-log` does not need to be used, as logs will be ingested | ||
in the tracing system and generate events, thus be relayed to this crate. It | ||
effectively replaces `sentry-log` when tracing is used. | ||
|
||
## Examples | ||
|
||
```rust | ||
use tracing_subscriber::prelude::*; | ||
|
||
tracing_subscriber::registry() | ||
.with(tracing_subscriber::fmt::layer()) | ||
.with(sentry_tracing::layer()) | ||
.try_init() | ||
.unwrap(); | ||
|
||
let _sentry = sentry::init(()); | ||
|
||
tracing::info!("Generates a breadcrumb"); | ||
tracing::error!("Generates an event"); | ||
// Also works, since log events are ingested by the tracing system | ||
log::info!("Generates a breadcrumb"); | ||
log::error!("Generates an event"); | ||
``` | ||
|
||
Or one might also set an explicit filter, to customize how to treat log | ||
records: | ||
|
||
```rust | ||
use sentry_tracing::EventFilter; | ||
|
||
let layer = sentry_tracing::layer().filter(|md| match md.level() { | ||
&tracing::Level::ERROR => EventFilter::Event, | ||
_ => EventFilter::Ignore, | ||
}); | ||
``` | ||
|
||
## Resources | ||
|
||
License: Apache-2.0 | ||
|
||
- [Discord](https://discord.gg/ez5KZN7) server for project discussions. | ||
- Follow [@getsentry](https://twitter.com/getsentry) on Twitter for updates | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
use std::collections::BTreeMap; | ||
|
||
use sentry_core::protocol::{Event, Value}; | ||
use sentry_core::{Breadcrumb, Level}; | ||
use tracing_core::field::{Field, Visit}; | ||
|
||
/// Converts a [`tracing_core::Level`] to a Sentry [`Level`] | ||
pub fn convert_tracing_level(level: &tracing_core::Level) -> Level { | ||
match level { | ||
&tracing_core::Level::TRACE | &tracing_core::Level::DEBUG => Level::Debug, | ||
&tracing_core::Level::INFO => Level::Info, | ||
&tracing_core::Level::WARN => Level::Warning, | ||
&tracing_core::Level::ERROR => Level::Error, | ||
} | ||
} | ||
|
||
/// Extracts the message and metadata from an event | ||
pub fn extract_data(event: &tracing_core::Event) -> (Option<String>, BTreeMap<String, Value>) { | ||
// Find message of the event, if any | ||
let mut data = BTreeMapRecorder::default(); | ||
event.record(&mut data); | ||
let message = data | ||
.0 | ||
.remove("message") | ||
.map(|v| v.as_str().map(|s| s.to_owned())) | ||
.flatten(); | ||
|
||
(message, data.0) | ||
} | ||
|
||
#[derive(Default)] | ||
/// Records all fields of [`tracing_core::Event`] for easy access | ||
struct BTreeMapRecorder(pub BTreeMap<String, Value>); | ||
|
||
impl BTreeMapRecorder { | ||
fn record<T: Into<Value>>(&mut self, field: &Field, value: T) { | ||
self.0.insert(field.name().to_owned(), value.into()); | ||
} | ||
} | ||
|
||
impl Visit for BTreeMapRecorder { | ||
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { | ||
self.record(field, format!("{:?}", value)); | ||
} | ||
fn record_i64(&mut self, field: &Field, value: i64) { | ||
self.record(field, value); | ||
} | ||
fn record_u64(&mut self, field: &Field, value: u64) { | ||
self.record(field, value); | ||
} | ||
fn record_bool(&mut self, field: &Field, value: bool) { | ||
self.record(field, value); | ||
} | ||
fn record_str(&mut self, field: &Field, value: &str) { | ||
self.record(field, value); | ||
} | ||
} | ||
|
||
/// Creates a [`Breadcrumb`] from a given [`tracing_core::Event`] | ||
pub fn breadcrumb_from_event(event: &tracing_core::Event) -> Breadcrumb { | ||
let (message, data) = extract_data(&event); | ||
Breadcrumb { | ||
category: Some(event.metadata().target().to_owned()), | ||
ty: "log".into(), | ||
level: convert_tracing_level(event.metadata().level()), | ||
message, | ||
data, | ||
..Default::default() | ||
} | ||
} | ||
|
||
/// Creates an [`Event`] from a given [`tracing_core::Event`] | ||
pub fn event_from_event(event: &tracing_core::Event) -> Event<'static> { | ||
let (message, extra) = extract_data(&event); | ||
Event { | ||
logger: Some(event.metadata().target().to_owned()), | ||
level: convert_tracing_level(event.metadata().level()), | ||
message, | ||
extra, | ||
..Default::default() | ||
} | ||
} | ||
|
||
/// Creates an exception [`Event`] from a given [`tracing_core::Event`] | ||
pub fn exception_from_event(event: &tracing_core::Event) -> Event<'static> { | ||
// TODO: Exception records in Sentry need a valid type, value and full stack trace to support | ||
// proper grouping and issue metadata generation. tracing_core::Record does not contain sufficient | ||
// information for this. However, it may contain a serialized error which we can parse to emit | ||
// an exception record. | ||
event_from_event(event) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
use sentry_core::protocol::Breadcrumb; | ||
use tracing_core::{Event, Level, Metadata, Subscriber}; | ||
use tracing_subscriber::layer::{Context, Layer}; | ||
|
||
use crate::converters::*; | ||
|
||
/// The action that Sentry should perform for a [`Metadata`] | ||
#[derive(Debug, Clone, Copy)] | ||
pub enum EventFilter { | ||
/// Ignore the [`Event`] | ||
Ignore, | ||
/// Create a [`Breadcrumb`] from this [`Event`] | ||
Breadcrumb, | ||
/// Create a message [`sentry_core::protocol::Event`] from this [`Event`] | ||
Event, | ||
/// Create an exception [`sentry_core::protocol::Event`] from this [`Event`] | ||
Exception, | ||
} | ||
|
||
/// The type of data Sentry should ingest for a [`Event`] | ||
#[derive(Debug)] | ||
#[allow(clippy::large_enum_variant)] | ||
pub enum EventMapping { | ||
/// Ignore the [`Event`] | ||
Ignore, | ||
/// Adds the [`Breadcrumb`] to the Sentry scope. | ||
Breadcrumb(Breadcrumb), | ||
/// Captures the [`sentry_core::protocol::Event`] to Sentry. | ||
Event(sentry_core::protocol::Event<'static>), | ||
} | ||
|
||
/// The default event filter. | ||
/// | ||
/// By default, an exception event is captured for `error`, a breadcrumb for | ||
/// `warning` and `info`, and `debug` and `trace` logs are ignored. | ||
pub fn default_filter(metadata: &Metadata) -> EventFilter { | ||
match metadata.level() { | ||
&Level::ERROR => EventFilter::Exception, | ||
&Level::WARN | &Level::INFO => EventFilter::Breadcrumb, | ||
&Level::DEBUG | &Level::TRACE => EventFilter::Ignore, | ||
} | ||
} | ||
|
||
/// Provides a tracing layer that dispatches events to sentry | ||
pub struct SentryLayer { | ||
filter: Box<dyn Fn(&Metadata) -> EventFilter + Send + Sync>, | ||
mapper: Option<Box<dyn Fn(&Event) -> EventMapping + Send + Sync>>, | ||
} | ||
|
||
impl SentryLayer { | ||
/// Sets a custom filter function. | ||
/// | ||
/// The filter classifies how sentry should handle [`Event`]s based | ||
/// on their [`Metadata`]. | ||
pub fn filter<F>(mut self, filter: F) -> Self | ||
where | ||
F: Fn(&Metadata) -> EventFilter + Send + Sync + 'static, | ||
{ | ||
self.filter = Box::new(filter); | ||
self | ||
} | ||
|
||
/// Sets a custom mapper function. | ||
/// | ||
/// The mapper is responsible for creating either breadcrumbs or events from | ||
/// [`Event`]s. | ||
pub fn mapper<F>(mut self, mapper: F) -> Self | ||
where | ||
F: Fn(&Event) -> EventMapping + Send + Sync + 'static, | ||
{ | ||
self.mapper = Some(Box::new(mapper)); | ||
self | ||
} | ||
} | ||
|
||
impl Default for SentryLayer { | ||
fn default() -> Self { | ||
Self { | ||
filter: Box::new(default_filter), | ||
mapper: None, | ||
} | ||
} | ||
} | ||
|
||
impl<S: Subscriber> Layer<S> for SentryLayer { | ||
fn on_event(&self, event: &Event, _ctx: Context<'_, S>) { | ||
let item = match &self.mapper { | ||
Some(mapper) => mapper(event), | ||
None => match (self.filter)(event.metadata()) { | ||
EventFilter::Ignore => EventMapping::Ignore, | ||
EventFilter::Breadcrumb => EventMapping::Breadcrumb(breadcrumb_from_event(event)), | ||
EventFilter::Event => EventMapping::Event(event_from_event(event)), | ||
EventFilter::Exception => EventMapping::Event(exception_from_event(event)), | ||
}, | ||
}; | ||
|
||
match item { | ||
EventMapping::Event(event) => { | ||
sentry_core::capture_event(event); | ||
} | ||
EventMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb), | ||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
/// Creates a default Sentry layer | ||
pub fn layer() -> SentryLayer { | ||
Default::default() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//! Adds support for automatic Breadcrumb and Event capturing from tracing | ||
//! events, similar to the `sentry-log` crate. | ||
//! | ||
//! The `tracing` crate is supported in two ways. First, events can be captured | ||
//! as breadcrumbs for later. Secondly, error events can be captured as events | ||
//! to Sentry. By default, anything above `Info` is recorded as breadcrumb and | ||
//! anything above `Error` is captured as error event. | ||
//! | ||
//! By using this crate in combination with `tracing-subscriber` and its `log` | ||
//! integration, `sentry-log` does not need to be used, as logs will be ingested | ||
//! in the tracing system and generate events, thus be relayed to this crate. It | ||
//! effectively replaces `sentry-log` when tracing is used. | ||
//! | ||
//! ## Examples | ||
//! | ||
//! ```rust | ||
//! use tracing_subscriber::prelude::*; | ||
//! | ||
//! tracing_subscriber::registry() | ||
//! .with(tracing_subscriber::fmt::layer()) | ||
//! .with(sentry_tracing::layer()) | ||
//! .try_init() | ||
//! .unwrap(); | ||
//! | ||
//! let _sentry = sentry::init(()); | ||
//! | ||
//! tracing::info!("Generates a breadcrumb"); | ||
//! tracing::error!("Generates an event"); | ||
//! // Also works, since log events are ingested by the tracing system | ||
//! log::info!("Generates a breadcrumb"); | ||
//! log::error!("Generates an event"); | ||
//! ``` | ||
//! | ||
//! Or one might also set an explicit filter, to customize how to treat log | ||
//! records: | ||
//! | ||
//! ```rust | ||
//! use sentry_tracing::EventFilter; | ||
//! | ||
//! let layer = sentry_tracing::layer().filter(|md| match md.level() { | ||
//! &tracing::Level::ERROR => EventFilter::Event, | ||
//! _ => EventFilter::Ignore, | ||
//! }); | ||
//! ``` | ||
#![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")] | ||
#![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")] | ||
#![warn(missing_docs)] | ||
|
||
mod converters; | ||
mod layer; | ||
|
||
pub use converters::*; | ||
pub use layer::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.