1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::ops::{Deref, DerefMut};
4use std::sync::{Arc, Mutex, MutexGuard};
5use std::time::SystemTime;
6
7use sentry_types::protocol::v7::SpanId;
8
9use crate::{protocol, Hub};
10
11#[cfg(feature = "client")]
12use crate::Client;
13
14#[cfg(feature = "client")]
15const MAX_SPANS: usize = 1_000;
16
17pub fn start_transaction(ctx: TransactionContext) -> Transaction {
26 #[cfg(feature = "client")]
27 {
28 let client = Hub::with_active(|hub| hub.client());
29 Transaction::new(client, ctx)
30 }
31 #[cfg(not(feature = "client"))]
32 {
33 Transaction::new_noop(ctx)
34 }
35}
36
37pub fn start_transaction_with_timestamp(
44 ctx: TransactionContext,
45 timestamp: SystemTime,
46) -> Transaction {
47 let transaction = start_transaction(ctx);
48 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
49 tx.start_timestamp = timestamp;
50 }
51 transaction
52}
53
54impl Hub {
57 pub fn start_transaction(&self, ctx: TransactionContext) -> Transaction {
61 #[cfg(feature = "client")]
62 {
63 Transaction::new(self.client(), ctx)
64 }
65 #[cfg(not(feature = "client"))]
66 {
67 Transaction::new_noop(ctx)
68 }
69 }
70
71 pub fn start_transaction_with_timestamp(
75 &self,
76 ctx: TransactionContext,
77 timestamp: SystemTime,
78 ) -> Transaction {
79 let transaction = start_transaction(ctx);
80 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
81 tx.start_timestamp = timestamp;
82 }
83 transaction
84 }
85}
86
87pub type CustomTransactionContext = serde_json::Map<String, serde_json::Value>;
95
96#[derive(Debug, Clone)]
101pub struct TransactionContext {
102 #[cfg_attr(not(feature = "client"), allow(dead_code))]
103 name: String,
104 op: String,
105 trace_id: protocol::TraceId,
106 parent_span_id: Option<protocol::SpanId>,
107 span_id: protocol::SpanId,
108 sampled: Option<bool>,
109 custom: Option<CustomTransactionContext>,
110}
111
112impl TransactionContext {
113 #[must_use = "this must be used with `start_transaction`"]
125 pub fn new(name: &str, op: &str) -> Self {
126 Self::new_with_trace_id(name, op, protocol::TraceId::default())
127 }
128
129 #[must_use = "this must be used with `start_transaction`"]
136 pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self {
137 Self {
138 name: name.into(),
139 op: op.into(),
140 trace_id,
141 parent_span_id: None,
142 span_id: Default::default(),
143 sampled: None,
144 custom: None,
145 }
146 }
147
148 #[must_use = "this must be used with `start_transaction`"]
156 pub fn new_with_details(
157 name: &str,
158 op: &str,
159 trace_id: protocol::TraceId,
160 span_id: Option<protocol::SpanId>,
161 parent_span_id: Option<protocol::SpanId>,
162 ) -> Self {
163 let mut slf = Self::new_with_trace_id(name, op, trace_id);
164 if let Some(span_id) = span_id {
165 slf.span_id = span_id;
166 }
167 slf.parent_span_id = parent_span_id;
168 slf
169 }
170
171 #[must_use = "this must be used with `start_transaction`"]
177 pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
178 name: &str,
179 op: &str,
180 headers: I,
181 ) -> Self {
182 parse_headers(headers)
183 .map(|sentry_trace| Self::continue_from_sentry_trace(name, op, &sentry_trace, None))
184 .unwrap_or_else(|| Self {
185 name: name.into(),
186 op: op.into(),
187 trace_id: Default::default(),
188 parent_span_id: None,
189 span_id: Default::default(),
190 sampled: None,
191 custom: None,
192 })
193 }
194
195 pub fn continue_from_sentry_trace(
198 name: &str,
199 op: &str,
200 sentry_trace: &SentryTrace,
201 span_id: Option<SpanId>,
202 ) -> Self {
203 let (trace_id, parent_span_id, sampled) =
204 (sentry_trace.0, Some(sentry_trace.1), sentry_trace.2);
205 Self {
206 name: name.into(),
207 op: op.into(),
208 trace_id,
209 parent_span_id,
210 sampled,
211 span_id: span_id.unwrap_or_default(),
212 custom: None,
213 }
214 }
215
216 pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
222 let span = match span {
223 Some(span) => span,
224 None => return Self::new(name, op),
225 };
226
227 let (trace_id, parent_span_id, sampled) = match span {
228 TransactionOrSpan::Transaction(transaction) => {
229 let inner = transaction.inner.lock().unwrap();
230 (
231 inner.context.trace_id,
232 inner.context.span_id,
233 Some(inner.sampled),
234 )
235 }
236 TransactionOrSpan::Span(span) => {
237 let sampled = span.sampled;
238 let span = span.span.lock().unwrap();
239 (span.trace_id, span.span_id, Some(sampled))
240 }
241 };
242
243 Self {
244 name: name.into(),
245 op: op.into(),
246 trace_id,
247 parent_span_id: Some(parent_span_id),
248 span_id: protocol::SpanId::default(),
249 sampled,
250 custom: None,
251 }
252 }
253
254 pub fn set_sampled(&mut self, sampled: impl Into<Option<bool>>) {
259 self.sampled = sampled.into();
260 }
261
262 pub fn sampled(&self) -> Option<bool> {
264 self.sampled
265 }
266
267 pub fn name(&self) -> &str {
269 &self.name
270 }
271
272 pub fn operation(&self) -> &str {
274 &self.op
275 }
276
277 pub fn trace_id(&self) -> protocol::TraceId {
279 self.trace_id
280 }
281
282 pub fn span_id(&self) -> protocol::SpanId {
284 self.span_id
285 }
286
287 pub fn custom(&self) -> Option<&CustomTransactionContext> {
289 self.custom.as_ref()
290 }
291
292 pub fn custom_mut(&mut self) -> &mut Option<CustomTransactionContext> {
296 &mut self.custom
297 }
298
299 pub fn custom_insert(
306 &mut self,
307 key: String,
308 value: serde_json::Value,
309 ) -> Option<serde_json::Value> {
310 let mut custom = None;
312 std::mem::swap(&mut self.custom, &mut custom);
313
314 let mut custom = custom.unwrap_or_default();
316
317 let existing_value = custom.insert(key, value);
319 std::mem::swap(&mut self.custom, &mut Some(custom));
320 existing_value
321 }
322
323 #[must_use]
330 pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
331 TransactionContextBuilder {
332 ctx: TransactionContext::new(name, op),
333 }
334 }
335}
336
337pub struct TransactionContextBuilder {
339 ctx: TransactionContext,
340}
341
342impl TransactionContextBuilder {
343 #[must_use]
345 pub fn with_name(mut self, name: String) -> Self {
346 self.ctx.name = name;
347 self
348 }
349
350 #[must_use]
352 pub fn with_op(mut self, op: String) -> Self {
353 self.ctx.op = op;
354 self
355 }
356
357 #[must_use]
359 pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
360 self.ctx.trace_id = trace_id;
361 self
362 }
363
364 #[must_use]
366 pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
367 self.ctx.parent_span_id = parent_span_id;
368 self
369 }
370
371 #[must_use]
373 pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
374 self.ctx.span_id = span_id;
375 self
376 }
377
378 #[must_use]
380 pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
381 self.ctx.sampled = sampled;
382 self
383 }
384
385 #[must_use]
387 pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
388 self.ctx.custom_insert(key, value);
389 self
390 }
391
392 pub fn finish(self) -> TransactionContext {
394 self.ctx
395 }
396}
397
398pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
404
405#[derive(Clone, Debug)]
409pub enum TransactionOrSpan {
410 Transaction(Transaction),
412 Span(Span),
414}
415
416impl From<Transaction> for TransactionOrSpan {
417 fn from(transaction: Transaction) -> Self {
418 Self::Transaction(transaction)
419 }
420}
421
422impl From<Span> for TransactionOrSpan {
423 fn from(span: Span) -> Self {
424 Self::Span(span)
425 }
426}
427
428impl TransactionOrSpan {
429 pub fn set_data(&self, key: &str, value: protocol::Value) {
431 match self {
432 TransactionOrSpan::Transaction(transaction) => transaction.set_data(key, value),
433 TransactionOrSpan::Span(span) => span.set_data(key, value),
434 }
435 }
436
437 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
439 match self {
440 TransactionOrSpan::Transaction(transaction) => transaction.set_tag(key, value),
441 TransactionOrSpan::Span(span) => span.set_tag(key, value),
442 }
443 }
444
445 pub fn get_trace_context(&self) -> protocol::TraceContext {
449 match self {
450 TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context(),
451 TransactionOrSpan::Span(span) => span.get_trace_context(),
452 }
453 }
454
455 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
457 match self {
458 TransactionOrSpan::Transaction(transaction) => transaction.get_status(),
459 TransactionOrSpan::Span(span) => span.get_status(),
460 }
461 }
462
463 pub fn set_status(&self, status: protocol::SpanStatus) {
465 match self {
466 TransactionOrSpan::Transaction(transaction) => transaction.set_status(status),
467 TransactionOrSpan::Span(span) => span.set_status(status),
468 }
469 }
470
471 pub fn set_request(&self, request: protocol::Request) {
473 match self {
474 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
475 TransactionOrSpan::Span(span) => span.set_request(request),
476 }
477 }
478
479 pub fn iter_headers(&self) -> TraceHeadersIter {
481 match self {
482 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
483 TransactionOrSpan::Span(span) => span.iter_headers(),
484 }
485 }
486
487 pub fn is_sampled(&self) -> bool {
489 match self {
490 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
491 TransactionOrSpan::Span(span) => span.is_sampled(),
492 }
493 }
494
495 #[must_use = "a span must be explicitly closed via `finish()`"]
500 pub fn start_child(&self, op: &str, description: &str) -> Span {
501 match self {
502 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
503 TransactionOrSpan::Span(span) => span.start_child(op, description),
504 }
505 }
506
507 #[must_use = "a span must be explicitly closed via `finish()`"]
512 pub fn start_child_with_details(
513 &self,
514 op: &str,
515 description: &str,
516 id: SpanId,
517 timestamp: SystemTime,
518 ) -> Span {
519 match self {
520 TransactionOrSpan::Transaction(transaction) => {
521 transaction.start_child_with_details(op, description, id, timestamp)
522 }
523 TransactionOrSpan::Span(span) => {
524 span.start_child_with_details(op, description, id, timestamp)
525 }
526 }
527 }
528
529 #[cfg(feature = "client")]
530 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
531 if event.contexts.contains_key("trace") {
532 return;
533 }
534
535 let context = match self {
536 TransactionOrSpan::Transaction(transaction) => {
537 transaction.inner.lock().unwrap().context.clone()
538 }
539 TransactionOrSpan::Span(span) => {
540 let span = span.span.lock().unwrap();
541 protocol::TraceContext {
542 span_id: span.span_id,
543 trace_id: span.trace_id,
544 ..Default::default()
545 }
546 }
547 };
548 event.contexts.insert("trace".into(), context.into());
549 }
550
551 pub fn finish_with_timestamp(self, timestamp: SystemTime) {
556 match self {
557 TransactionOrSpan::Transaction(transaction) => {
558 transaction.finish_with_timestamp(timestamp)
559 }
560 TransactionOrSpan::Span(span) => span.finish_with_timestamp(timestamp),
561 }
562 }
563
564 pub fn finish(self) {
569 match self {
570 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
571 TransactionOrSpan::Span(span) => span.finish(),
572 }
573 }
574}
575
576#[derive(Debug)]
577pub(crate) struct TransactionInner {
578 #[cfg(feature = "client")]
579 client: Option<Arc<Client>>,
580 sampled: bool,
581 pub(crate) context: protocol::TraceContext,
582 pub(crate) transaction: Option<protocol::Transaction<'static>>,
583}
584
585type TransactionArc = Arc<Mutex<TransactionInner>>;
586
587#[cfg(feature = "client")]
591fn transaction_sample_rate(
592 traces_sampler: Option<&TracesSampler>,
593 ctx: &TransactionContext,
594 traces_sample_rate: f32,
595) -> f32 {
596 match (traces_sampler, traces_sample_rate) {
597 (Some(traces_sampler), _) => traces_sampler(ctx),
598 (None, traces_sample_rate) => ctx
599 .sampled
600 .map(|sampled| if sampled { 1.0 } else { 0.0 })
601 .unwrap_or(traces_sample_rate),
602 }
603}
604
605#[cfg(feature = "client")]
607impl Client {
608 fn is_transaction_sampled(&self, ctx: &TransactionContext) -> bool {
609 let client_options = self.options();
610 self.sample_should_send(transaction_sample_rate(
611 client_options.traces_sampler.as_deref(),
612 ctx,
613 client_options.traces_sample_rate,
614 ))
615 }
616}
617
618#[derive(Clone, Debug)]
624pub struct Transaction {
625 pub(crate) inner: TransactionArc,
626}
627
628pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
630
631impl<'a> TransactionData<'a> {
632 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
639 if self.0.transaction.is_some() {
640 Box::new(self.0.context.data.iter())
641 } else {
642 Box::new(std::iter::empty())
643 }
644 }
645
646 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
648 if self.0.transaction.is_some() {
649 self.0.context.data.insert(key.into(), value);
650 }
651 }
652
653 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
655 if let Some(transaction) = self.0.transaction.as_mut() {
656 transaction.tags.insert(key.into(), value);
657 }
658 }
659}
660
661impl Transaction {
662 #[cfg(feature = "client")]
663 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
664 let (sampled, transaction) = match client.as_ref() {
665 Some(client) => (
666 client.is_transaction_sampled(&ctx),
667 Some(protocol::Transaction {
668 name: Some(ctx.name),
669 ..Default::default()
670 }),
671 ),
672 None => (ctx.sampled.unwrap_or(false), None),
673 };
674
675 let context = protocol::TraceContext {
676 trace_id: ctx.trace_id,
677 parent_span_id: ctx.parent_span_id,
678 span_id: ctx.span_id,
679 op: Some(ctx.op),
680 ..Default::default()
681 };
682
683 Self {
684 inner: Arc::new(Mutex::new(TransactionInner {
685 client,
686 sampled,
687 context,
688 transaction,
689 })),
690 }
691 }
692
693 #[cfg(not(feature = "client"))]
694 fn new_noop(ctx: TransactionContext) -> Self {
695 let context = protocol::TraceContext {
696 trace_id: ctx.trace_id,
697 parent_span_id: ctx.parent_span_id,
698 op: Some(ctx.op),
699 ..Default::default()
700 };
701 let sampled = ctx.sampled.unwrap_or(false);
702
703 Self {
704 inner: Arc::new(Mutex::new(TransactionInner {
705 sampled,
706 context,
707 transaction: None,
708 })),
709 }
710 }
711
712 pub fn set_data(&self, key: &str, value: protocol::Value) {
714 let mut inner = self.inner.lock().unwrap();
715 if inner.transaction.is_some() {
716 inner.context.data.insert(key.into(), value);
717 }
718 }
719
720 pub fn set_extra(&self, key: &str, value: protocol::Value) {
722 let mut inner = self.inner.lock().unwrap();
723 if let Some(transaction) = inner.transaction.as_mut() {
724 transaction.extra.insert(key.into(), value);
725 }
726 }
727
728 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
730 let mut inner = self.inner.lock().unwrap();
731 if let Some(transaction) = inner.transaction.as_mut() {
732 transaction.tags.insert(key.into(), value.to_string());
733 }
734 }
735
736 pub fn data(&self) -> TransactionData {
746 TransactionData(self.inner.lock().unwrap())
747 }
748
749 pub fn get_trace_context(&self) -> protocol::TraceContext {
753 let inner = self.inner.lock().unwrap();
754 inner.context.clone()
755 }
756
757 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
759 let inner = self.inner.lock().unwrap();
760 inner.context.status
761 }
762
763 pub fn set_status(&self, status: protocol::SpanStatus) {
765 let mut inner = self.inner.lock().unwrap();
766 inner.context.status = Some(status);
767 }
768
769 pub fn set_request(&self, request: protocol::Request) {
771 let mut inner = self.inner.lock().unwrap();
772 if let Some(transaction) = inner.transaction.as_mut() {
773 transaction.request = Some(request);
774 }
775 }
776
777 pub fn iter_headers(&self) -> TraceHeadersIter {
779 let inner = self.inner.lock().unwrap();
780 let trace = SentryTrace(
781 inner.context.trace_id,
782 inner.context.span_id,
783 Some(inner.sampled),
784 );
785 TraceHeadersIter {
786 sentry_trace: Some(trace.to_string()),
787 }
788 }
789
790 pub fn is_sampled(&self) -> bool {
792 self.inner.lock().unwrap().sampled
793 }
794
795 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
800 with_client_impl! {{
801 let mut inner = self.inner.lock().unwrap();
802
803 if !inner.sampled {
805 return;
806 }
807
808 if let Some(mut transaction) = inner.transaction.take() {
809 if let Some(client) = inner.client.take() {
810 transaction.finish_with_timestamp(_timestamp);
811 transaction
812 .contexts
813 .insert("trace".into(), inner.context.clone().into());
814
815 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
816 let opts = client.options();
817 transaction.release.clone_from(&opts.release);
818 transaction.environment.clone_from(&opts.environment);
819 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
820 transaction.server_name.clone_from(&opts.server_name);
821
822 drop(inner);
823
824 let mut envelope = protocol::Envelope::new();
825 envelope.add_item(transaction);
826
827 client.send_envelope(envelope)
828 }
829 }
830 }}
831 }
832
833 pub fn finish(self) {
838 self.finish_with_timestamp(SystemTime::now());
839 }
840
841 #[must_use = "a span must be explicitly closed via `finish()`"]
845 pub fn start_child(&self, op: &str, description: &str) -> Span {
846 let inner = self.inner.lock().unwrap();
847 let span = protocol::Span {
848 trace_id: inner.context.trace_id,
849 parent_span_id: Some(inner.context.span_id),
850 op: Some(op.into()),
851 description: if description.is_empty() {
852 None
853 } else {
854 Some(description.into())
855 },
856 ..Default::default()
857 };
858 Span {
859 transaction: Arc::clone(&self.inner),
860 sampled: inner.sampled,
861 span: Arc::new(Mutex::new(span)),
862 }
863 }
864
865 #[must_use = "a span must be explicitly closed via `finish()`"]
869 pub fn start_child_with_details(
870 &self,
871 op: &str,
872 description: &str,
873 id: SpanId,
874 timestamp: SystemTime,
875 ) -> Span {
876 let inner = self.inner.lock().unwrap();
877 let span = protocol::Span {
878 trace_id: inner.context.trace_id,
879 parent_span_id: Some(inner.context.span_id),
880 op: Some(op.into()),
881 description: if description.is_empty() {
882 None
883 } else {
884 Some(description.into())
885 },
886 span_id: id,
887 start_timestamp: timestamp,
888 ..Default::default()
889 };
890 Span {
891 transaction: Arc::clone(&self.inner),
892 sampled: inner.sampled,
893 span: Arc::new(Mutex::new(span)),
894 }
895 }
896}
897
898pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
900
901impl Data<'_> {
902 pub fn set_data(&mut self, key: String, value: protocol::Value) {
904 self.0.data.insert(key, value);
905 }
906
907 pub fn set_tag(&mut self, key: String, value: String) {
909 self.0.tags.insert(key, value);
910 }
911}
912
913impl Deref for Data<'_> {
914 type Target = BTreeMap<String, protocol::Value>;
915
916 fn deref(&self) -> &Self::Target {
917 &self.0.data
918 }
919}
920
921impl DerefMut for Data<'_> {
922 fn deref_mut(&mut self) -> &mut Self::Target {
923 &mut self.0.data
924 }
925}
926
927#[derive(Clone, Debug)]
932pub struct Span {
933 pub(crate) transaction: TransactionArc,
934 sampled: bool,
935 span: SpanArc,
936}
937
938type SpanArc = Arc<Mutex<protocol::Span>>;
939
940impl Span {
941 pub fn set_data(&self, key: &str, value: protocol::Value) {
943 let mut span = self.span.lock().unwrap();
944 span.data.insert(key.into(), value);
945 }
946
947 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
949 let mut span = self.span.lock().unwrap();
950 span.tags.insert(key.into(), value.to_string());
951 }
952
953 pub fn data(&self) -> Data {
965 Data(self.span.lock().unwrap())
966 }
967
968 pub fn get_trace_context(&self) -> protocol::TraceContext {
972 let transaction = self.transaction.lock().unwrap();
973 transaction.context.clone()
974 }
975
976 pub fn get_span_id(&self) -> protocol::SpanId {
978 let span = self.span.lock().unwrap();
979 span.span_id
980 }
981
982 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
984 let span = self.span.lock().unwrap();
985 span.status
986 }
987
988 pub fn set_status(&self, status: protocol::SpanStatus) {
990 let mut span = self.span.lock().unwrap();
991 span.status = Some(status);
992 }
993
994 pub fn set_request(&self, request: protocol::Request) {
996 let mut span = self.span.lock().unwrap();
997 if let Some(method) = request.method {
999 span.data.insert("method".into(), method.into());
1000 }
1001 if let Some(url) = request.url {
1002 span.data.insert("url".into(), url.to_string().into());
1003 }
1004 if let Some(data) = request.data {
1005 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1006 span.data.insert("data".into(), data);
1007 } else {
1008 span.data.insert("data".into(), data.into());
1009 }
1010 }
1011 if let Some(query_string) = request.query_string {
1012 span.data.insert("query_string".into(), query_string.into());
1013 }
1014 if let Some(cookies) = request.cookies {
1015 span.data.insert("cookies".into(), cookies.into());
1016 }
1017 if !request.headers.is_empty() {
1018 if let Ok(headers) = serde_json::to_value(request.headers) {
1019 span.data.insert("headers".into(), headers);
1020 }
1021 }
1022 if !request.env.is_empty() {
1023 if let Ok(env) = serde_json::to_value(request.env) {
1024 span.data.insert("env".into(), env);
1025 }
1026 }
1027 }
1028
1029 pub fn iter_headers(&self) -> TraceHeadersIter {
1031 let span = self.span.lock().unwrap();
1032 let trace = SentryTrace(span.trace_id, span.span_id, Some(self.sampled));
1033 TraceHeadersIter {
1034 sentry_trace: Some(trace.to_string()),
1035 }
1036 }
1037
1038 pub fn is_sampled(&self) -> bool {
1040 self.sampled
1041 }
1042
1043 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1048 with_client_impl! {{
1049 let mut span = self.span.lock().unwrap();
1050 if span.timestamp.is_some() {
1051 return;
1053 }
1054 span.finish_with_timestamp(_timestamp);
1055 let mut inner = self.transaction.lock().unwrap();
1056 if let Some(transaction) = inner.transaction.as_mut() {
1057 if transaction.spans.len() <= MAX_SPANS {
1058 transaction.spans.push(span.clone());
1059 }
1060 }
1061 }}
1062 }
1063
1064 pub fn finish(self) {
1069 self.finish_with_timestamp(SystemTime::now());
1070 }
1071
1072 #[must_use = "a span must be explicitly closed via `finish()`"]
1076 pub fn start_child(&self, op: &str, description: &str) -> Span {
1077 let span = self.span.lock().unwrap();
1078 let span = protocol::Span {
1079 trace_id: span.trace_id,
1080 parent_span_id: Some(span.span_id),
1081 op: Some(op.into()),
1082 description: if description.is_empty() {
1083 None
1084 } else {
1085 Some(description.into())
1086 },
1087 ..Default::default()
1088 };
1089 Span {
1090 transaction: self.transaction.clone(),
1091 sampled: self.sampled,
1092 span: Arc::new(Mutex::new(span)),
1093 }
1094 }
1095
1096 #[must_use = "a span must be explicitly closed via `finish()`"]
1100 fn start_child_with_details(
1101 &self,
1102 op: &str,
1103 description: &str,
1104 id: SpanId,
1105 timestamp: SystemTime,
1106 ) -> Span {
1107 let span = self.span.lock().unwrap();
1108 let span = protocol::Span {
1109 trace_id: span.trace_id,
1110 parent_span_id: Some(span.span_id),
1111 op: Some(op.into()),
1112 description: if description.is_empty() {
1113 None
1114 } else {
1115 Some(description.into())
1116 },
1117 span_id: id,
1118 start_timestamp: timestamp,
1119 ..Default::default()
1120 };
1121 Span {
1122 transaction: self.transaction.clone(),
1123 sampled: self.sampled,
1124 span: Arc::new(Mutex::new(span)),
1125 }
1126 }
1127}
1128
1129pub struct TraceHeadersIter {
1134 sentry_trace: Option<String>,
1135}
1136
1137impl Iterator for TraceHeadersIter {
1138 type Item = (&'static str, String);
1139
1140 fn next(&mut self) -> Option<Self::Item> {
1141 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1142 }
1143}
1144
1145#[derive(Debug, PartialEq)]
1148pub struct SentryTrace(protocol::TraceId, protocol::SpanId, Option<bool>);
1149
1150impl SentryTrace {
1151 pub fn new(
1153 trace_id: protocol::TraceId,
1154 span_id: protocol::SpanId,
1155 sampled: Option<bool>,
1156 ) -> Self {
1157 SentryTrace(trace_id, span_id, sampled)
1158 }
1159}
1160
1161fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1162 let header = header.trim();
1163 let mut parts = header.splitn(3, '-');
1164
1165 let trace_id = parts.next()?.parse().ok()?;
1166 let parent_span_id = parts.next()?.parse().ok()?;
1167 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1168 "1" => Some(true),
1169 "0" => Some(false),
1170 _ => None,
1171 });
1172
1173 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
1174}
1175
1176fn parse_w3c_traceparent(header: &str) -> Option<SentryTrace> {
1179 let header = header.trim();
1180 let mut parts = header.splitn(4, '-');
1181
1182 let _version = parts.next()?;
1183 let trace_id = parts.next()?.parse().ok()?;
1184 let parent_span_id = parts.next()?.parse().ok()?;
1185 let parent_sampled = parts
1186 .next()
1187 .and_then(|sampled| u8::from_str_radix(sampled, 16).ok())
1188 .map(|flag| flag & 1 != 0);
1189
1190 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
1191}
1192
1193pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1196 headers: I,
1197) -> Option<SentryTrace> {
1198 let mut trace = None;
1199 for (k, v) in headers.into_iter() {
1200 if k.eq_ignore_ascii_case("sentry-trace") {
1201 trace = parse_sentry_trace(v);
1202 break;
1203 }
1204
1205 if k.eq_ignore_ascii_case("traceparent") {
1206 trace = parse_w3c_traceparent(v);
1207 }
1208 }
1209 trace
1210}
1211
1212impl std::fmt::Display for SentryTrace {
1213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1214 write!(f, "{}-{}", self.0, self.1)?;
1215 if let Some(sampled) = self.2 {
1216 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1217 }
1218 Ok(())
1219 }
1220}
1221
1222#[cfg(test)]
1223mod tests {
1224 use std::str::FromStr;
1225
1226 use super::*;
1227
1228 #[test]
1229 fn parses_sentry_trace() {
1230 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1231 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1232
1233 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1234 assert_eq!(
1235 trace,
1236 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1237 );
1238
1239 let trace = SentryTrace(Default::default(), Default::default(), None);
1240 let parsed = parse_sentry_trace(&trace.to_string());
1241 assert_eq!(parsed, Some(trace));
1242 }
1243
1244 #[test]
1245 fn parses_traceparent() {
1246 let trace_id = protocol::TraceId::from_str("4bf92f3577b34da6a3ce929d0e0e4736").unwrap();
1247 let parent_trace_id = protocol::SpanId::from_str("00f067aa0ba902b7").unwrap();
1248
1249 let trace =
1250 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
1251 assert_eq!(
1252 trace,
1253 Some(SentryTrace(trace_id, parent_trace_id, Some(true)))
1254 );
1255
1256 let trace =
1257 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00");
1258 assert_eq!(
1259 trace,
1260 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1261 );
1262 }
1263
1264 #[test]
1265 fn disabled_forwards_trace_id() {
1266 let headers = [(
1267 "SenTrY-TRAce",
1268 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1269 )];
1270 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1271 let trx = start_transaction(ctx);
1272
1273 let span = trx.start_child("noop", "noop");
1274
1275 let header = span.iter_headers().next().unwrap().1;
1276 let parsed = parse_sentry_trace(&header).unwrap();
1277
1278 assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
1279 assert_eq!(parsed.2, Some(true));
1280 }
1281
1282 #[test]
1283 fn transaction_context_public_getters() {
1284 let mut ctx = TransactionContext::new("test-name", "test-operation");
1285 assert_eq!(ctx.name(), "test-name");
1286 assert_eq!(ctx.operation(), "test-operation");
1287 assert_eq!(ctx.sampled(), None);
1288
1289 ctx.set_sampled(true);
1290 assert_eq!(ctx.sampled(), Some(true));
1291 }
1292
1293 #[cfg(feature = "client")]
1294 #[test]
1295 fn compute_transaction_sample_rate() {
1296 let ctx = TransactionContext::new("noop", "noop");
1298 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1299 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1300
1301 let mut ctx = TransactionContext::new("noop", "noop");
1303 ctx.set_sampled(true);
1304 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1305 ctx.set_sampled(false);
1306 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1307
1308 let mut ctx = TransactionContext::new("noop", "noop");
1310 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1311 ctx.set_sampled(false);
1312 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1313 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1315 Some(true) => 0.8,
1316 Some(false) => 0.4,
1317 None => 0.6,
1318 };
1319 ctx.set_sampled(true);
1320 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1321 ctx.set_sampled(None);
1322 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1323
1324 let sampler = |ctx: &TransactionContext| {
1326 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1327 return 1.0;
1328 }
1329
1330 if let Some(custom) = ctx.custom() {
1331 if let Some(rate) = custom.get("rate") {
1332 if let Some(rate) = rate.as_f64() {
1333 return rate as f32;
1334 }
1335 }
1336 }
1337
1338 0.1
1339 };
1340 let ctx = TransactionContext::new("noop", "must-operation");
1342 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1343 let ctx = TransactionContext::new("must-name", "noop");
1344 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1345 let mut ctx = TransactionContext::new("noop", "noop");
1347 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1348 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1349 }
1350}