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 self.0.transaction.is_some() {
541 Box::new(self.0.context.data.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 self.0.transaction.is_some() {
550 self.0.context.data.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 inner.transaction.is_some() {
617 inner.context.data.insert(key.into(), value);
618 }
619 }
620
621 pub fn set_extra(&self, key: &str, value: protocol::Value) {
623 let mut inner = self.inner.lock().unwrap();
624 if let Some(transaction) = inner.transaction.as_mut() {
625 transaction.extra.insert(key.into(), value);
626 }
627 }
628
629 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
631 let mut inner = self.inner.lock().unwrap();
632 if let Some(transaction) = inner.transaction.as_mut() {
633 transaction.tags.insert(key.into(), value.to_string());
634 }
635 }
636
637 pub fn data(&self) -> TransactionData {
647 TransactionData(self.inner.lock().unwrap())
648 }
649
650 pub fn get_trace_context(&self) -> protocol::TraceContext {
654 let inner = self.inner.lock().unwrap();
655 inner.context.clone()
656 }
657
658 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
660 let inner = self.inner.lock().unwrap();
661 inner.context.status
662 }
663
664 pub fn set_status(&self, status: protocol::SpanStatus) {
666 let mut inner = self.inner.lock().unwrap();
667 inner.context.status = Some(status);
668 }
669
670 pub fn set_request(&self, request: protocol::Request) {
672 let mut inner = self.inner.lock().unwrap();
673 if let Some(transaction) = inner.transaction.as_mut() {
674 transaction.request = Some(request);
675 }
676 }
677
678 pub fn iter_headers(&self) -> TraceHeadersIter {
680 let inner = self.inner.lock().unwrap();
681 let trace = SentryTrace(
682 inner.context.trace_id,
683 inner.context.span_id,
684 Some(inner.sampled),
685 );
686 TraceHeadersIter {
687 sentry_trace: Some(trace.to_string()),
688 }
689 }
690
691 pub fn is_sampled(&self) -> bool {
693 self.inner.lock().unwrap().sampled
694 }
695
696 pub fn finish(self) {
701 with_client_impl! {{
702 let mut inner = self.inner.lock().unwrap();
703
704 if !inner.sampled {
706 return;
707 }
708
709 if let Some(mut transaction) = inner.transaction.take() {
710 if let Some(client) = inner.client.take() {
711 transaction.finish();
712 transaction
713 .contexts
714 .insert("trace".into(), inner.context.clone().into());
715
716 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
717 let opts = client.options();
718 transaction.release.clone_from(&opts.release);
719 transaction.environment.clone_from(&opts.environment);
720 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
721 transaction.server_name.clone_from(&opts.server_name);
722
723 drop(inner);
724
725 let mut envelope = protocol::Envelope::new();
726 envelope.add_item(transaction);
727
728 client.send_envelope(envelope)
729 }
730 }
731 }}
732 }
733
734 #[must_use = "a span must be explicitly closed via `finish()`"]
738 pub fn start_child(&self, op: &str, description: &str) -> Span {
739 let inner = self.inner.lock().unwrap();
740 let span = protocol::Span {
741 trace_id: inner.context.trace_id,
742 parent_span_id: Some(inner.context.span_id),
743 op: Some(op.into()),
744 description: if description.is_empty() {
745 None
746 } else {
747 Some(description.into())
748 },
749 ..Default::default()
750 };
751 Span {
752 transaction: Arc::clone(&self.inner),
753 sampled: inner.sampled,
754 span: Arc::new(Mutex::new(span)),
755 }
756 }
757}
758
759pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
761
762impl Data<'_> {
763 pub fn set_data(&mut self, key: String, value: protocol::Value) {
765 self.0.data.insert(key, value);
766 }
767
768 pub fn set_tag(&mut self, key: String, value: String) {
770 self.0.tags.insert(key, value);
771 }
772}
773
774impl Deref for Data<'_> {
775 type Target = BTreeMap<String, protocol::Value>;
776
777 fn deref(&self) -> &Self::Target {
778 &self.0.data
779 }
780}
781
782impl DerefMut for Data<'_> {
783 fn deref_mut(&mut self) -> &mut Self::Target {
784 &mut self.0.data
785 }
786}
787
788#[derive(Clone, Debug)]
793pub struct Span {
794 pub(crate) transaction: TransactionArc,
795 sampled: bool,
796 span: SpanArc,
797}
798
799type SpanArc = Arc<Mutex<protocol::Span>>;
800
801impl Span {
802 pub fn set_data(&self, key: &str, value: protocol::Value) {
804 let mut span = self.span.lock().unwrap();
805 span.data.insert(key.into(), value);
806 }
807
808 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
810 let mut span = self.span.lock().unwrap();
811 span.tags.insert(key.into(), value.to_string());
812 }
813
814 pub fn data(&self) -> Data {
826 Data(self.span.lock().unwrap())
827 }
828
829 pub fn get_trace_context(&self) -> protocol::TraceContext {
833 let transaction = self.transaction.lock().unwrap();
834 transaction.context.clone()
835 }
836
837 pub fn get_span_id(&self) -> protocol::SpanId {
839 let span = self.span.lock().unwrap();
840 span.span_id
841 }
842
843 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
845 let span = self.span.lock().unwrap();
846 span.status
847 }
848
849 pub fn set_status(&self, status: protocol::SpanStatus) {
851 let mut span = self.span.lock().unwrap();
852 span.status = Some(status);
853 }
854
855 pub fn set_request(&self, request: protocol::Request) {
857 let mut span = self.span.lock().unwrap();
858 if let Some(method) = request.method {
860 span.data.insert("method".into(), method.into());
861 }
862 if let Some(url) = request.url {
863 span.data.insert("url".into(), url.to_string().into());
864 }
865 if let Some(data) = request.data {
866 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
867 span.data.insert("data".into(), data);
868 } else {
869 span.data.insert("data".into(), data.into());
870 }
871 }
872 if let Some(query_string) = request.query_string {
873 span.data.insert("query_string".into(), query_string.into());
874 }
875 if let Some(cookies) = request.cookies {
876 span.data.insert("cookies".into(), cookies.into());
877 }
878 if !request.headers.is_empty() {
879 if let Ok(headers) = serde_json::to_value(request.headers) {
880 span.data.insert("headers".into(), headers);
881 }
882 }
883 if !request.env.is_empty() {
884 if let Ok(env) = serde_json::to_value(request.env) {
885 span.data.insert("env".into(), env);
886 }
887 }
888 }
889
890 pub fn iter_headers(&self) -> TraceHeadersIter {
892 let span = self.span.lock().unwrap();
893 let trace = SentryTrace(span.trace_id, span.span_id, Some(self.sampled));
894 TraceHeadersIter {
895 sentry_trace: Some(trace.to_string()),
896 }
897 }
898
899 pub fn is_sampled(&self) -> bool {
901 self.sampled
902 }
903
904 pub fn finish(self) {
909 with_client_impl! {{
910 let mut span = self.span.lock().unwrap();
911 if span.timestamp.is_some() {
912 return;
914 }
915 span.finish();
916 let mut inner = self.transaction.lock().unwrap();
917 if let Some(transaction) = inner.transaction.as_mut() {
918 if transaction.spans.len() <= MAX_SPANS {
919 transaction.spans.push(span.clone());
920 }
921 }
922 }}
923 }
924
925 #[must_use = "a span must be explicitly closed via `finish()`"]
929 pub fn start_child(&self, op: &str, description: &str) -> Span {
930 let span = self.span.lock().unwrap();
931 let span = protocol::Span {
932 trace_id: span.trace_id,
933 parent_span_id: Some(span.span_id),
934 op: Some(op.into()),
935 description: if description.is_empty() {
936 None
937 } else {
938 Some(description.into())
939 },
940 ..Default::default()
941 };
942 Span {
943 transaction: self.transaction.clone(),
944 sampled: self.sampled,
945 span: Arc::new(Mutex::new(span)),
946 }
947 }
948}
949
950pub struct TraceHeadersIter {
955 sentry_trace: Option<String>,
956}
957
958impl Iterator for TraceHeadersIter {
959 type Item = (&'static str, String);
960
961 fn next(&mut self) -> Option<Self::Item> {
962 self.sentry_trace.take().map(|st| ("sentry-trace", st))
963 }
964}
965
966#[derive(Debug, PartialEq)]
967struct SentryTrace(protocol::TraceId, protocol::SpanId, Option<bool>);
968
969fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
970 let header = header.trim();
971 let mut parts = header.splitn(3, '-');
972
973 let trace_id = parts.next()?.parse().ok()?;
974 let parent_span_id = parts.next()?.parse().ok()?;
975 let parent_sampled = parts.next().and_then(|sampled| match sampled {
976 "1" => Some(true),
977 "0" => Some(false),
978 _ => None,
979 });
980
981 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
982}
983
984fn parse_w3c_traceparent(header: &str) -> Option<SentryTrace> {
987 let header = header.trim();
988 let mut parts = header.splitn(4, '-');
989
990 let _version = parts.next()?;
991 let trace_id = parts.next()?.parse().ok()?;
992 let parent_span_id = parts.next()?.parse().ok()?;
993 let parent_sampled = parts
994 .next()
995 .and_then(|sampled| u8::from_str_radix(sampled, 16).ok())
996 .map(|flag| flag & 1 != 0);
997
998 Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
999}
1000
1001impl std::fmt::Display for SentryTrace {
1002 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1003 write!(f, "{}-{}", self.0, self.1)?;
1004 if let Some(sampled) = self.2 {
1005 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1006 }
1007 Ok(())
1008 }
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013 use std::str::FromStr;
1014
1015 use super::*;
1016
1017 #[test]
1018 fn parses_sentry_trace() {
1019 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1020 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1021
1022 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1023 assert_eq!(
1024 trace,
1025 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1026 );
1027
1028 let trace = SentryTrace(Default::default(), Default::default(), None);
1029 let parsed = parse_sentry_trace(&trace.to_string());
1030 assert_eq!(parsed, Some(trace));
1031 }
1032
1033 #[test]
1034 fn parses_traceparent() {
1035 let trace_id = protocol::TraceId::from_str("4bf92f3577b34da6a3ce929d0e0e4736").unwrap();
1036 let parent_trace_id = protocol::SpanId::from_str("00f067aa0ba902b7").unwrap();
1037
1038 let trace =
1039 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
1040 assert_eq!(
1041 trace,
1042 Some(SentryTrace(trace_id, parent_trace_id, Some(true)))
1043 );
1044
1045 let trace =
1046 parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00");
1047 assert_eq!(
1048 trace,
1049 Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1050 );
1051 }
1052
1053 #[test]
1054 fn disabled_forwards_trace_id() {
1055 let headers = [(
1056 "SenTrY-TRAce",
1057 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1058 )];
1059 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1060 let trx = start_transaction(ctx);
1061
1062 let span = trx.start_child("noop", "noop");
1063
1064 let header = span.iter_headers().next().unwrap().1;
1065 let parsed = parse_sentry_trace(&header).unwrap();
1066
1067 assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
1068 assert_eq!(parsed.2, Some(true));
1069 }
1070
1071 #[test]
1072 fn transaction_context_public_getters() {
1073 let mut ctx = TransactionContext::new("test-name", "test-operation");
1074 assert_eq!(ctx.name(), "test-name");
1075 assert_eq!(ctx.operation(), "test-operation");
1076 assert_eq!(ctx.sampled(), None);
1077
1078 ctx.set_sampled(true);
1079 assert_eq!(ctx.sampled(), Some(true));
1080 }
1081
1082 #[cfg(feature = "client")]
1083 #[test]
1084 fn compute_transaction_sample_rate() {
1085 let ctx = TransactionContext::new("noop", "noop");
1087 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1088 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1089
1090 let mut ctx = TransactionContext::new("noop", "noop");
1092 ctx.set_sampled(true);
1093 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1094 ctx.set_sampled(false);
1095 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1096
1097 let mut ctx = TransactionContext::new("noop", "noop");
1099 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1100 ctx.set_sampled(false);
1101 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1102 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1104 Some(true) => 0.8,
1105 Some(false) => 0.4,
1106 None => 0.6,
1107 };
1108 ctx.set_sampled(true);
1109 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1110 ctx.set_sampled(None);
1111 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1112
1113 let sampler = |ctx: &TransactionContext| {
1115 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1116 return 1.0;
1117 }
1118
1119 if let Some(custom) = ctx.custom() {
1120 if let Some(rate) = custom.get("rate") {
1121 if let Some(rate) = rate.as_f64() {
1122 return rate as f32;
1123 }
1124 }
1125 }
1126
1127 0.1
1128 };
1129 let ctx = TransactionContext::new("noop", "must-operation");
1131 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1132 let ctx = TransactionContext::new("must-name", "noop");
1133 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1134 let mut ctx = TransactionContext::new("noop", "noop");
1136 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1137 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1138 }
1139}