Skip to content

Commit d12a19a

Browse files
committed
feat: slog integration implemented
1 parent 78c7c02 commit d12a19a

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ 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", "serde_json"]
33+
with_slog_nested = ["with_slog", "slog/nested-values"]
3234
with_error_chain = ["error-chain", "with_backtrace"]
3335
with_device_info = ["libc", "hostname", "uname", "with_client_implementation"]
3436
with_rust_info = ["rustc_version", "with_client_implementation"]
@@ -44,6 +46,7 @@ failure = { version = "0.1.5", optional = true }
4446
log = { version = "0.4.6", optional = true, features = ["std"] }
4547
sentry-types = "0.11.0"
4648
env_logger = { version = "0.6.1", optional = true }
49+
slog = { version = "2.5.2", optional = true, default-features = false }
4750
reqwest = { version = "0.9.15", optional = true, default-features = false }
4851
lazy_static = "1.3.0"
4952
regex = { version = "1.1.6", optional = true }
@@ -76,5 +79,9 @@ actix-web = { version = "0.7.19", default-features = false }
7679
name = "error-chain-demo"
7780
required-features = ["with_error_chain"]
7881

82+
[[example]]
83+
name = "slog-demo"
84+
required-features = ["with_slog_nested"]
85+
7986
[workspace]
8087
members = [".", "integrations/sentry-actix"]

examples/slog-demo.rs

+20
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

