1use std::collections::BTreeMap;
2use std::error::Error;
3
4use sentry_core::protocol::{Event, Exception, Mechanism, Thread, Value};
5use sentry_core::{event_from_error, Breadcrumb, Level, TransactionOrSpan};
6use tracing_core::field::{Field, Visit};
7use tracing_core::Subscriber;
8use tracing_subscriber::layer::Context;
9use tracing_subscriber::registry::LookupSpan;
10
11use super::layer::SentrySpanData;
12use crate::TAGS_PREFIX;
13
14fn convert_tracing_level(level: &tracing_core::Level) -> Level {
16 match level {
17 &tracing_core::Level::TRACE | &tracing_core::Level::DEBUG => Level::Debug,
18 &tracing_core::Level::INFO => Level::Info,
19 &tracing_core::Level::WARN => Level::Warning,
20 &tracing_core::Level::ERROR => Level::Error,
21 }
22}
23
24fn level_to_exception_type(level: &tracing_core::Level) -> &'static str {
25 match *level {
26 tracing_core::Level::TRACE => "tracing::trace!",
27 tracing_core::Level::DEBUG => "tracing::debug!",
28 tracing_core::Level::INFO => "tracing::info!",
29 tracing_core::Level::WARN => "tracing::warn!",
30 tracing_core::Level::ERROR => "tracing::error!",
31 }
32}
33
34fn extract_event_data(event: &tracing_core::Event) -> (Option<String>, FieldVisitor) {
37 let mut visitor = FieldVisitor::default();
39 event.record(&mut visitor);
40 let message = visitor
41 .json_values
42 .remove("message")
43 .or_else(|| visitor.json_values.remove("error"))
46 .and_then(|v| match v {
47 Value::String(s) => Some(s),
48 _ => None,
49 });
50
51 (message, visitor)
52}
53
54fn extract_event_data_with_context<S>(
55 event: &tracing_core::Event,
56 ctx: Option<Context<S>>,
57) -> (Option<String>, FieldVisitor)
58where
59 S: Subscriber + for<'a> LookupSpan<'a>,
60{
61 let (message, mut visitor) = extract_event_data(event);
62
63 let current_span = ctx.as_ref().and_then(|ctx| {
65 event
66 .parent()
67 .and_then(|id| ctx.span(id))
68 .or_else(|| ctx.lookup_current())
69 });
70 if let Some(span) = current_span {
71 for span in span.scope() {
72 let name = span.name();
73 let ext = span.extensions();
74 if let Some(span_data) = ext.get::<SentrySpanData>() {
75 match &span_data.sentry_span {
76 TransactionOrSpan::Span(span) => {
77 for (key, value) in span.data().iter() {
78 if key != "message" {
79 let key = format!("{}:{}", name, key);
80 visitor.json_values.insert(key, value.clone());
81 }
82 }
83 }
84 TransactionOrSpan::Transaction(transaction) => {
85 for (key, value) in transaction.data().iter() {
86 if key != "message" {
87 let key = format!("{}:{}", name, key);
88 visitor.json_values.insert(key, value.clone());
89 }
90 }
91 }
92 }
93 }
94 }
95 }
96
97 (message, visitor)
98}
99
100#[derive(Default)]
102pub(crate) struct FieldVisitor {
103 pub json_values: BTreeMap<String, Value>,
104 pub exceptions: Vec<Exception>,
105}
106
107impl FieldVisitor {
108 fn record<T: Into<Value>>(&mut self, field: &Field, value: T) {
109 self.json_values
110 .insert(field.name().to_owned(), value.into());
111 }
112}
113
114impl Visit for FieldVisitor {
115 fn record_i64(&mut self, field: &Field, value: i64) {
116 self.record(field, value);
117 }
118
119 fn record_u64(&mut self, field: &Field, value: u64) {
120 self.record(field, value);
121 }
122
123 fn record_bool(&mut self, field: &Field, value: bool) {
124 self.record(field, value);
125 }
126
127 fn record_str(&mut self, field: &Field, value: &str) {
128 self.record(field, value);
129 }
130
131 fn record_error(&mut self, _field: &Field, value: &(dyn Error + 'static)) {
132 let event = event_from_error(value);
133 for exception in event.exception {
134 self.exceptions.push(exception);
135 }
136 }
137
138 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
139 self.record(field, format!("{value:?}"));
140 }
141}
142
143pub fn breadcrumb_from_event<'context, S>(
145 event: &tracing_core::Event,
146 ctx: impl Into<Option<Context<'context, S>>>,
147) -> Breadcrumb
148where
149 S: Subscriber + for<'a> LookupSpan<'a>,
150{
151 let (message, visitor) = extract_event_data_with_context(event, ctx.into());
152 Breadcrumb {
153 category: Some(event.metadata().target().to_owned()),
154 ty: "log".into(),
155 level: convert_tracing_level(event.metadata().level()),
156 message,
157 data: visitor.json_values,
158 ..Default::default()
159 }
160}
161
162fn tags_from_event(fields: &mut BTreeMap<String, Value>) -> BTreeMap<String, String> {
163 let mut tags = BTreeMap::new();
164
165 fields.retain(|key, value| {
166 let Some(key) = key.strip_prefix(TAGS_PREFIX) else {
167 return true;
168 };
169 let string = match value {
170 Value::Bool(b) => b.to_string(),
171 Value::Number(n) => n.to_string(),
172 Value::String(s) => std::mem::take(s),
173 Value::Null => return false,
175 Value::Array(_) | Value::Object(_) => return true,
177 };
178
179 tags.insert(key.to_owned(), string);
180
181 false
182 });
183
184 tags
185}
186
187fn contexts_from_event(
188 event: &tracing_core::Event,
189 fields: BTreeMap<String, Value>,
190) -> BTreeMap<String, sentry_core::protocol::Context> {
191 let event_meta = event.metadata();
192 let mut location_map = BTreeMap::new();
193 if let Some(module_path) = event_meta.module_path() {
194 location_map.insert("module_path".to_string(), module_path.into());
195 }
196 if let Some(file) = event_meta.file() {
197 location_map.insert("file".to_string(), file.into());
198 }
199 if let Some(line) = event_meta.line() {
200 location_map.insert("line".to_string(), line.into());
201 }
202
203 let mut context = BTreeMap::new();
204 if !fields.is_empty() {
205 context.insert(
206 "Rust Tracing Fields".to_string(),
207 sentry_core::protocol::Context::Other(fields),
208 );
209 }
210 if !location_map.is_empty() {
211 context.insert(
212 "Rust Tracing Location".to_string(),
213 sentry_core::protocol::Context::Other(location_map),
214 );
215 }
216 context
217}
218
219pub fn event_from_event<'context, S>(
221 event: &tracing_core::Event,
222 ctx: impl Into<Option<Context<'context, S>>>,
223) -> Event<'static>
224where
225 S: Subscriber + for<'a> LookupSpan<'a>,
226{
227 let (message, mut visitor) = extract_event_data_with_context(event, ctx.into());
228
229 Event {
230 logger: Some(event.metadata().target().to_owned()),
231 level: convert_tracing_level(event.metadata().level()),
232 message,
233 tags: tags_from_event(&mut visitor.json_values),
234 contexts: contexts_from_event(event, visitor.json_values),
235 ..Default::default()
236 }
237}
238
239pub fn exception_from_event<'context, S>(
241 event: &tracing_core::Event,
242 ctx: impl Into<Option<Context<'context, S>>>,
243) -> Event<'static>
244where
245 S: Subscriber + for<'a> LookupSpan<'a>,
246{
247 let (mut message, visitor) = extract_event_data_with_context(event, ctx.into());
252 let FieldVisitor {
253 mut exceptions,
254 mut json_values,
255 } = visitor;
256
257 if !exceptions.is_empty() && message.is_some() {
262 #[allow(unused_mut)]
263 let mut thread = Thread::default();
264
265 #[cfg(feature = "backtrace")]
266 if let Some(client) = sentry_core::Hub::current().client() {
267 if client.options().attach_stacktrace {
268 thread = sentry_backtrace::current_thread(true);
269 }
270 }
271
272 let exception = Exception {
273 ty: level_to_exception_type(event.metadata().level()).to_owned(),
274 value: message.take(),
275 module: event.metadata().module_path().map(str::to_owned),
276 stacktrace: thread.stacktrace,
277 raw_stacktrace: thread.raw_stacktrace,
278 thread_id: thread.id,
279 mechanism: Some(Mechanism {
280 synthetic: Some(true),
281 ..Mechanism::default()
282 }),
283 };
284
285 exceptions.push(exception);
286 }
287
288 if let Some(exception) = exceptions.last_mut() {
289 "tracing".clone_into(
290 &mut exception
291 .mechanism
292 .get_or_insert_with(Mechanism::default)
293 .ty,
294 );
295 }
296
297 Event {
298 logger: Some(event.metadata().target().to_owned()),
299 level: convert_tracing_level(event.metadata().level()),
300 message,
301 exception: exceptions.into(),
302 tags: tags_from_event(&mut json_values),
303 contexts: contexts_from_event(event, json_values),
304 ..Default::default()
305 }
306}