1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::ops::{Deref, DerefMut};
4use std::sync::{Arc, Mutex, MutexGuard};
5
6use crate::{protocol, Hub};
7
8#[cfg(feature = "client")]
9use crate::Client;
10
11#[cfg(feature = "client")]
12const MAX_SPANS: usize = 1_000;
13
14pub fn start_transaction(ctx: TransactionContext) -> Transaction {
23 #[cfg(feature = "client")]
24 {
25 let client = Hub::with_active(|hub| hub.client());
26 Transaction::new(client, ctx)
27 }
28 #[cfg(not(feature = "client"))]
29 {
30 Transaction::new_noop(ctx)
31 }
32}
33
34impl Hub {
37 pub fn start_transaction(&self, ctx: TransactionContext) -> Transaction {
41 #[cfg(feature = "client")]
42 {
43 Transaction::new(self.client(), ctx)
44 }
45 #[cfg(not(feature = "client"))]
46 {
47 Transaction::new_noop(ctx)
48 }
49 }
50}
51
52pub type CustomTransactionContext = serde_json::Map<String, serde_json::Value>;
60
61#[derive(Debug, Clone)]
66pub struct TransactionContext {
67 #[cfg_attr(not(feature = "client"), allow(dead_code))]
68 name: String,
69 op: String,
70 trace_id: protocol::TraceId,
71 parent_span_id: Option<protocol::SpanId>,
72 span_id: protocol::SpanId,
73 sampled: Option<bool>,
74 custom: Option<CustomTransactionContext>,
75}
76
77impl TransactionContext {
78 #[must_use = "this must be used with `start_transaction`"]
90 pub fn new(name: &str, op: &str) -> Self {
91 Self::new_with_trace_id(name, op, protocol::TraceId::default())
92 }
93
94 #[must_use = "this must be used with `start_transaction`"]
101 pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self {
102 Self {
103 name: name.into(),
104 op: op.into(),
105 trace_id,
106 parent_span_id: None,
107 span_id: Default::default(),
108 sampled: None,
109 custom: None,
110 }
111 }
112
113 #[must_use = "this must be used with `start_transaction`"]
119 pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
120 name: &str,
121 op: &str,
122 headers: I,
123 ) -> Self {
124 let mut trace = None;
125 for (k, v) in headers.into_iter() {
126 if k.eq_ignore_ascii_case("sentry-trace") {
127 trace = parse_sentry_trace(v);
128 break;
129 }
130
131 if k.eq_ignore_ascii_case("traceparent") {
132 trace = parse_w3c_traceparent(v);
133 }
134 }
135
136 let (trace_id, parent_span_id, sampled) = match trace {
137 Some(trace) => (trace.0, Some(trace.1), trace.2),
138 None => (protocol::TraceId::default(), None, None),
139 };
140
141 Self {
142 name: name.into(),
143 op: op.into(),
144 trace_id,
145 parent_span_id,
146 span_id: Default::default(),
147 sampled,
148 custom: None,
149 }
150 }
151
152 pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
158 let span = match span {
159 Some(span) => span,
160 None => return Self::new(name, op),
161 };
162
163 let (trace_id, parent_span_id, sampled) = match span {
164 TransactionOrSpan::Transaction(transaction) => {
165 let inner = transaction.inner.lock().unwrap();
166 (
167 inner.context.trace_id,
168 inner.context.span_id,
169 Some(inner.sampled),
170 )
171 }
172 TransactionOrSpan::Span(span) => {
173 let sampled = span.sampled;
174 let span = span.span.lock().unwrap();
175 (span.trace_id, span.span_id, Some(sampled))
176 }
177 };
178
179 Self {
180 name: name.into(),
181 op: op.into(),
182 trace_id,
183 parent_span_id: Some(parent_span_id),
184 span_id: protocol::SpanId::default(),
185 sampled,
186 custom: None,
187 }
188 }
189
190 pub fn set_sampled(&mut self, sampled: impl Into<Option<bool>>) {
195 self.sampled = sampled.into();
196 }
197
198 pub fn sampled(&self) -> Option<bool> {
200 self.sampled
201 }
202
203 pub fn name(&self) -> &str {
205 &self.name
206 }
207
208 pub fn operation(&self) -> &str {
210 &self.op
211 }
212
213 pub fn trace_id(&self) -> protocol::TraceId {
215 self.trace_id
216 }
217
218 pub fn span_id(&self) -> protocol::SpanId {
220 self.span_id
221 }
222
223 pub fn custom(&self) -> Option<&CustomTransactionContext> {
225 self.custom.as_ref()
226 }
227
228 pub fn custom_mut(&mut self) -> &mut Option<CustomTransactionContext> {
232 &mut self.custom
233 }
234
235 pub fn custom_insert(
242 &mut self,
243 key: String,
244 value: serde_json::Value,
245 ) -> Option<serde_json::Value> {
246 let mut custom = None;
248 std::mem::swap(&mut self.custom, &mut custom);
249
250 let mut custom = custom.unwrap_or_default();
252
253 let existing_value = custom.insert(key, value);
255 std::mem::swap(&mut self.custom, &mut Some(custom));
256 existing_value
257 }
258
259 #[must_use]
266 pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
267 TransactionContextBuilder {
268 ctx: TransactionContext::new(name, op),
269 }
270 }
271}
272
273pub struct TransactionContextBuilder {
275 ctx: TransactionContext,
276}
277
278impl TransactionContextBuilder {
279 #[must_use]
281 pub fn with_name(mut self, name: String) -> Self {
282 self.ctx.name = name;
283 self
284 }
285
286 #[must_use]
288 pub fn with_op(mut self, op: String) -> Self {
289 self.ctx.op = op;
290 self
291 }
292
293 #[must_use]
295 pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
296 self.ctx.trace_id = trace_id;
297 self
298 }
299
300 #[must_use]
302 pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
303 self.ctx.parent_span_id = parent_span_id;
304 self
305 }
306
307 #[must_use]
309 pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
310 self.ctx.span_id = span_id;
311 self
312 }
313
314 #[must_use]
316 pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
317 self.ctx.sampled = sampled;
318 self
319 }
320
321 #[must_use]
323 pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
324 self.ctx.custom_insert(key, value);
325 self
326 }
327
328 pub fn finish(self) -> TransactionContext {
330 self.ctx
331 }
332}
333
334pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
340
341#[derive(Clone, Debug)]
345pub enum TransactionOrSpan {
346 Transaction(Transaction),
348 Span(Span),
350}
351
352impl From<Transaction> for TransactionOrSpan {
353 fn from(transaction: Transaction) -> Self {
354 Self::Transaction(transaction)
355 }
356}
357
358impl From<Span> for TransactionOrSpan {
359 fn from(span: Span) -> Self {
360 Self::Span(span)
361 }
362}
363
364impl TransactionOrSpan {
365 pub fn set_data(&self, key: &str, value: protocol::Value) {
367 match self {
368 TransactionOrSpan::Transaction(transaction) => transaction.set_data(key, value),
369 TransactionOrSpan::Span(span) => span.set_data(key, value),
370 }
371 }
372
373 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
375 match self {
376 TransactionOrSpan::Transaction(transaction) => transaction.set_tag(key, value),
377 TransactionOrSpan::Span(span) => span.set_tag(key, value),
378 }
379 }
380
381 pub fn get_trace_context(&self) -> protocol::TraceContext {
385 match self {
386 TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context(),
387 TransactionOrSpan::Span(span) => span.get_trace_context(),
388 }
389 }
390
391 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
393 match self {
394 TransactionOrSpan::Transaction(transaction) => transaction.get_status(),
395 TransactionOrSpan::Span(span) => span.get_status(),
396 }
397 }
398
399 pub fn set_status(&self, status: protocol::SpanStatus) {
401 match self {
402 TransactionOrSpan::Transaction(transaction) => transaction.set_status(status),
403 TransactionOrSpan::Span(span) => span.set_status(status),
404 }
405 }
406
407 pub fn set_request(&self, request: protocol::Request) {
409 match self {
410 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
411 TransactionOrSpan::Span(span) => span.set_request(request),
412 }
413 }
414
415 pub fn iter_headers(&self) -> TraceHeadersIter {
417 match self {
418 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
419 TransactionOrSpan::Span(span) => span.iter_headers(),
420 }
421 }
422
423 pub fn is_sampled(&self) -> bool {
425 match self {
426 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
427 TransactionOrSpan::Span(span) => span.is_sampled(),
428 }
429 }
430
431 #[must_use = "a span must be explicitly closed via `finish()`"]
436 pub fn start_child(&self, op: &str, description: &str) -> Span {
437 match self {
438 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
439 TransactionOrSpan::Span(span) => span.start_child(op, description),
440 }
441 }
442
443 #[cfg(feature = "client")]
444 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
445 if event.contexts.contains_key("trace") {
446 return;
447 }
448
449 let context = match self {
450 TransactionOrSpan::Transaction(transaction) => {
451 transaction.inner.lock().unwrap().context.clone()
452 }
453 TransactionOrSpan::Span(span) => {
454 let span = span.span.lock().unwrap();
455 protocol::TraceContext {
456 span_id: span.span_id,
457 trace_id: span.trace_id,
458 ..Default::default()
459 }
460 }
461 };
462 event.contexts.insert("trace".into(), context.into());
463 }
464
465 pub fn finish(self) {
470 match self {
471 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
472 TransactionOrSpan::Span(span) => span.finish(),
473 }
474 }
475}
476
477#[derive(Debug)]
478pub(crate) struct TransactionInner {
479 #[cfg(feature = "client")]
480 client: Option<Arc<Client>>,
481 sampled: bool,
482 pub(crate) context: protocol::TraceContext,
483 pub(crate) transaction: Option<protocol::Transaction<'static>>,
484}
485
486type TransactionArc = Arc<Mutex<TransactionInner>>;
487
488#[cfg(feature = "client")]
492fn transaction_sample_rate(
493 traces_sampler: Option<&TracesSampler>,
494 ctx: &TransactionContext,
495 traces_sample_rate: f32,
496) -> f32 {
497 match (traces_sampler, traces_sample_rate) {
498 (Some(traces_sampler), _) => traces_sampler(ctx),
499 (None, traces_sample_rate) => ctx
500 .sampled
501 .map(|sampled| if sampled { 1.0 } else { 0.0 })
502 .unwrap_or(traces_sample_rate),
503 }
504}
505
506#[cfg(feature = "client")]
508impl Client {
509 fn is_transaction_sampled(&self, ctx: &TransactionContext) -> bool {
510 let client_options = self.options();
511 self.sample_should_send(transaction_sample_rate(
512 client_options.traces_sampler.as_deref(),
513 ctx,
514 client_options.traces_sample_rate,
515 ))
516 }
517}
518
519#[derive(Clone, Debug)]
525pub struct Transaction {
526 pub(crate) inner: TransactionArc,
527}
528
529pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
531
532impl<'a> TransactionData<'a> {
533 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
540 if let Some(ref rx) = self.0.transaction {
541 Box::new(rx.extra.iter())
542 } else {
543 Box::new(std::iter::empty())
544 }
545 }
546
547 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
549 if let Some(transaction) = self.0.transaction.as_mut() {
550 transaction.extra.insert(key.into(), value);
551 }
552 }
553
554 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
556 if let Some(transaction) = self.0.transaction.as_mut() {
557 transaction.tags.insert(key.into(), value);
558 }
559 }
560}
561
562impl Transaction {
563 #[cfg(feature = "client")]
564 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
565 let (sampled, transaction) = match client.as_ref() {
566 Some(client) => (
567 client.is_transaction_sampled(&ctx),
568 Some(protocol::Transaction {
569 name: Some(ctx.name),
570 ..Default::default()
571 }),
572 ),
573 None => (ctx.sampled.unwrap_or(false), None),
574 };
575
576 let context = protocol::TraceContext {
577 trace_id: ctx.trace_id,
578 parent_span_id: ctx.parent_span_id,
579 span_id: ctx.span_id,
580 op: Some(ctx.op),
581 ..Default::default()
582 };
583
584 Self {
585 inner: Arc::new(Mutex::new(TransactionInner {
586 client,
587 sampled,
588 context,
589 transaction,
590 })),
591 }
592 }
593
594 #[cfg(not(feature = "client"))]
595 fn new_noop(ctx: TransactionContext) -> Self {
596 let context = protocol::TraceContext {
597 trace_id: ctx.trace_id,
598 parent_span_id: ctx.parent_span_id,
599 op: Some(ctx.op),
600 ..Default::default()
601 };
602 let sampled = ctx.sampled.unwrap_or(false);
603
604 Self {
605 inner: Arc::new(Mutex::new(TransactionInner {
606 sampled,
607 context,
608 transaction: None,
609 })),
610 }
611 }
612
613 pub fn set_data(&self, key: &str, value: protocol::Value) {
615 let mut inner = self.inner.lock().unwrap();
616 if let Some(transaction) = inner.transaction.as_mut() {
617 transaction.extra.insert(key.into(), value);
618 }
619 }
620
621 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
623 let mut inner = self.inner.lock().unwrap();
624 if let Some(transaction) = inner.transaction.as_mut() {
625 transaction.tags.insert(key.into(), value.to_string());
626 }
627 }
628
629 pub fn data(&self) -> TransactionData {
639 TransactionData(self.inner.lock().unwrap())
640 }
641
642 pub fn get_trace_context(&self) -> protocol::TraceContext {
646 let inner = self.inner.lock().unwrap();
647 inner.context.clone()
648 }
649
650 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
652 let inner = self.inner.lock().unwrap();
653 inner.context.status
654 }
655
656 pub fn set_status(&self, status: protocol::SpanStatus) {
658 let mut inner = self.inner.lock().unwrap();
659 inner.context.status = Some(status);
660 }
661
662 pub fn set_request(&self, request: protocol::Request) {
664 let mut inner = self.inner.lock().unwrap();
665 if let Some(transaction) = inner.transaction.as_mut() {
666 transaction.request = Some(request);
667 }
668 }
669
670 pub fn iter_headers(&self) -> TraceHeadersIter {
672 let inner = self.inner.lock().unwrap();
673 let trace = SentryTrace(
674 inner.context.trace_id,
675 inner.context.span_id,
676 Some(inner.sampled),
677 );
678 TraceHeadersIter {
679 sentry_trace: Some(trace.to_string()),
680 }
681 }
682
683 pub fn is_sampled(&self) -> bool {
685 self.inner.lock().unwrap().sampled
686 }
687
688 pub fn finish(self) {
693 with_client_impl! {{
694 let mut inner = self.inner.lock().unwrap();
695
696 if !inner.sampled {
698 return;
699 }
700
701 if let Some(mut transaction) = inner.transaction.take() {
702 if let Some(client) = inner.client.take() {
703 transaction.finish();
704 transaction
705 .contexts
706 .insert("trace".into(), inner.context.clone().into());
707
708 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
709 let opts = client.options();
710 transaction.release.clone_from(&opts.release);
711 transaction.environment.clone_from(&opts.environment);
712 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
713 transaction.server_name.clone_from(&opts.server_name);
714
715 drop(inner);
716
717 let mut envelope = protocol::Envelope::new();
718 envelope.add_item(transaction);
719
720 client.send_envelope(envelope)
721 }
722 }
723 }}
724 }
725
726 #[must_use = "a span must be explicitly closed via `finish()`"]
730 pub fn start_child(&self, op: &str, description: &str) -> Span {
731 let inner = self.inner.lock().unwrap();
732 let span = protocol::Span {
733 trace_id: inner.context.trace_id,
734 parent_span_id: Some(inner.context.span_id),
735 op: Some(op.into()),
736 description: if description.is_empty() {
737 None
738 } else {
739 Some(description.into())
740 },
741 ..Default::default()
742 };
743 Span {
744 transaction: Arc::clone(&self.inner),
745 sampled: inner.sampled,
746 span: Arc::new(Mutex::new(span)),
747 }
748 }
749}
750
751pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
753
754impl Data<'_> {
755 pub fn set_data(&mut self, key: String, value: protocol::Value) {
757 self.0.data.insert(key, value);
758 }
759
760 pub fn set_tag(&mut self, key: String, value: String) {
762 self.0.tags.insert(key, value);
763 }
764}
765
766impl Deref for Data<'_> {
767 type Target = BTreeMap<String, protocol::Value>;
768
769 fn deref(&self) -> &Self::Target {
770 &self.0.data
771 }
772}
773
774impl DerefMut for Data<'_> {
775 fn deref_mut(&mut self) -> &mut Self::Target {
776 &mut self.0.data
777 }
778}
779
780#[derive(Clone, Debug)]
785pub struct Span {
786 pub(crate) transaction: TransactionArc,
787 sampled: bool,
788 span: SpanArc,
789}
790
791type SpanArc = Arc<Mutex<protocol::Span>>;
792
793impl Span {
794 pub fn set_data(&self, key: &str, value: protocol::Value) {
796 let mut span = self.span.lock().unwrap();
797 span.data.insert(key.into(), value);
798 }
799
800 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
802 let mut span = self.span.lock().unwrap();
803 span.tags.insert(key.into(), value.to_string());
804 }
805
806 pub fn data(&self) -> Data {
818 Data(self.span.lock().unwrap())
819 }
820
821 pub fn get_trace_context(&self) -> protocol::TraceContext {
825 let transaction = self.transaction.lock().unwrap();
826 transaction.context.clone()
827 }
828
829 pub fn get_span_id(&self) -> protocol::SpanId {
831 let span = self.span.lock().unwrap();
832 span.span_id
833 }
834
835 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
837 let span = self.span.lock().unwrap();
838 span.status
839 }
840
841 pub fn set_status(&self, status: protocol::SpanStatus) {
843 let mut span = self.span.lock().unwrap();
844 span.status = Some(status);
845 }
846
847 pub fn set_request(&self, request: protocol::Request) {
849 let mut span = self.span.lock().unwrap();
850 if let Some(method) = request.method {
852 span.data.insert("method".into(), method.into());
853 }
854 if let Some(url) = request.url {
855 span.data.insert("url".into(), url.to_string().into());
856 }
857 if let Some(data) = request.data {
858 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
859 span.data.insert("data".into(), data);
860 } else {
861 span.data.insert("data".into(), data.into());
862 }
863 }
864 if let Some(query_string) = request.query_string {
865 span.data.insert("query_string".into(), query_string.into());
866 }
867 if let Some(cookies) = request.cookies {
868 span.data.insert("cookies".into(), cookies.into());
869 }
870 if !request.headers.is_empty() {
871 if let Ok(headers) = serde_json::to_value(request.headers) {
872 span.data.insert("headers".into(), headers);
873 }
874 }
875 if !request.env.is_empty() {
876 if let Ok(env) = serde_json::to_value(request.env) {
877 span.data.insert("env".into(), env);
878 }
879 }
880 }
881
882 pub fn iter_headers(&self) -> TraceHeadersIter {
884 let span = self.span.lock().unwrap();
885 let trace = SentryTrace(span.trace_id, span.span_id, Some(self.sampled));
886 TraceHeadersIter {
887 sentry_trace: Some(trace.to_string()),
888 }
889 }
890
891 pub fn is_sampled(&self) -> bool {
893 self.sampled
894 }
895
896 pub fn finish(self) {
901 with_client_impl! {{
902 let mut span = self.span.lock().unwrap();
903 if span.timestamp.is_some() {
904 return;
906 }
907 span.finish();
908 let mut inner = self.transaction.lock().unwrap();
909 if let Some(transaction) = inner.transaction.as_mut() {
910 if transaction.spans.len() <= MAX_SPANS {
911 transaction.spans.push(span.clone());
912 }
913 }
914 }}
915 }
916
917 #[must_use = "a span must be explicitly closed via `finish()`"]
921 pub fn start_child(&self, op: &str, description: &str) -> Span {
922 let span = self.span.lock().unwrap();
923 let span = protocol::Span {
924 trace_id: span.trace_id,
925 parent_span_id: Some(span.span_id),
926 op: Some(op.into()),
927 description: if description.is_empty() {
928 None
929 } else {
930 Some(description.into())
931 },
932 ..Default::default()
933 };
934 Span {
935 transaction: self.transaction.clone(),
936 sampled: self.sampled,
937 span: Arc::new(Mutex::new(span)),
938 }
939 }
940}
941
942pub struct TraceHeadersIter {
947 sentry_trace: Option<String>,
948}
949
950impl Iterator for TraceHeadersIter {
951 type Item = (&'static str, String);
952
953 fn next(&mut self) -> Option<Self::Item> {
954 self.sentry_trace.take().map(|st| ("sentry-trace", st))
955 }
956}
957
958#[derive(Debug, PartialEq)]
959struct SentryTrace(protocol::TraceId, protocol::SpanId, Option<bool>);
960
961fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
962 let header = header.trim();
963 let mut parts = header.splitn(3, '-');
964
965 let trace_id = parts.next()?.parse().ok()?;
966 let parent_span_id = parts.next()?.parse().ok()?;
967 let parent_sampled = parts.next().and_then(|sampled| match sampled {
968 "1" => Some(true),
969 "0" => Some(false),
970 _ => None,
971 });
972
973 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
974}
975
976fn parse_w3c_traceparent(header: &str) -> Option<SentryTrace> {
979 let header = header.trim();
980 let mut parts = header.splitn(4, '-');
981
982 let _version = parts.next()?;
983 let trace_id = parts.next()?.parse().ok()?;
984 let parent_span_id = parts.next()?.parse().ok()?;
985 let parent_sampled = parts
986 .next()
987 .and_then(|sampled| u8::from_str_radix(sampled, 16).ok())
988 .map(|flag| flag & 1 != 0);
989
990 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
991}
992
993impl std::fmt::Display for SentryTrace {
994 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
995 write!(f, "{}-{}", self.0, self.1)?;
996 if let Some(sampled) = self.2 {
997 write!(f, "-{}", if sampled { '1' } else { '0' })?;
998 }
999 Ok(())
1000 }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use std::str::FromStr;
1006
1007 use super::*;
1008
1009 #[test]
1010 fn parses_sentry_trace() {
1011 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1012 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1013
1014 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1015 assert_eq!(
1016 trace,
1017 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1018 );
1019
1020 let trace = SentryTrace(Default::default(), Default::default(), None);
1021 let parsed = parse_sentry_trace(&trace.to_string());
1022 assert_eq!(parsed, Some(trace));
1023 }
1024
1025 #[test]
1026 fn parses_traceparent() {
1027 let trace_id = protocol::TraceId::from_str("4bf92f3577b34da6a3ce929d0e0e4736").unwrap();
1028 let parent_trace_id = protocol::SpanId::from_str("00f067aa0ba902b7").unwrap();
1029
1030 let trace =
1031 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
1032 assert_eq!(
1033 trace,
1034 Some(SentryTrace(trace_id, parent_trace_id, Some(true)))
1035 );
1036
1037 let trace =
1038 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00");
1039 assert_eq!(
1040 trace,
1041 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1042 );
1043 }
1044
1045 #[test]
1046 fn disabled_forwards_trace_id() {
1047 let headers = [(
1048 "SenTrY-TRAce",
1049 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1050 )];
1051 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1052 let trx = start_transaction(ctx);
1053
1054 let span = trx.start_child("noop", "noop");
1055
1056 let header = span.iter_headers().next().unwrap().1;
1057 let parsed = parse_sentry_trace(&header).unwrap();
1058
1059 assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
1060 assert_eq!(parsed.2, Some(true));
1061 }
1062
1063 #[test]
1064 fn transaction_context_public_getters() {
1065 let mut ctx = TransactionContext::new("test-name", "test-operation");
1066 assert_eq!(ctx.name(), "test-name");
1067 assert_eq!(ctx.operation(), "test-operation");
1068 assert_eq!(ctx.sampled(), None);
1069
1070 ctx.set_sampled(true);
1071 assert_eq!(ctx.sampled(), Some(true));
1072 }
1073
1074 #[cfg(feature = "client")]
1075 #[test]
1076 fn compute_transaction_sample_rate() {
1077 let ctx = TransactionContext::new("noop", "noop");
1079 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1080 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1081
1082 let mut ctx = TransactionContext::new("noop", "noop");
1084 ctx.set_sampled(true);
1085 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1086 ctx.set_sampled(false);
1087 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1088
1089 let mut ctx = TransactionContext::new("noop", "noop");
1091 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1092 ctx.set_sampled(false);
1093 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1094 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1096 Some(true) => 0.8,
1097 Some(false) => 0.4,
1098 None => 0.6,
1099 };
1100 ctx.set_sampled(true);
1101 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1102 ctx.set_sampled(None);
1103 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1104
1105 let sampler = |ctx: &TransactionContext| {
1107 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1108 return 1.0;
1109 }
1110
1111 if let Some(custom) = ctx.custom() {
1112 if let Some(rate) = custom.get("rate") {
1113 if let Some(rate) = rate.as_f64() {
1114 return rate as f32;
1115 }
1116 }
1117 }
1118
1119 0.1
1120 };
1121 let ctx = TransactionContext::new("noop", "must-operation");
1123 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1124 let ctx = TransactionContext::new("must-name", "noop");
1125 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1126 let mut ctx = TransactionContext::new("noop", "noop");
1128 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1129 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1130 }
1131}