Skip to content

Commit 8999079

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

File tree

5 files changed

+327
-0
lines changed

5 files changed

+327
-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", "serde_json"]
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(), None);
7+
let _sentry = sentry::init((
8+
"https://a94ae32be2584e0bbd7a4cbb95971fee@sentry.io/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: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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(), None);
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, sync::Arc};
20+
21+
use crate::{
22+
hub::Hub,
23+
protocol::{Breadcrumb, Event},
24+
Level,
25+
};
26+
27+
// Serializer which stores the serde_json values in BTreeMap
28+
#[derive(Default)]
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+
#[allow(missing_docs)]
123+
fn emit_arguments(&mut self, _: slog::Key, _: &fmt::Arguments) -> slog::Result {
124+
Ok(())
125+
}
126+
}
127+
128+
/// Converts `slog::Level` to `Level`
129+
fn into_sentry_level(slog_level: slog::Level) -> Level {
130+
match slog_level {
131+
slog::Level::Trace | slog::Level::Debug => Level::Debug,
132+
slog::Level::Info => Level::Info,
133+
slog::Level::Warning => Level::Warning,
134+
slog::Level::Error | slog::Level::Critical => Level::Error,
135+
}
136+
}
137+
138+
/// Options for the slog configuration
139+
#[derive(Debug, Copy, Clone)]
140+
pub struct Options {
141+
/// Level since when the breadcrumbs are created
142+
breadcrumb_level: Option<slog::Level>,
143+
/// Level since when the events are sent
144+
event_level: Option<slog::Level>,
145+
}
146+
147+
impl Options {
148+
/// Creates new slog integration options
149+
pub fn new(breadcrumb_level: Option<slog::Level>, event_level: Option<slog::Level>) -> Self {
150+
Self {
151+
breadcrumb_level,
152+
event_level,
153+
}
154+
}
155+
}
156+
157+
impl Default for Options {
158+
fn default() -> Self {
159+
Self {
160+
breadcrumb_level: Some(slog::Level::Info),
161+
event_level: Some(slog::Level::Warning),
162+
}
163+
}
164+
}
165+
166+
/// Prepare data for sentry
167+
pub fn process_record(
168+
record: &slog::Record,
169+
kv: Option<&dyn slog::KV>,
170+
options: Options,
171+
hub: Option<Arc<Hub>>,
172+
) {
173+
let mut structured_data = BTreeMap::new();
174+
175+
// Append logger KVs
176+
if let Some(kv_data) = kv {
177+
let mut storing_serializer_1 = StoringSerializer::default();
178+
if kv_data.serialize(record, &mut storing_serializer_1).is_ok() {
179+
structured_data.append(&mut storing_serializer_1.result)
180+
}
181+
}
182+
183+
// Append record KVs
184+
let mut storing_serializer_2 = StoringSerializer::default();
185+
if record
186+
.kv()
187+
.serialize(record, &mut storing_serializer_2)
188+
.is_ok()
189+
{
190+
structured_data.append(&mut storing_serializer_2.result)
191+
}
192+
193+
// get hub
194+
let hub = if let Some(hub) = hub {
195+
hub
196+
} else {
197+
Hub::current()
198+
};
199+
200+
if options.event_level.is_some() && record.level() <= options.event_level.unwrap() {
201+
// Capture an event
202+
let event = Event {
203+
message: Some(record.msg().to_string()),
204+
level: into_sentry_level(record.level()),
205+
..Event::default()
206+
};
207+
208+
hub.with_scope(
209+
|scope| {
210+
for (key, value) in structured_data {
211+
scope.set_extra(&key, value);
212+
}
213+
},
214+
|| {
215+
hub.capture_event(event);
216+
},
217+
);
218+
} else if options.breadcrumb_level.is_some()
219+
&& record.level() <= options.breadcrumb_level.unwrap()
220+
{
221+
// Create a breadcrumb
222+
let breadcrumb = Breadcrumb {
223+
message: Some(record.msg().to_string()),
224+
level: into_sentry_level(record.level()),
225+
data: structured_data,
226+
..Breadcrumb::default()
227+
};
228+
229+
hub.add_breadcrumb(breadcrumb);
230+
}
231+
}
232+
233+
/// Wrapped drain for sentry logging
234+
#[derive(Debug, Clone)]
235+
pub struct WrappedDrain<D>
236+
where
237+
D: Drain,
238+
{
239+
drain: D,
240+
options: Options,
241+
hub: Option<Arc<Hub>>,
242+
}
243+
244+
impl<D> WrappedDrain<D>
245+
where
246+
D: Drain,
247+
{
248+
/// Creates a new wrapped Drain
249+
fn new(drain: D, options: Options, hub: Option<Arc<Hub>>) -> Self {
250+
Self {
251+
drain,
252+
options,
253+
hub,
254+
}
255+
}
256+
257+
/// Processes slog record
258+
fn process_record(&self, record: &slog::Record, kv: &slog::OwnedKVList) {
259+
process_record(record, Some(kv), self.options, self.hub.clone());
260+
}
261+
}
262+
263+
impl<D> Drain for WrappedDrain<D>
264+
where
265+
D: Drain,
266+
{
267+
type Ok = D::Ok;
268+
type Err = D::Err;
269+
270+
fn log(
271+
&self,
272+
record: &slog::Record,
273+
values: &slog::OwnedKVList,
274+
) -> Result<Self::Ok, Self::Err> {
275+
self.process_record(record, values);
276+
self.drain.log(record, values)
277+
}
278+
}
279+
280+
impl<D> std::ops::Deref for WrappedDrain<D>
281+
where
282+
D: Drain,
283+
{
284+
type Target = D;
285+
286+
fn deref(&self) -> &Self::Target {
287+
&self.drain
288+
}
289+
}
290+
291+
/// Wraps `slog::Drain`
292+
pub fn wrap_drain<D>(drain: D, options: Options, hub: Option<Arc<Hub>>) -> WrappedDrain<D>
293+
where
294+
D: slog::Drain,
295+
{
296+
WrappedDrain::new(drain, options, hub)
297+
}

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)