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`"]
176 pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
177 name: &str,
178 op: &str,
179 headers: I,
180 ) -> Self {
181 parse_headers(headers)
182 .map(|sentry_trace| Self::continue_from_sentry_trace(name, op, &sentry_trace, None))
183 .unwrap_or_else(|| Self {
184 name: name.into(),
185 op: op.into(),
186 trace_id: Default::default(),
187 parent_span_id: None,
188 span_id: Default::default(),
189 sampled: None,
190 custom: None,
191 })
192 }
193
194 pub fn continue_from_sentry_trace(
197 name: &str,
198 op: &str,
199 sentry_trace: &SentryTrace,
200 span_id: Option<SpanId>,
201 ) -> Self {
202 Self {
203 name: name.into(),
204 op: op.into(),
205 trace_id: sentry_trace.trace_id,
206 parent_span_id: Some(sentry_trace.span_id),
207 sampled: sentry_trace.sampled,
208 span_id: span_id.unwrap_or_default(),
209 custom: None,
210 }
211 }
212
213 pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
219 let span = match span {
220 Some(span) => span,
221 None => return Self::new(name, op),
222 };
223
224 let (trace_id, parent_span_id, sampled) = match span {
225 TransactionOrSpan::Transaction(transaction) => {
226 let inner = transaction.inner.lock().unwrap();
227 (
228 inner.context.trace_id,
229 inner.context.span_id,
230 Some(inner.sampled),
231 )
232 }
233 TransactionOrSpan::Span(span) => {
234 let sampled = span.sampled;
235 let span = span.span.lock().unwrap();
236 (span.trace_id, span.span_id, Some(sampled))
237 }
238 };
239
240 Self {
241 name: name.into(),
242 op: op.into(),
243 trace_id,
244 parent_span_id: Some(parent_span_id),
245 span_id: protocol::SpanId::default(),
246 sampled,
247 custom: None,
248 }
249 }
250
251 pub fn set_sampled(&mut self, sampled: impl Into<Option<bool>>) {
256 self.sampled = sampled.into();
257 }
258
259 pub fn sampled(&self) -> Option<bool> {
261 self.sampled
262 }
263
264 pub fn name(&self) -> &str {
266 &self.name
267 }
268
269 pub fn operation(&self) -> &str {
271 &self.op
272 }
273
274 pub fn trace_id(&self) -> protocol::TraceId {
276 self.trace_id
277 }
278
279 pub fn span_id(&self) -> protocol::SpanId {
281 self.span_id
282 }
283
284 pub fn custom(&self) -> Option<&CustomTransactionContext> {
286 self.custom.as_ref()
287 }
288
289 pub fn custom_mut(&mut self) -> &mut Option<CustomTransactionContext> {
293 &mut self.custom
294 }
295
296 pub fn custom_insert(
303 &mut self,
304 key: String,
305 value: serde_json::Value,
306 ) -> Option<serde_json::Value> {
307 let mut custom = None;
309 std::mem::swap(&mut self.custom, &mut custom);
310
311 let mut custom = custom.unwrap_or_default();
313
314 let existing_value = custom.insert(key, value);
316 self.custom = Some(custom);
317 existing_value
318 }
319
320 #[must_use]
327 pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
328 TransactionContextBuilder {
329 ctx: TransactionContext::new(name, op),
330 }
331 }
332}
333
334pub struct TransactionContextBuilder {
336 ctx: TransactionContext,
337}
338
339impl TransactionContextBuilder {
340 #[must_use]
342 pub fn with_name(mut self, name: String) -> Self {
343 self.ctx.name = name;
344 self
345 }
346
347 #[must_use]
349 pub fn with_op(mut self, op: String) -> Self {
350 self.ctx.op = op;
351 self
352 }
353
354 #[must_use]
356 pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
357 self.ctx.trace_id = trace_id;
358 self
359 }
360
361 #[must_use]
363 pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
364 self.ctx.parent_span_id = parent_span_id;
365 self
366 }
367
368 #[must_use]
370 pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
371 self.ctx.span_id = span_id;
372 self
373 }
374
375 #[must_use]
377 pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
378 self.ctx.sampled = sampled;
379 self
380 }
381
382 #[must_use]
384 pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
385 self.ctx.custom_insert(key, value);
386 self
387 }
388
389 pub fn finish(self) -> TransactionContext {
391 self.ctx
392 }
393}
394
395pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
401
402#[derive(Clone, Debug)]
406pub enum TransactionOrSpan {
407 Transaction(Transaction),
409 Span(Span),
411}
412
413impl From<Transaction> for TransactionOrSpan {
414 fn from(transaction: Transaction) -> Self {
415 Self::Transaction(transaction)
416 }
417}
418
419impl From<Span> for TransactionOrSpan {
420 fn from(span: Span) -> Self {
421 Self::Span(span)
422 }
423}
424
425impl TransactionOrSpan {
426 pub fn set_data(&self, key: &str, value: protocol::Value) {
428 match self {
429 TransactionOrSpan::Transaction(transaction) => transaction.set_data(key, value),
430 TransactionOrSpan::Span(span) => span.set_data(key, value),
431 }
432 }
433
434 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
436 match self {
437 TransactionOrSpan::Transaction(transaction) => transaction.set_tag(key, value),
438 TransactionOrSpan::Span(span) => span.set_tag(key, value),
439 }
440 }
441
442 pub fn get_trace_context(&self) -> protocol::TraceContext {
446 match self {
447 TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context(),
448 TransactionOrSpan::Span(span) => span.get_trace_context(),
449 }
450 }
451
452 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
454 match self {
455 TransactionOrSpan::Transaction(transaction) => transaction.get_status(),
456 TransactionOrSpan::Span(span) => span.get_status(),
457 }
458 }
459
460 pub fn set_status(&self, status: protocol::SpanStatus) {
462 match self {
463 TransactionOrSpan::Transaction(transaction) => transaction.set_status(status),
464 TransactionOrSpan::Span(span) => span.set_status(status),
465 }
466 }
467
468 pub fn set_request(&self, request: protocol::Request) {
470 match self {
471 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
472 TransactionOrSpan::Span(span) => span.set_request(request),
473 }
474 }
475
476 pub fn iter_headers(&self) -> TraceHeadersIter {
480 match self {
481 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
482 TransactionOrSpan::Span(span) => span.iter_headers(),
483 }
484 }
485
486 pub fn is_sampled(&self) -> bool {
488 match self {
489 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
490 TransactionOrSpan::Span(span) => span.is_sampled(),
491 }
492 }
493
494 #[must_use = "a span must be explicitly closed via `finish()`"]
499 pub fn start_child(&self, op: &str, description: &str) -> Span {
500 match self {
501 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
502 TransactionOrSpan::Span(span) => span.start_child(op, description),
503 }
504 }
505
506 #[must_use = "a span must be explicitly closed via `finish()`"]
511 pub fn start_child_with_details(
512 &self,
513 op: &str,
514 description: &str,
515 id: SpanId,
516 timestamp: SystemTime,
517 ) -> Span {
518 match self {
519 TransactionOrSpan::Transaction(transaction) => {
520 transaction.start_child_with_details(op, description, id, timestamp)
521 }
522 TransactionOrSpan::Span(span) => {
523 span.start_child_with_details(op, description, id, timestamp)
524 }
525 }
526 }
527
528 #[cfg(feature = "client")]
529 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
530 if event.contexts.contains_key("trace") {
531 return;
532 }
533
534 let context = match self {
535 TransactionOrSpan::Transaction(transaction) => {
536 transaction.inner.lock().unwrap().context.clone()
537 }
538 TransactionOrSpan::Span(span) => {
539 let span = span.span.lock().unwrap();
540 protocol::TraceContext {
541 span_id: span.span_id,
542 trace_id: span.trace_id,
543 ..Default::default()
544 }
545 }
546 };
547 event.contexts.insert("trace".into(), context.into());
548 }
549
550 pub fn finish_with_timestamp(self, timestamp: SystemTime) {
555 match self {
556 TransactionOrSpan::Transaction(transaction) => {
557 transaction.finish_with_timestamp(timestamp)
558 }
559 TransactionOrSpan::Span(span) => span.finish_with_timestamp(timestamp),
560 }
561 }
562
563 pub fn finish(self) {
568 match self {
569 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
570 TransactionOrSpan::Span(span) => span.finish(),
571 }
572 }
573}
574
575#[derive(Debug)]
576pub(crate) struct TransactionInner {
577 #[cfg(feature = "client")]
578 client: Option<Arc<Client>>,
579 sampled: bool,
580 pub(crate) context: protocol::TraceContext,
581 pub(crate) transaction: Option<protocol::Transaction<'static>>,
582}
583
584type TransactionArc = Arc<Mutex<TransactionInner>>;
585
586#[cfg(feature = "client")]
590fn transaction_sample_rate(
591 traces_sampler: Option<&TracesSampler>,
592 ctx: &TransactionContext,
593 traces_sample_rate: f32,
594) -> f32 {
595 match (traces_sampler, traces_sample_rate) {
596 (Some(traces_sampler), _) => traces_sampler(ctx),
597 (None, traces_sample_rate) => ctx
598 .sampled
599 .map(|sampled| if sampled { 1.0 } else { 0.0 })
600 .unwrap_or(traces_sample_rate),
601 }
602}
603
604#[cfg(feature = "client")]
606impl Client {
607 fn is_transaction_sampled(&self, ctx: &TransactionContext) -> bool {
608 let client_options = self.options();
609 self.sample_should_send(transaction_sample_rate(
610 client_options.traces_sampler.as_deref(),
611 ctx,
612 client_options.traces_sample_rate,
613 ))
614 }
615}
616
617#[derive(Clone, Debug)]
623pub struct Transaction {
624 pub(crate) inner: TransactionArc,
625}
626
627pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
629
630impl<'a> TransactionData<'a> {
631 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
638 if self.0.transaction.is_some() {
639 Box::new(self.0.context.data.iter())
640 } else {
641 Box::new(std::iter::empty())
642 }
643 }
644
645 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
647 if self.0.transaction.is_some() {
648 self.0.context.data.insert(key.into(), value);
649 }
650 }
651
652 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
654 if let Some(transaction) = self.0.transaction.as_mut() {
655 transaction.tags.insert(key.into(), value);
656 }
657 }
658}
659
660impl Transaction {
661 #[cfg(feature = "client")]
662 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
663 let (sampled, transaction) = match client.as_ref() {
664 Some(client) => (
665 client.is_transaction_sampled(&ctx),
666 Some(protocol::Transaction {
667 name: Some(ctx.name),
668 ..Default::default()
669 }),
670 ),
671 None => (ctx.sampled.unwrap_or(false), None),
672 };
673
674 let context = protocol::TraceContext {
675 trace_id: ctx.trace_id,
676 parent_span_id: ctx.parent_span_id,
677 span_id: ctx.span_id,
678 op: Some(ctx.op),
679 ..Default::default()
680 };
681
682 Self {
683 inner: Arc::new(Mutex::new(TransactionInner {
684 client,
685 sampled,
686 context,
687 transaction,
688 })),
689 }
690 }
691
692 #[cfg(not(feature = "client"))]
693 fn new_noop(ctx: TransactionContext) -> Self {
694 let context = protocol::TraceContext {
695 trace_id: ctx.trace_id,
696 parent_span_id: ctx.parent_span_id,
697 op: Some(ctx.op),
698 ..Default::default()
699 };
700 let sampled = ctx.sampled.unwrap_or(false);
701
702 Self {
703 inner: Arc::new(Mutex::new(TransactionInner {
704 sampled,
705 context,
706 transaction: None,
707 })),
708 }
709 }
710
711 pub fn set_data(&self, key: &str, value: protocol::Value) {
713 let mut inner = self.inner.lock().unwrap();
714 if inner.transaction.is_some() {
715 inner.context.data.insert(key.into(), value);
716 }
717 }
718
719 pub fn set_extra(&self, key: &str, value: protocol::Value) {
721 let mut inner = self.inner.lock().unwrap();
722 if let Some(transaction) = inner.transaction.as_mut() {
723 transaction.extra.insert(key.into(), value);
724 }
725 }
726
727 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
729 let mut inner = self.inner.lock().unwrap();
730 if let Some(transaction) = inner.transaction.as_mut() {
731 transaction.tags.insert(key.into(), value.to_string());
732 }
733 }
734
735 pub fn data(&self) -> TransactionData {
745 TransactionData(self.inner.lock().unwrap())
746 }
747
748 pub fn get_trace_context(&self) -> protocol::TraceContext {
752 let inner = self.inner.lock().unwrap();
753 inner.context.clone()
754 }
755
756 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
758 let inner = self.inner.lock().unwrap();
759 inner.context.status
760 }
761
762 pub fn set_status(&self, status: protocol::SpanStatus) {
764 let mut inner = self.inner.lock().unwrap();
765 inner.context.status = Some(status);
766 }
767
768 pub fn set_request(&self, request: protocol::Request) {
770 let mut inner = self.inner.lock().unwrap();
771 if let Some(transaction) = inner.transaction.as_mut() {
772 transaction.request = Some(request);
773 }
774 }
775
776 pub fn iter_headers(&self) -> TraceHeadersIter {
780 let inner = self.inner.lock().unwrap();
781 let trace = SentryTrace::new(
782 inner.context.trace_id,
783 inner.context.span_id,
784 Some(inner.sampled),
785 );
786 TraceHeadersIter {
787 sentry_trace: Some(trace.to_string()),
788 }
789 }
790
791 pub fn is_sampled(&self) -> bool {
793 self.inner.lock().unwrap().sampled
794 }
795
796 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
801 with_client_impl! {{
802 let mut inner = self.inner.lock().unwrap();
803
804 if !inner.sampled {
806 return;
807 }
808
809 if let Some(mut transaction) = inner.transaction.take() {
810 if let Some(client) = inner.client.take() {
811 transaction.finish_with_timestamp(_timestamp);
812 transaction
813 .contexts
814 .insert("trace".into(), inner.context.clone().into());
815
816 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
817 let opts = client.options();
818 transaction.release.clone_from(&opts.release);
819 transaction.environment.clone_from(&opts.environment);
820 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
821 transaction.server_name.clone_from(&opts.server_name);
822
823 drop(inner);
824
825 let mut envelope = protocol::Envelope::new();
826 envelope.add_item(transaction);
827
828 client.send_envelope(envelope)
829 }
830 }
831 }}
832 }
833
834 pub fn finish(self) {
839 self.finish_with_timestamp(SystemTime::now());
840 }
841
842 #[must_use = "a span must be explicitly closed via `finish()`"]
846 pub fn start_child(&self, op: &str, description: &str) -> Span {
847 let inner = self.inner.lock().unwrap();
848 let span = protocol::Span {
849 trace_id: inner.context.trace_id,
850 parent_span_id: Some(inner.context.span_id),
851 op: Some(op.into()),
852 description: if description.is_empty() {
853 None
854 } else {
855 Some(description.into())
856 },
857 ..Default::default()
858 };
859 Span {
860 transaction: Arc::clone(&self.inner),
861 sampled: inner.sampled,
862 span: Arc::new(Mutex::new(span)),
863 }
864 }
865
866 #[must_use = "a span must be explicitly closed via `finish()`"]
870 pub fn start_child_with_details(
871 &self,
872 op: &str,
873 description: &str,
874 id: SpanId,
875 timestamp: SystemTime,
876 ) -> Span {
877 let inner = self.inner.lock().unwrap();
878 let span = protocol::Span {
879 trace_id: inner.context.trace_id,
880 parent_span_id: Some(inner.context.span_id),
881 op: Some(op.into()),
882 description: if description.is_empty() {
883 None
884 } else {
885 Some(description.into())
886 },
887 span_id: id,
888 start_timestamp: timestamp,
889 ..Default::default()
890 };
891 Span {
892 transaction: Arc::clone(&self.inner),
893 sampled: inner.sampled,
894 span: Arc::new(Mutex::new(span)),
895 }
896 }
897}
898
899pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
901
902impl Data<'_> {
903 pub fn set_data(&mut self, key: String, value: protocol::Value) {
905 self.0.data.insert(key, value);
906 }
907
908 pub fn set_tag(&mut self, key: String, value: String) {
910 self.0.tags.insert(key, value);
911 }
912}
913
914impl Deref for Data<'_> {
915 type Target = BTreeMap<String, protocol::Value>;
916
917 fn deref(&self) -> &Self::Target {
918 &self.0.data
919 }
920}
921
922impl DerefMut for Data<'_> {
923 fn deref_mut(&mut self) -> &mut Self::Target {
924 &mut self.0.data
925 }
926}
927
928#[derive(Clone, Debug)]
933pub struct Span {
934 pub(crate) transaction: TransactionArc,
935 sampled: bool,
936 span: SpanArc,
937}
938
939type SpanArc = Arc<Mutex<protocol::Span>>;
940
941impl Span {
942 pub fn set_data(&self, key: &str, value: protocol::Value) {
944 let mut span = self.span.lock().unwrap();
945 span.data.insert(key.into(), value);
946 }
947
948 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
950 let mut span = self.span.lock().unwrap();
951 span.tags.insert(key.into(), value.to_string());
952 }
953
954 pub fn data(&self) -> Data {
966 Data(self.span.lock().unwrap())
967 }
968
969 pub fn get_trace_context(&self) -> protocol::TraceContext {
973 let transaction = self.transaction.lock().unwrap();
974 transaction.context.clone()
975 }
976
977 pub fn get_span_id(&self) -> protocol::SpanId {
979 let span = self.span.lock().unwrap();
980 span.span_id
981 }
982
983 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
985 let span = self.span.lock().unwrap();
986 span.status
987 }
988
989 pub fn set_status(&self, status: protocol::SpanStatus) {
991 let mut span = self.span.lock().unwrap();
992 span.status = Some(status);
993 }
994
995 pub fn set_request(&self, request: protocol::Request) {
997 let mut span = self.span.lock().unwrap();
998 if let Some(method) = request.method {
1000 span.data.insert("method".into(), method.into());
1001 }
1002 if let Some(url) = request.url {
1003 span.data.insert("url".into(), url.to_string().into());
1004 }
1005 if let Some(data) = request.data {
1006 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1007 span.data.insert("data".into(), data);
1008 } else {
1009 span.data.insert("data".into(), data.into());
1010 }
1011 }
1012 if let Some(query_string) = request.query_string {
1013 span.data.insert("query_string".into(), query_string.into());
1014 }
1015 if let Some(cookies) = request.cookies {
1016 span.data.insert("cookies".into(), cookies.into());
1017 }
1018 if !request.headers.is_empty() {
1019 if let Ok(headers) = serde_json::to_value(request.headers) {
1020 span.data.insert("headers".into(), headers);
1021 }
1022 }
1023 if !request.env.is_empty() {
1024 if let Ok(env) = serde_json::to_value(request.env) {
1025 span.data.insert("env".into(), env);
1026 }
1027 }
1028 }
1029
1030 pub fn iter_headers(&self) -> TraceHeadersIter {
1034 let span = self.span.lock().unwrap();
1035 let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
1036 TraceHeadersIter {
1037 sentry_trace: Some(trace.to_string()),
1038 }
1039 }
1040
1041 pub fn is_sampled(&self) -> bool {
1043 self.sampled
1044 }
1045
1046 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1051 with_client_impl! {{
1052 let mut span = self.span.lock().unwrap();
1053 if span.timestamp.is_some() {
1054 return;
1056 }
1057 span.finish_with_timestamp(_timestamp);
1058 let mut inner = self.transaction.lock().unwrap();
1059 if let Some(transaction) = inner.transaction.as_mut() {
1060 if transaction.spans.len() <= MAX_SPANS {
1061 transaction.spans.push(span.clone());
1062 }
1063 }
1064 }}
1065 }
1066
1067 pub fn finish(self) {
1072 self.finish_with_timestamp(SystemTime::now());
1073 }
1074
1075 #[must_use = "a span must be explicitly closed via `finish()`"]
1079 pub fn start_child(&self, op: &str, description: &str) -> Span {
1080 let span = self.span.lock().unwrap();
1081 let span = protocol::Span {
1082 trace_id: span.trace_id,
1083 parent_span_id: Some(span.span_id),
1084 op: Some(op.into()),
1085 description: if description.is_empty() {
1086 None
1087 } else {
1088 Some(description.into())
1089 },
1090 ..Default::default()
1091 };
1092 Span {
1093 transaction: self.transaction.clone(),
1094 sampled: self.sampled,
1095 span: Arc::new(Mutex::new(span)),
1096 }
1097 }
1098
1099 #[must_use = "a span must be explicitly closed via `finish()`"]
1103 fn start_child_with_details(
1104 &self,
1105 op: &str,
1106 description: &str,
1107 id: SpanId,
1108 timestamp: SystemTime,
1109 ) -> Span {
1110 let span = self.span.lock().unwrap();
1111 let span = protocol::Span {
1112 trace_id: span.trace_id,
1113 parent_span_id: Some(span.span_id),
1114 op: Some(op.into()),
1115 description: if description.is_empty() {
1116 None
1117 } else {
1118 Some(description.into())
1119 },
1120 span_id: id,
1121 start_timestamp: timestamp,
1122 ..Default::default()
1123 };
1124 Span {
1125 transaction: self.transaction.clone(),
1126 sampled: self.sampled,
1127 span: Arc::new(Mutex::new(span)),
1128 }
1129 }
1130}
1131
1132pub type TraceHeader = (&'static str, String);
1134
1135pub struct TraceHeadersIter {
1140 sentry_trace: Option<String>,
1141}
1142
1143impl TraceHeadersIter {
1144 #[cfg(feature = "client")]
1145 pub(crate) fn new(sentry_trace: String) -> Self {
1146 Self {
1147 sentry_trace: Some(sentry_trace),
1148 }
1149 }
1150}
1151
1152impl Iterator for TraceHeadersIter {
1153 type Item = (&'static str, String);
1154
1155 fn next(&mut self) -> Option<Self::Item> {
1156 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1157 }
1158}
1159
1160#[derive(Debug, PartialEq, Clone, Copy, Default)]
1163pub struct SentryTrace {
1164 pub(crate) trace_id: protocol::TraceId,
1165 pub(crate) span_id: protocol::SpanId,
1166 pub(crate) sampled: Option<bool>,
1167}
1168
1169impl SentryTrace {
1170 pub fn new(
1172 trace_id: protocol::TraceId,
1173 span_id: protocol::SpanId,
1174 sampled: Option<bool>,
1175 ) -> Self {
1176 SentryTrace {
1177 trace_id,
1178 span_id,
1179 sampled,
1180 }
1181 }
1182}
1183
1184fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1185 let header = header.trim();
1186 let mut parts = header.splitn(3, '-');
1187
1188 let trace_id = parts.next()?.parse().ok()?;
1189 let parent_span_id = parts.next()?.parse().ok()?;
1190 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1191 "1" => Some(true),
1192 "0" => Some(false),
1193 _ => None,
1194 });
1195
1196 Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
1197}
1198
1199pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1202 headers: I,
1203) -> Option<SentryTrace> {
1204 let mut trace = None;
1205 for (k, v) in headers.into_iter() {
1206 if k.eq_ignore_ascii_case("sentry-trace") {
1207 trace = parse_sentry_trace(v);
1208 break;
1209 }
1210 }
1211 trace
1212}
1213
1214impl std::fmt::Display for SentryTrace {
1215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1216 write!(f, "{}-{}", self.trace_id, self.span_id)?;
1217 if let Some(sampled) = self.sampled {
1218 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1219 }
1220 Ok(())
1221 }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use std::str::FromStr;
1227
1228 use super::*;
1229
1230 #[test]
1231 fn parses_sentry_trace() {
1232 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1233 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1234
1235 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1236 assert_eq!(
1237 trace,
1238 Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
1239 );
1240
1241 let trace = SentryTrace::new(Default::default(), Default::default(), None);
1242 let parsed = parse_sentry_trace(&trace.to_string());
1243 assert_eq!(parsed, Some(trace));
1244 }
1245
1246 #[test]
1247 fn disabled_forwards_trace_id() {
1248 let headers = [(
1249 "SenTrY-TRAce",
1250 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1251 )];
1252 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1253 let trx = start_transaction(ctx);
1254
1255 let span = trx.start_child("noop", "noop");
1256
1257 let header = span.iter_headers().next().unwrap().1;
1258 let parsed = parse_sentry_trace(&header).unwrap();
1259
1260 assert_eq!(
1261 &parsed.trace_id.to_string(),
1262 "09e04486820349518ac7b5d2adbf6ba5"
1263 );
1264 assert_eq!(parsed.sampled, Some(true));
1265 }
1266
1267 #[test]
1268 fn transaction_context_public_getters() {
1269 let mut ctx = TransactionContext::new("test-name", "test-operation");
1270 assert_eq!(ctx.name(), "test-name");
1271 assert_eq!(ctx.operation(), "test-operation");
1272 assert_eq!(ctx.sampled(), None);
1273
1274 ctx.set_sampled(true);
1275 assert_eq!(ctx.sampled(), Some(true));
1276 }
1277
1278 #[cfg(feature = "client")]
1279 #[test]
1280 fn compute_transaction_sample_rate() {
1281 let ctx = TransactionContext::new("noop", "noop");
1283 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1284 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1285
1286 let mut ctx = TransactionContext::new("noop", "noop");
1288 ctx.set_sampled(true);
1289 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1290 ctx.set_sampled(false);
1291 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1292
1293 let mut ctx = TransactionContext::new("noop", "noop");
1295 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1296 ctx.set_sampled(false);
1297 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1298 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1300 Some(true) => 0.8,
1301 Some(false) => 0.4,
1302 None => 0.6,
1303 };
1304 ctx.set_sampled(true);
1305 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1306 ctx.set_sampled(None);
1307 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1308
1309 let sampler = |ctx: &TransactionContext| {
1311 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1312 return 1.0;
1313 }
1314
1315 if let Some(custom) = ctx.custom() {
1316 if let Some(rate) = custom.get("rate") {
1317 if let Some(rate) = rate.as_f64() {
1318 return rate as f32;
1319 }
1320 }
1321 }
1322
1323 0.1
1324 };
1325 let ctx = TransactionContext::new("noop", "must-operation");
1327 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1328 let ctx = TransactionContext::new("must-name", "noop");
1329 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1330 let mut ctx = TransactionContext::new("noop", "noop");
1332 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1333 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1334 }
1335}