+3
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

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
//! Adds support for automatic breadcrumb capturing from logs with `slog`.
2+
//!
3+
//! **Feature:** `with_slog` + `with_slog_nested` (optional)
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_str(&mut self, key: slog::Key, val: &str) -> slog::Result {
97+
self.emit_serde_json_string(key, val.to_string())
98+
}
99+
100+
#[allow(missing_docs)]
101+
fn emit_f64(&mut self, key: slog::Key, val: f64) -> slog::Result {
102+
if let Some(num) = serde_json::Number::from_f64(val) {
103+
self.emit_serde_json_value(key, serde_json::Value::Number(num))
104+
} else {
105+
self.emit_serde_json_null(key)
106+
}
107+
}
108+
109+
impl_number!(u8 => emit_u8);
110+
impl_number!(i8 => emit_i8);
111+
impl_number!(u16 => emit_u16);
112+
impl_number!(i16 => emit_i16);
113+
impl_number!(u32 => emit_u32);
114+
impl_number!(i32 => emit_i32);
115+
impl_number!(u64 => emit_u64);
116+
impl_number!(i64 => emit_i64);
117+
118+
// u128 and i128 should be implemented in serde_json 1.0.40
119+
// impl_number!(u128 => emit_u128);
120+
// impl_number!(i128 => emit_i128);
121+
122+
#[cfg(feature = "with_slog_nested")]
123+
#[allow(missing_docs)]
124+
fn emit_serde(&mut self, key: slog::Key, value: &dyn slog::SerdeValue) -> slog::Result {
125+
self.emit_serde_json_value(key, serde_json::json!(value.as_serde()))
126+
}
127+
128+
#[allow(missing_docs)]
129+
fn emit_arguments(&mut self, _: slog::Key, _: &fmt::Arguments) -> slog::Result {
130+
Ok(())
131+
}
132+
}
133+
134+
/// Converts `slog::Level` to `Level`
135+
fn into_sentry_level(slog_level: slog::Level) -> Level {
136+
match slog_level {
137+
slog::Level::Trace | slog::Level::Debug => Level::Debug,
138+
slog::Level::Info => Level::Info,
139+
slog::Level::Warning => Level::Warning,
140+
slog::Level::Error | slog::Level::Critical => Level::Error,
141+
}
142+
}
143+
144+
/// Options for the slog configuration
145+
#[derive(Debug, Copy, Clone)]
146+
pub struct Options {
147+
/// Level since when the breadcrumbs are created
148+
breadcrumb_level: slog::Level,
149+
/// Level since when the events are sent
150+
event_level: slog::Level,
151+
}
152+
153+
impl Options {
154+
/// Creates new slog integration options
155+
pub fn new(breadcrumb_level: slog::Level, event_level: slog::Level) -> Self {
156+
Self {
157+
breadcrumb_level,
158+
event_level,
159+
}
160+
}
161+
}
162+
163+
impl Default for Options {
164+
fn default() -> Self {
165+
Self {
166+
breadcrumb_level: slog::Level::Info,
167+
event_level: slog::Level::Warning,
168+
}
169+
}
170+
}
171+
172+
/// Wrapped drain for sentry logging
173+
#[derive(Debug, Copy, Clone)]
174+
pub struct WrappedDrain<D>
175+
where
176+
D: Drain,
177+
{
178+
drain: D,
179+
options: Options,
180+
}
181+
182+
fn get_record_data(record: &slog::Record) -> BTreeMap<String, serde_json::Value> {
183+
let mut storing_serializer = StoringSerializer {
184+
result: BTreeMap::new(),
185+
};
186+
187+
// slog::KV can be only serialized, but we need to obtain its data
188+
// To do that a Serializer was implemented to store these data to a dict
189+
if record
190+
.kv()
191+
.serialize(record, &mut storing_serializer)
192+
.is_ok()
193+
{
194+
storing_serializer.result
195+
} else {
196+
BTreeMap::new()
197+
}
198+
}
199+
200+
impl<D> WrappedDrain<D>
201+
where
202+
D: Drain,
203+
{
204+
/// Creates a new wrapped Drain
205+
fn new(drain: D, options: Options) -> Self {
206+
Self { drain, options }
207+
}
208+
209+
/// Creates a breadcrumb
210+
fn add_breadcrumb(record: &slog::Record) {
211+
let data = get_record_data(record);
212+
213+
let breadcrumb = Breadcrumb {
214+
message: Some(record.msg().to_string()),
215+
level: into_sentry_level(record.level()),
216+
data,
217+
..Breadcrumb::default()
218+
};
219+
add_breadcrumb(|| breadcrumb);
220+
}
221+
222+
/// Captures an event
223+
fn capture_event(record: &slog::Record) {
224+
let extra = get_record_data(record);
225+
226+
let event = Event {
227+
message: Some(record.msg().to_string()),
228+
level: into_sentry_level(record.level()),
229+
..Event::default()
230+
};
231+
232+
with_scope(
233+
|scope| {
234+
for (key, value) in extra {
235+
scope.set_extra(&key, value);
236+
}
237+
},
238+
|| {
239+
Hub::with_active(move |hub| hub.capture_event(event));
240+
},
241+
);
242+
}
243+
}
244+
245+
impl<D> Drain for WrappedDrain<D>
246+
where
247+
D: Drain,
248+
{
249+
type Ok = D::Ok;
250+
type Err = D::Err;
251+
252+
fn log(
253+
&self,
254+
record: &slog::Record,
255+
values: &slog::OwnedKVList,
256+
) -> Result<Self::Ok, Self::Err> {
257+
let level = record.level();
258+
if level <= self.options.event_level {
259+
// log event
260+
Self::capture_event(record);
261+
} else if level <= self.options.breadcrumb_level {
262+
// or log bread crumbs
263+
Self::add_breadcrumb(record);
264+
}
265+
266+
self.drain.log(record, values)
267+
}
268+
}
269+
270+
impl<D> std::ops::Deref for WrappedDrain<D>
271+
where
272+
D: Drain,
273+
{
274+
type Target = D;
275+
276+
fn deref(&self) -> &Self::Target {
277+
&self.drain
278+
}
279+
}
280+
281+
/// Wraps `slog::Drain`
282+
pub fn wrap_drain<D>(drain: D, options: Options) -> WrappedDrain<D>
283+
where
284+
D: slog::Drain,
285+
{
286+
WrappedDrain::new(drain, options)
287+
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@
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
112+
//! * `with_slog_nested`: enables the `slog` integration with nested feature compiled
111113
#![warn(missing_docs)]
112114

113115
#[macro_use]

0 commit comments

Comments
 (0)