sentry_slog/
converters.rs

1use sentry_core::protocol::{Breadcrumb, Event, Level, Map, Value};
2use slog::{Key, OwnedKVList, Record, Serializer, KV};
3use std::fmt;
4
5/// Converts a [`slog::Level`] to a Sentry [`Level`]
6pub fn convert_log_level(level: slog::Level) -> Level {
7    match level {
8        slog::Level::Trace | slog::Level::Debug => Level::Debug,
9        slog::Level::Info => Level::Info,
10        slog::Level::Warning => Level::Warning,
11        slog::Level::Error | slog::Level::Critical => Level::Error,
12    }
13}
14
15struct MapSerializer<'a>(&'a mut Map<String, Value>);
16
17macro_rules! impl_into {
18    ($t:ty => $f:ident) => {
19        fn $f(&mut self, key: Key, val: $t) -> slog::Result {
20            self.0.insert(key.into(), val.into());
21            Ok(())
22        }
23    };
24}
25impl Serializer for MapSerializer<'_> {
26    fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> slog::Result {
27        self.0.insert(key.into(), val.to_string().into());
28        Ok(())
29    }
30
31    fn emit_serde(&mut self, key: Key, val: &dyn slog::SerdeValue) -> slog::Result {
32        let value = serde_json::to_value(val.as_serde()).map_err(|_e| slog::Error::Other)?;
33        self.0.insert(key.into(), value);
34        Ok(())
35    }
36
37    impl_into! { usize => emit_usize }
38    impl_into! { isize => emit_isize }
39    impl_into! { bool  => emit_bool  }
40    impl_into! { u8    => emit_u8    }
41    impl_into! { i8    => emit_i8    }
42    impl_into! { u16   => emit_u16   }
43    impl_into! { i16   => emit_i16   }
44    impl_into! { u32   => emit_u32   }
45    impl_into! { i32   => emit_i32   }
46    impl_into! { f32   => emit_f32   }
47    impl_into! { u64   => emit_u64   }
48    impl_into! { i64   => emit_i64   }
49    impl_into! { f64   => emit_f64   }
50    impl_into! { &str  => emit_str   }
51}
52
53/// Adds the data from a [`slog::KV`] into a Sentry [`Map`].
54fn add_kv_to_map(map: &mut Map<String, Value>, record: &Record, kv: &impl KV) {
55    // TODO: Do something with these errors?
56    let _ = record.kv().serialize(record, &mut MapSerializer(map));
57    let _ = kv.serialize(record, &mut MapSerializer(map));
58}
59
60/// Creates a Sentry [`Breadcrumb`] from the [`Record`].
61pub fn breadcrumb_from_record(record: &Record, values: &OwnedKVList) -> Breadcrumb {
62    let mut data = Map::new();
63    add_kv_to_map(&mut data, record, values);
64
65    Breadcrumb {
66        ty: "log".into(),
67        message: Some(record.msg().to_string()),
68        level: convert_log_level(record.level()),
69        data,
70        ..Default::default()
71    }
72}
73
74/// Creates a simple message [`Event`] from the [`Record`].
75pub fn event_from_record(record: &Record, values: &OwnedKVList) -> Event<'static> {
76    let mut extra = Map::new();
77    add_kv_to_map(&mut extra, record, values);
78    Event {
79        message: Some(record.msg().to_string()),
80        level: convert_log_level(record.level()),
81        extra,
82        ..Default::default()
83    }
84}
85
86/// Creates an exception [`Event`] from the [`Record`].
87///
88/// # Examples
89///
90/// ```
91/// let args = format_args!("");
92/// let record = slog::record!(slog::Level::Error, "", &args, slog::b!());
93/// let kv = slog::o!().into();
94/// let event = sentry_slog::exception_from_record(&record, &kv);
95/// ```
96pub fn exception_from_record(record: &Record, values: &OwnedKVList) -> Event<'static> {
97    // TODO: Exception records in Sentry need a valid type, value and full stack trace to support
98    // proper grouping and issue metadata generation. log::Record does not contain sufficient
99    // information for this. However, it may contain a serialized error which we can parse to emit
100    // an exception record.
101    event_from_record(record, values)
102}
103
104#[cfg(test)]
105mod test {
106    use super::*;
107    use serde::Serialize;
108
109    use slog::{b, o, record, Level};
110
111    #[derive(Serialize, Clone)]
112    struct Something {
113        msg: String,
114        count: usize,
115    }
116
117    impl slog::Value for Something {
118        fn serialize(
119            &self,
120            _record: &Record,
121            key: Key,
122            serializer: &mut dyn slog::Serializer,
123        ) -> slog::Result {
124            serializer.emit_serde(key, self)
125        }
126    }
127
128    impl slog::SerdeValue for Something {
129        fn as_serde(&self) -> &dyn erased_serde::Serialize {
130            self
131        }
132
133        fn to_sendable(&self) -> Box<dyn slog::SerdeValue + Send + 'static> {
134            Box::new(self.clone())
135        }
136    }
137
138    #[test]
139    fn test_slog_kvs() {
140        let extras = o!("lib" => "sentry", "version" => 1, "test" => true);
141
142        let mut map: Map<String, Value> = Map::new();
143
144        add_kv_to_map(
145            &mut map,
146            &record!(
147                Level::Debug,
148                "test",
149                &format_args!("Hello, world!"),
150                b!("something" => &Something {
151                    msg: "message!".into(),
152                    count: 42,
153                })
154            ),
155            &extras,
156        );
157
158        assert_eq!(map.get("lib"), Some(&"sentry".into()));
159        assert_eq!(map.get("version"), Some(&1.into()));
160        assert_eq!(map.get("test"), Some(&true.into()));
161        assert_eq!(
162            map.get("something"),
163            Some(&Value::Object(
164                vec![
165                    ("msg".to_string(), Value::from("message!")),
166                    ("count".to_string(), Value::from(42))
167                ]
168                .into_iter()
169                .collect()
170            ))
171        )
172    }
173}