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