Skip to content

Commit

Permalink
feat: added slog integration
Browse files Browse the repository at this point in the history
  • Loading branch information
shenek committed Oct 3, 2019
1 parent 1c21261 commit 59cdaeb
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ with_failure = ["failure", "with_backtrace"]
with_log = ["log", "with_backtrace"]
with_debug_to_log = ["log"]
with_env_logger = ["with_log", "env_logger"]
with_slog = ["slog"]
with_error_chain = ["error-chain", "with_backtrace"]
with_device_info = ["libc", "hostname", "uname", "with_client_implementation"]
with_rust_info = ["rustc_version", "with_client_implementation"]
Expand All @@ -44,6 +45,7 @@ failure = { version = "0.1.5", optional = true }
log = { version = "0.4.6", optional = true, features = ["std"] }
sentry-types = "0.11.0"
env_logger = { version = "0.6.1", optional = true }
slog = { version = "2.5.2", optional = true }
reqwest = { version = "0.9.15", optional = true, default-features = false }
lazy_static = "1.3.0"
regex = { version = "1.1.6", optional = true }
Expand Down Expand Up @@ -76,5 +78,9 @@ actix-web = { version = "0.7.19", default-features = false }
name = "error-chain-demo"
required-features = ["with_error_chain"]

[[example]]
name = "slog-demo"
required-features = ["with_slog"]

[workspace]
members = [".", "integrations/sentry-actix"]
20 changes: 20 additions & 0 deletions examples/slog-demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use slog::{debug, error, info, warn};

fn main() {
let drain = slog::Discard;
// Default options - breadcrumb from info, event from warnings
let wrapped_drain = sentry::integrations::slog::wrap_drain(drain, Default::default());
let _sentry = sentry::init((
"https://[email protected]/1041156",
sentry::ClientOptions {
release: sentry::release_name!(),
..Default::default()
},
));
let root = slog::Logger::root(wrapped_drain, slog::o!("test_slog" => 0));

debug!(root, "This should not appear"; "111" => "222");
info!(root, "Info breadcrumb"; "222" => 333);
warn!(root, "Warning event"; "333" => true);
error!(root, "Error event"; "444" => "555");
}
3 changes: 3 additions & 0 deletions src/integrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ pub mod log;
#[cfg(feature = "with_env_logger")]
pub mod env_logger;

#[cfg(feature = "with_slog")]
pub mod slog;

#[cfg(feature = "with_panic")]
pub mod panic;
266 changes: 266 additions & 0 deletions src/integrations/slog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
//! Adds support for automatic breadcrumb capturing from logs with `slog`.
//!
//! **Feature:** `with_slog`
//!
//! # Configuration
//!
//! In the most trivial version you could proceed like this:
//!
//! ```no_run
//! # extern crate slog;
//!
//! let drain = slog::Discard;
//! let wrapped_drain = sentry::integrations::slog::wrap_drain(drain, Default::default());
//! let root = slog::Logger::root(drain, slog::o!());
//!
//! slog::warn!(root, "Log for sentry")
//! ```
use slog::{Drain, Serializer, KV};
use std::{collections::BTreeMap, fmt};

use crate::{
api::add_breadcrumb,
hub::Hub,
protocol::{Breadcrumb, Event},
with_scope, Level,
};

// Serializer which stores the serde_json values in BTreeMap
struct StoringSerializer {
result: BTreeMap<String, serde_json::Value>,
}

impl StoringSerializer {
#[allow(missing_docs)]
fn emit_serde_json_value(&mut self, key: slog::Key, val: serde_json::Value) -> slog::Result {
self.result.insert(key.to_string(), val);
Ok(())
}

#[allow(missing_docs)]
fn emit_serde_json_null(&mut self, key: slog::Key) -> slog::Result {
self.emit_serde_json_value(key, serde_json::Value::Null)
}

#[allow(missing_docs)]
fn emit_serde_json_bool(&mut self, key: slog::Key, val: bool) -> slog::Result {
self.emit_serde_json_value(key, serde_json::Value::Bool(val))
}

#[allow(missing_docs)]
fn emit_serde_json_number<V>(&mut self, key: slog::Key, value: V) -> slog::Result
where
serde_json::Number: From<V>,
{
let num = serde_json::Number::from(value);
self.emit_serde_json_value(key, serde_json::Value::Number(num))
}

#[allow(missing_docs)]
fn emit_serde_json_string(&mut self, key: slog::Key, val: String) -> slog::Result {
self.emit_serde_json_value(key, serde_json::Value::String(val))
}
}

macro_rules! impl_number {
( $type:ty => $function_name:ident ) => {
#[allow(missing_docs)]
fn $function_name(&mut self, key: slog::Key, val: $type) -> slog::Result {
self.emit_serde_json_number(key, val)
}
};
}

