Skip to content

Commit e446e8e

Browse files
shenekStepan Henek
authored and
Stepan Henek
committed
feat: slog integration implemented
1 parent 1c21261 commit e446e8e

File tree

5 files changed

+296
-0
lines changed

5 files changed

+296
-0
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ with_failure = ["failure", "with_backtrace"]
2929
with_log = ["log", "with_backtrace"]
3030
with_debug_to_log = ["log"]
3131
with_env_logger = ["with_log", "env_logger"]
32+
with_slog = ["slog"]
3233
with_error_chain = ["error-chain", "with_backtrace"]
3334
with_device_info = ["libc", "hostname", "uname", "with_client_implementation"]
3435
with_rust_info = ["rustc_version", "with_client_implementation"]
@@ -44,6 +45,7 @@ failure = { version = "0.1.5", optional = true }
4445
log = { version = "0.4.6", optional = true, features = ["std"] }
4546
sentry-types = "0.11.0"
4647
env_logger = { version = "0.6.1", optional = true }
48+
slog = { version = "2.5.2", optional = true }
4749
reqwest = { version = "0.9.15", optional = true, default-features = false }
4850
lazy_static = "1.3.0"
4951
regex = { version = "1.1.6", optional = true }
@@ -76,5 +78,9 @@ actix-web = { version = "0.7.19", default-features = false }
7678
name = "error-chain-demo"
7779
required-features = ["with_error_chain"]
7880

81+
[[example]]
82+
name = "slog-demo"
83+
required-features = ["with_slog"]
84+
7985
[workspace]
8086
members = [".", "integrations/sentry-actix"]

examples/slog-demo.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use slog::{debug, error, info, warn};
2+
3+
fn main() {
4+
let drain = slog::Discard;
5+
// Default options - breadcrumb from info, event from warnings
6+
let wrapped_drain = sentry::integrations::slog::wrap_drain(drain, Default::default());
7+
let _sentry = sentry::init((
8+
"https://[email protected]/1041156",
9+
sentry::ClientOptions {
10+
release: sentry::release_name!(),
11+
..Default::default()
12+
},
13+
));
14+
let root = slog::Logger::root(wrapped_drain, slog::o!("test_slog" => 0));
15+
16+
debug!(root, "This should not appear"; "111" => "222");
17+
info!(root, "Info breadcrumb"; "222" => 333);
18+
warn!(root, "Warning event"; "333" => true);
19+
error!(root, "Error event"; "444" => "555");
20+
}

src/integrations/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ pub mod log;
1313
#[cfg(feature = "with_env_logger")]
1414
pub mod env_logger;
1515

16+
#[cfg(feature = "with_slog")]
17+
pub mod slog;
18+
1619
#[cfg(feature = "with_panic")]
1720
pub mod panic;

