-
-
Notifications
You must be signed in to change notification settings - Fork 184
feat: Add a slog Integration
#217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -11,5 +11,6 @@ members = [ | |
| "sentry-failure", | ||
| "sentry-log", | ||
| "sentry-panic", | ||
| "sentry-slog", | ||
| "sentry-types", | ||
| ] | ||
This file contains hidden or 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,3 @@ | ||
| # Uncomment and use this with `cargo +nightly fmt`: | ||
| # unstable_features = true | ||
| # format_code_in_doc_comments = true |
This file contains hidden or 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,20 @@ | ||
| [package] | ||
| name = "sentry-slog" | ||
| version = "0.18.0" | ||
| authors = ["Sentry <hello@sentry.io>"] | ||
| license = "Apache-2.0" | ||
| readme = "README.md" | ||
| repository = "https://github.com/getsentry/sentry-rust" | ||
| homepage = "https://github.com/getsentry/sentry-rust" | ||
| documentation = "https://getsentry.github.io/sentry-rust" | ||
| description = """ | ||
| Sentry Integration for slog | ||
| """ | ||
| edition = "2018" | ||
|
|
||
| [dependencies] | ||
| sentry-core = { version = "0.18.0", path = "../sentry-core" } | ||
| slog = "2.5.2" | ||
|
|
||
| [dev-dependencies] | ||
| sentry = { version = "0.18.0", path = "../sentry", features = ["with_test_support"] } |
This file contains hidden or 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,87 @@ | ||
| use sentry_core::protocol::{Breadcrumb, Event, Exception, Frame, Level, Map, Stacktrace, Value}; | ||
| use slog::{OwnedKVList, Record, KV}; | ||
|
|
||
| /// Converts a `slog::Level` to a sentry `Level` | ||
| pub fn convert_log_level(level: slog::Level) -> Level { | ||
| match level { | ||
| slog::Level::Trace | slog::Level::Debug => Level::Debug, | ||
| slog::Level::Info => Level::Info, | ||
| slog::Level::Warning => Level::Warning, | ||
| slog::Level::Error | slog::Level::Critical => Level::Error, | ||
| } | ||
| } | ||
|
|
||
| /// Adds the data from a `slog::KV` into a sentry `Map`. | ||
| fn add_kv_to_map(map: &mut Map<String, Value>, kv: &impl KV) { | ||
| let _ = (map, kv); | ||
| // TODO: actually implement this ;-) | ||
| } | ||
|
|
||
| /// Creates a sentry `Breadcrumb` from the `slog::Record`. | ||
|
Swatinem marked this conversation as resolved.
|
||
| pub fn breadcrumb_from_record(record: &Record, values: &OwnedKVList) -> Breadcrumb { | ||
| let mut data = Map::new(); | ||
| add_kv_to_map(&mut data, &record.kv()); | ||
| add_kv_to_map(&mut data, values); | ||
|
|
||
| Breadcrumb { | ||
| ty: "log".into(), | ||
| message: Some(record.msg().to_string()), | ||
| level: convert_log_level(record.level()), | ||
| data, | ||
| ..Default::default() | ||
| } | ||
| } | ||
|
|
||
| /// Creates a simple message `Event` from the `slog::Record`. | ||
| pub fn event_from_record(record: &Record, values: &OwnedKVList) -> Event<'static> { | ||
| let mut extra = Map::new(); | ||
| add_kv_to_map(&mut extra, &record.kv()); | ||
| add_kv_to_map(&mut extra, values); | ||
| Event { | ||
| message: Some(record.msg().to_string()), | ||
| level: convert_log_level(record.level()), | ||
| ..Default::default() | ||
| } | ||
| } | ||
|
|
||
| /// Creates an exception `Event` from the `slog::Record`. | ||
| /// | ||
| /// The exception will have a stacktrace that corresponds to the location | ||
| /// information contained in the `slog::Record`. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// let args = format_args!(""); | ||
| /// let record = slog::record!(slog::Level::Error, "", &args, slog::b!()); | ||
| /// let kv = slog::o!().into(); | ||
| /// let event = sentry_slog::exception_from_record(&record, &kv); | ||
| /// | ||
| /// let frame = &event.exception.as_ref()[0] | ||
| /// .stacktrace | ||
| /// .as_ref() | ||
| /// .unwrap() | ||
| /// .frames[0]; | ||
| /// assert!(frame.lineno.unwrap() > 0); | ||
| /// ``` | ||
| pub fn exception_from_record(record: &Record, values: &OwnedKVList) -> Event<'static> { | ||
| let mut event = event_from_record(record, values); | ||
| let frame = Frame { | ||
| function: Some(record.function().into()), | ||
| module: Some(record.module().into()), | ||
| filename: Some(record.file().into()), | ||
| lineno: Some(record.line().into()), | ||
| colno: Some(record.column().into()), | ||
| ..Default::default() | ||
| }; | ||
| let exception = Exception { | ||
| ty: "slog::Record".into(), | ||
| stacktrace: Some(Stacktrace { | ||
| frames: vec![frame], | ||
| ..Default::default() | ||
| }), | ||
| ..Default::default() | ||
| }; | ||
| event.exception = vec![exception].into(); | ||
| event | ||
| } | ||
This file contains hidden or 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,40 @@ | ||
| use crate::SlogIntegration; | ||
| use sentry_core::Hub; | ||
| use slog::{Drain, OwnedKVList, Record}; | ||
|
|
||
| /// A Drain which passes all Records to sentry. | ||
| pub struct SentryDrain<D: Drain> { | ||
| drain: D, | ||
| } | ||
|
|
||
| impl<D: Drain> SentryDrain<D> { | ||
| /// Creates a new `SentryDrain`, wrapping a `slog::Drain`. | ||
| pub fn new(drain: D) -> Self { | ||
| Self { drain } | ||
| } | ||
| } | ||
|
|
||
| // TODO: move this into `sentry-core`, as this is generally useful for more | ||
| // integrations. | ||
| fn with_integration<F, R>(f: F) -> R | ||
| where | ||
| F: Fn(&Hub, &SlogIntegration) -> R, | ||
| R: Default, | ||
| { | ||
| Hub::with_active(|hub| hub.with_integration(|integration| f(hub, integration))) | ||
| } | ||
|
|
||
| impl<D: Drain> slog::Drain for SentryDrain<D> { | ||
| type Ok = D::Ok; | ||
| type Err = D::Err; | ||
|
|
||
| fn log(&self, record: &Record, values: &OwnedKVList) -> Result<Self::Ok, Self::Err> { | ||
| with_integration(|hub, integration| integration.log(hub, record, values)); | ||
| self.drain.log(record, values) | ||
| } | ||
|
|
||
| fn is_enabled(&self, level: slog::Level) -> bool { | ||
| with_integration(|_, integration| integration.is_enabled(level)) | ||
| || self.drain.is_enabled(level) | ||
| } | ||
| } |
This file contains hidden or 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,122 @@ | ||
| use sentry_core::protocol::{Breadcrumb, Event}; | ||
| use sentry_core::{Hub, Integration}; | ||
| use slog::{OwnedKVList, Record}; | ||
|
|
||
| use crate::{breadcrumb_from_record, event_from_record, exception_from_record}; | ||
|
|
||
| /// The Action that Sentry should perform for a `slog::Level`. | ||
| pub enum LevelFilter { | ||
| /// Ignore the `Record`. | ||
| Ignore, | ||
| /// Create a `Breadcrumb` from this `Record`. | ||
| Breadcrumb, | ||
| /// Create a message `Event` from this `Record`. | ||
| Event, | ||
| /// Create an exception `Event` from this `Record`. | ||
| Exception, | ||
| } | ||
|
|
||
| /// Custom Mappers | ||
| #[allow(clippy::large_enum_variant)] | ||
| pub enum RecordMapping { | ||
| /// Adds the `Breadcrumb` to the sentry scope. | ||
| Breadcrumb(Breadcrumb), | ||
| /// Captures the `Event` to sentry. | ||
| Event(Event<'static>), | ||
| } | ||
|
|
||
| /// The default slog filter. | ||
| /// | ||
| /// By default, an exception event is captured for `critical` logs, | ||
| /// a regular event for `error` and `warning` logs, and breadcrumbs for | ||
| /// everything else. | ||
| pub fn default_filter(level: slog::Level) -> LevelFilter { | ||
| match level { | ||
| slog::Level::Critical => LevelFilter::Exception, | ||
| slog::Level::Error | slog::Level::Warning => LevelFilter::Event, | ||
| slog::Level::Info | slog::Level::Debug | slog::Level::Trace => LevelFilter::Breadcrumb, | ||
| } | ||
| } | ||
|
|
||
| /// The Sentry `slog` Integration. | ||
| /// | ||
| /// Can be configured with a custom filter and mapper. | ||
| pub struct SlogIntegration { | ||
| filter: Box<dyn Fn(slog::Level) -> LevelFilter + Send + Sync>, | ||
| mapper: Option<Box<dyn Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync>>, | ||
| } | ||
|
|
||
| impl Default for SlogIntegration { | ||
| fn default() -> Self { | ||
| Self { | ||
| filter: Box::new(default_filter), | ||
| mapper: None, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl SlogIntegration { | ||
| /// Create a new `slog` Integration. | ||
| pub fn new() -> Self { | ||
| Self::default() | ||
| } | ||
|
|
||
| /// Sets a custom filter function. | ||
| /// | ||
| /// The filter classifies how sentry should handle `slog::Record`s based on | ||
| /// their level. | ||
| pub fn filter<F>(mut self, filter: F) -> Self | ||
| where | ||
| F: Fn(slog::Level) -> LevelFilter + 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 `slog::Record`s. | ||
| pub fn mapper<M>(mut self, mapper: M) -> Self | ||
| where | ||
| M: Fn(&Record, &OwnedKVList) -> RecordMapping + Send + Sync + 'static, | ||
| { | ||
| self.mapper = Some(Box::new(mapper)); | ||
| self | ||
| } | ||
|
|
||
| pub(crate) fn log(&self, hub: &Hub, record: &Record, values: &OwnedKVList) { | ||
| let item: RecordMapping = match &self.mapper { | ||
| Some(mapper) => mapper(record, values), | ||
| None => match (self.filter)(record.level()) { | ||
| LevelFilter::Ignore => return, | ||
| LevelFilter::Breadcrumb => { | ||
| RecordMapping::Breadcrumb(breadcrumb_from_record(record, values)) | ||
| } | ||
| LevelFilter::Event => RecordMapping::Event(event_from_record(record, values)), | ||
| LevelFilter::Exception => { | ||
| RecordMapping::Event(exception_from_record(record, values)) | ||
| } | ||
| }, | ||
| }; | ||
| match item { | ||
| RecordMapping::Breadcrumb(b) => hub.add_breadcrumb(b), | ||
| RecordMapping::Event(e) => { | ||
| hub.capture_event(e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn is_enabled(&self, level: slog::Level) -> bool { | ||
| match (self.filter)(level) { | ||
| LevelFilter::Ignore => false, | ||
| _ => true, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Integration for SlogIntegration { | ||
| fn name(&self) -> &'static str { | ||
| "slog" | ||
| } | ||
| } |
This file contains hidden or 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,76 @@ | ||
| //! Sentry `slog` Integration. | ||
| //! | ||
| //! The sentry `slog` integration consists of two parts, the | ||
| //! [`SlogIntegration`] which configures how sentry should treat | ||
| //! `slog::Record`s, and the [`SentryDrain`], which can be used to create a | ||
| //! `slog::Logger`. | ||
| //! | ||
| //! *NOTE*: This integration currently does not process any `slog::KV` pairs, | ||
| //! but support for this will be added in the future. | ||
| //! | ||
| //! # Examples | ||
| //! | ||
| //! ``` | ||
| //! use sentry::{init, ClientOptions}; | ||
| //! use sentry_slog::{SentryDrain, SlogIntegration}; | ||
| //! | ||
| //! let integration = SlogIntegration::default(); | ||
| //! let options = ClientOptions::default().add_integration(integration); | ||
| //! let _sentry = sentry::init(options); | ||
| //! | ||
| //! let drain = SentryDrain::new(slog::Discard); | ||
| //! let root = slog::Logger::root(drain, slog::o!()); | ||
| //! | ||
| //! # let options = ClientOptions::default().add_integration(SlogIntegration::default()); | ||
| //! # let events = sentry::test::with_captured_events_options(|| { | ||
| //! slog::info!(root, "recorded as breadcrumb"); | ||
| //! slog::warn!(root, "recorded as regular event"); | ||
| //! # }, options.clone()); | ||
| //! # let captured_event = events.into_iter().next().unwrap(); | ||
| //! | ||
| //! assert_eq!( | ||
| //! captured_event.breadcrumbs.as_ref()[0].message.as_deref(), | ||
| //! Some("recorded as breadcrumb") | ||
| //! ); | ||
| //! assert_eq!( | ||
| //! captured_event.message.as_deref(), | ||
| //! Some("recorded as regular event") | ||
| //! ); | ||
| //! | ||
| //! # let events = sentry::test::with_captured_events_options(|| { | ||
| //! slog::crit!(root, "recorded as exception event"); | ||
| //! # }, options); | ||
| //! # let captured_event = events.into_iter().next().unwrap(); | ||
| //! | ||
| //! assert_eq!(captured_event.exception.len(), 1); | ||
| //! ``` | ||
| //! | ||
| //! The integration can also be customized with a `filter`, and a `mapper`: | ||
| //! | ||
| //! ``` | ||
| //! use sentry_slog::{exception_from_record, LevelFilter, RecordMapping, SlogIntegration}; | ||
| //! | ||
| //! let integration = SlogIntegration::default() | ||
| //! .filter(|level| match level { | ||
| //! slog::Level::Critical | slog::Level::Error => LevelFilter::Event, | ||
| //! _ => LevelFilter::Ignore, | ||
| //! }) | ||
| //! .mapper(|record, kv| RecordMapping::Event(exception_from_record(record, kv))); | ||
| //! ``` | ||
| //! | ||
| //! Please not that the `mapper` can override any classification from the | ||
| //! previous `filter`. | ||
| //! | ||
| //! [`SlogIntegration`]: struct.SlogIntegration.html | ||
| //! [`SentryDrain`]: struct.SentryDrain.html | ||
|
|
||
| #![deny(missing_docs)] | ||
| #![deny(unsafe_code)] | ||
|
|
||
| mod converters; | ||
| mod drain; | ||
| mod integration; | ||
|
|
||
| pub use converters::*; | ||
| pub use drain::SentryDrain; | ||
| pub use integration::{default_filter, LevelFilter, RecordMapping, SlogIntegration}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.