impl Serializer for StoringSerializer {
#[allow(missing_docs)]
fn emit_bool(&mut self, key: slog::Key, val: bool) -> slog::Result {
self.emit_serde_json_bool(key, val)
}

#[allow(missing_docs)]
fn emit_unit(&mut self, key: slog::Key) -> slog::Result {
self.emit_serde_json_null(key)
}

#[allow(missing_docs)]
fn emit_none(&mut self, key: slog::Key) -> slog::Result {
self.emit_serde_json_null(key)
}

#[allow(missing_docs)]
fn emit_char(&mut self, key: slog::Key, val: char) -> slog::Result {
self.emit_serde_json_string(key, val.to_string())
}

#[allow(missing_docs)]
fn emit_f64(&mut self, key: slog::Key, val: f64) -> slog::Result {
if let Some(num) = serde_json::Number::from_f64(val) {
self.emit_serde_json_value(key, serde_json::Value::Number(num))
} else {
self.emit_serde_json_null(key)
}
}

impl_number!(u8 => emit_u8);
impl_number!(i8 => emit_i8);
impl_number!(u16 => emit_u16);
impl_number!(i16 => emit_i16);
impl_number!(u32 => emit_u32);
impl_number!(i32 => emit_i32);
impl_number!(u64 => emit_u64);
impl_number!(i64 => emit_i64);

// u128 and i128 should be implemented in serde_json 1.0.40
// impl_number!(u128 => emit_u128);
// impl_number!(i128 => emit_i128);

#[allow(missing_docs)]
fn emit_arguments(&mut self, _: slog::Key, _: &fmt::Arguments) -> slog::Result {
Ok(())
}
}

/// Converts `slog::Level` to `Level`
fn into_sentry_level(slog_level: slog::Level) -> Level {
match slog_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,
}
}

/// Options for the slog configuration
#[derive(Debug, Copy, Clone)]
pub struct Options {
/// Level since when the breadcrumbs are created
breadcrumb_level: slog::Level,
/// Level since when the events are sent
event_level: slog::Level,
}

impl Default for Options {
fn default() -> Self {
Self {
breadcrumb_level: slog::Level::Info,
event_level: slog::Level::Warning,
}
}
}

/// Wrapped drain for sentry logging
#[derive(Debug, Copy, Clone)]
pub struct WrappedDrain<D>
where
D: Drain,
{
drain: D,
options: Options,
}

fn get_record_data(record: &slog::Record) -> BTreeMap<String, serde_json::Value> {
let mut storing_serializer = StoringSerializer {
result: BTreeMap::new(),
};

// slog::KV can be only serialized, but we need to obtain its data
// To do that a Serializer was implemented to store these data to a dict
if record
.kv()
.serialize(record, &mut storing_serializer)
.is_ok()
{
storing_serializer.result
} else {
BTreeMap::new()
}
}

impl<D> WrappedDrain<D>
where
D: Drain,
{
/// Creates a new wrapped Drain
fn new(drain: D, options: Options) -> Self {
Self { drain, options }
}

/// Creates a breadcrumb
fn add_breadcrumb(record: &slog::Record) {
let data = get_record_data(record);

let breadcrumb = Breadcrumb {
message: Some(record.msg().to_string()),
level: into_sentry_level(record.level()),
data,
..Breadcrumb::default()
};
add_breadcrumb(|| breadcrumb);
}

/// Captures an event
fn capture_event(record: &slog::Record) {
let extra = get_record_data(record);

let event = Event {
message: Some(record.msg().to_string()),
level: into_sentry_level(record.level()),
..Event::default()
};

with_scope(
|scope| {
for (key, value) in extra {
scope.set_extra(&key, value);
}
},
|| {
Hub::with_active(move |hub| hub.capture_event(event));
},
);
}
}

impl<D> Drain for WrappedDrain<D>
where
D: Drain,
{
type Ok = D::Ok;
type Err = D::Err;

fn log(
&self,
record: &slog::Record,
values: &slog::OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
let level = record.level();
if level <= self.options.event_level {
// log event
Self::capture_event(record);
} else if level <= self.options.breadcrumb_level {
// or log bread crumbs
Self::add_breadcrumb(record);
}

self.drain.log(record, values)
}
}

impl<D> std::ops::Deref for WrappedDrain<D>
where
D: Drain,
{
type Target = D;

fn deref(&self) -> &Self::Target {
&self.drain
}
}

/// Wraps `slog::Drain`
pub fn wrap_drain<D>(drain: D, options: Options) -> WrappedDrain<D>
where
D: slog::Drain,
{
WrappedDrain::new(drain, options)
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
//! * `with_reqwest_transport`: enables the reqwest transport explicitly. This
//! is currently the default transport.
//! * `with_curl_transport`: enables the curl transport.
//! * `with_slog`: enables the `slog` integration
#![warn(missing_docs)]

#[macro_use]
Expand Down

0 comments on commit 59cdaeb

Please sign in to comment.