src/integrations/slog.rs

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//! Adds support for automatic breadcrumb capturing from logs with `slog`.
2+
//!
3+
//! **Feature:** `with_slog`
4+
//!
5+
//! # Configuration
6+
//!
7+
//! In the most trivial version you could proceed like this:
8+
//!
9+
//! ```no_run
10+
//! # extern crate slog;
11+
//!
12+
//! let drain = slog::Discard;
13+
//! let wrapped_drain = sentry::integrations::slog::wrap_drain(drain, Default::default());
14+
//! let root = slog::Logger::root(drain, slog::o!());
15+
//!
16+
//! slog::warn!(root, "Log for sentry")
17+
//! ```
18+
use slog::{Drain, Serializer, KV};
19+
use std::{collections::BTreeMap, fmt};
20+
21+
use crate::{
22+
api::add_breadcrumb,
23+
hub::Hub,
24+
protocol::{Breadcrumb, Event},
25+
with_scope, Level,
26+
};
27+
28+
// Serializer which stores the serde_json values in BTreeMap
29+
struct StoringSerializer {
30+
result: BTreeMap<String, serde_json::Value>,
31+
}
32+
33+
impl StoringSerializer {
34+
#[allow(missing_docs)]
35+
fn emit_serde_json_value(&mut self, key: slog::Key, val: serde_json::Value) -> slog::Result {
36+
self.result.insert(key.to_string(), val);
37+
Ok(())
38+
}
39+
40+
#[allow(missing_docs)]
41+
fn emit_serde_json_null(&mut self, key: slog::Key) -> slog::Result {
42+
self.emit_serde_json_value(key, serde_json::Value::Null)
43+
}
44+
45+
#[allow(missing_docs)]
46+
fn emit_serde_json_bool(&mut self, key: slog::Key, val: bool) -> slog::Result {
47+
self.emit_serde_json_value(key, serde_json::Value::Bool(val))
48+
}
49+
50+
#[allow(missing_docs)]
51+
fn emit_serde_json_number<V>(&mut self, key: slog::Key, value: V) -> slog::Result
52+
where
53+
serde_json::Number: From<V>,
54+
{
55+
let num = serde_json::Number::from(value);
56+
self.emit_serde_json_value(key, serde_json::Value::Number(num))
57+
}
58+
59+
#[allow(missing_docs)]
60+
fn emit_serde_json_string(&mut self, key: slog::Key, val: String) -> slog::Result {
61+
self.emit_serde_json_value(key, serde_json::Value::String(val))
62+
}
63+
}
64+
65+
macro_rules! impl_number {
66+
( $type:ty => $function_name:ident ) => {
67+
#[allow(missing_docs)]
68+
fn $function_name(&mut self, key: slog::Key, val: $type) -> slog::Result {
69+
self.emit_serde_json_number(key, val)
70+
}
71+
};
72+
}
73+
74+
impl Serializer for StoringSerializer {
75+
#[allow(missing_docs)]
76+
fn emit_bool(&mut self, key: slog::Key, val: bool) -> slog::Result {
77+
self.emit_serde_json_bool(key, val)
78+
}
79+
80+
#[allow(missing_docs)]
81+
fn emit_unit(&mut self, key: slog::Key) -> slog::Result {
82+
self.emit_serde_json_null(key)
83+
}
84+
85+
#[allow(missing_docs)]
86+
fn emit_none(&mut self, key: slog::Key) -> slog::Result {
87+
self.emit_serde_json_null(key)
88+
}
89+
90+
#[allow(missing_docs)]
91+
fn emit_char(&mut self, key: slog::Key, val: char) -> slog::Result {
92+
self.emit_serde_json_string(key, val.to_string())
93+
}
94+
95+
#[allow(missing_docs)]
96+
fn emit_f64(&mut self, key: slog::Key, val: f64) -> slog::Result {
97+
if let Some(num) = serde_json::Number::from_f64(val) {
98+
self.emit_serde_json_value(key, serde_json::Value::Number(num))
99+
} else {
100+
self.emit_serde_json_null(key)
101+
}
102+
}
103+
104+
impl_number!(u8 => emit_u8);
105+
impl_number!(i8 => emit_i8);
106+
impl_number!(u16 => emit_u16);
107+
impl_number!(i16 => emit_i16);
108+
impl_number!(u32 => emit_u32);
109+
impl_number!(i32 => emit_i32);
110+
impl_number!(u64 => emit_u64);
111+
impl_number!(i64 => emit_i64);
112+
113+
// u128 and i128 should be implemented in serde_json 1.0.40
114+
// impl_number!(u128 => emit_u128);
115+
// impl_number!(i128 => emit_i128);
116+
117+
#[allow(missing_docs)]
118+
fn emit_arguments(&mut self, _: slog::Key, _: &fmt::Arguments) -> slog::Result {
119+
Ok(())
120+
}
121+
}
122+
123+
/// Converts `slog::Level` to `Level`
124+
fn into_sentry_level(slog_level: slog::Level) -> Level {
125+
match slog_level {
126+
slog::Level::Trace | slog::Level::Debug => Level::Debug,
127+
slog::Level::Info => Level::Info,
128+
slog::Level::Warning => Level::Warning,
129+
slog::Level::Error | slog::Level::Critical => Level::Error,
130+
}
131+
}
132+
133+
/// Options for the slog configuration
134+
#[derive(Debug, Copy, Clone)]
135+
pub struct Options {
136+
/// Level since when the breadcrumbs are created
137+
breadcrumb_level: slog::Level,
138+
/// Level since when the events are sent
139+
event_level: slog::Level,
140+
}
141+
142+
impl Default for Options {
143+
fn default() -> Self {
144+
Self {
145+
breadcrumb_level: slog::Level::Info,
146+
event_level: slog::Level::Warning,
147+
}
148+
}
149+
}
150+
151+
/// Wrapped drain for sentry logging
152+
#[derive(Debug, Copy, Clone)]
153+
pub struct WrappedDrain<D>
154+
where
155+
D: Drain,
156+
{
157+
drain: D,
158+
options: Options,
159+
}
160+
161+
fn get_record_data(record: &slog::Record) -> BTreeMap<String, serde_json::Value> {
162+
let mut storing_serializer = StoringSerializer {
163+
result: BTreeMap::new(),
164+
};
165+
166+
// slog::KV can be only serialized, but we need to obtain its data
167+
// To do that a Serializer was implemented to store these data to a dict
168+
if record
169+
.kv()
170+
.serialize(record, &mut storing_serializer)
171+
.is_ok()
172+
{
173+
storing_serializer.result
174+
} else {
175+
BTreeMap::new()
176+
}
177+
}
178+
179+
impl<D> WrappedDrain<D>
180+
where
181+
D: Drain,
182+
{
183+
/// Creates a new wrapped Drain
184+
fn new(drain: D, options: Options) -> Self {
185+
Self { drain, options }
186+
}
187+
188+
/// Creates a breadcrumb
189+
fn add_breadcrumb(record: &slog::Record) {
190+
let data = get_record_data(record);
191+
192+
let breadcrumb = Breadcrumb {
193+
message: Some(record.msg().to_string()),
194+
level: into_sentry_level(record.level()),
195+
data,
196+
..Breadcrumb::default()
197+
};
198+
add_breadcrumb(|| breadcrumb);
199+
}
200+
201+
/// Captures an event
202+
fn capture_event(record: &slog::Record) {
203+
let extra = get_record_data(record);
204+
205+
let event = Event {
206+
message: Some(record.msg().to_string()),
207+
level: into_sentry_level(record.level()),
208+
..Event::default()
209+
};
210+
211+
with_scope(
212+
|scope| {
213+
for (key, value) in extra {
214+
scope.set_extra(&key, value);
215+
}
216+
},
217+
|| {
218+
Hub::with_active(move |hub| hub.capture_event(event));
219+
},
220+
);
221+
}
222+
}
223+
224+
impl<D> Drain for WrappedDrain<D>
225+
where
226+
D: Drain,
227+
{
228+
type Ok = D::Ok;
229+
type Err = D::Err;
230+
231+
fn log(
232+
&self,
233+
record: &slog::Record,
234+
values: &slog::OwnedKVList,
235+
) -> Result<Self::Ok, Self::Err> {
236+
let level = record.level();
237+
if level <= self.options.event_level {
238+
// log event
239+
Self::capture_event(record);
240+
} else if level <= self.options.breadcrumb_level {
241+
// or log bread crumbs
242+
Self::add_breadcrumb(record);
243+
}
244+
245+
self.drain.log(record, values)
246+
}
247+
}
248+
249+
impl<D> std::ops::Deref for WrappedDrain<D>
250+
where
251+
D: Drain,
252+
{
253+
type Target = D;
254+
255+
fn deref(&self) -> &Self::Target {
256+
&self.drain
257+
}
258+
}
259+
260+
/// Wraps `slog::Drain`
261+
pub fn wrap_drain<D>(drain: D, options: Options) -> WrappedDrain<D>
262+
where
263+
D: slog::Drain,
264+
{
265+
WrappedDrain::new(drain, options)
266+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
//! * `with_reqwest_transport`: enables the reqwest transport explicitly. This
109109
//! is currently the default transport.
110110
//! * `with_curl_transport`: enables the curl transport.
111+
//! * `with_slog`: enables the `slog` integration
111112
#![warn(missing_docs)]
112113

113114
#[macro_use]

0 commit comments

Comments
 (0)