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_op(&self, op: &str) {
470 match self {
471 TransactionOrSpan::Transaction(transaction) => transaction.set_op(op),
472 TransactionOrSpan::Span(span) => span.set_op(op),
473 }
474 }
475
476 pub fn set_name(&self, name: &str) {
478 match self {
479 TransactionOrSpan::Transaction(transaction) => transaction.set_name(name),
480 TransactionOrSpan::Span(span) => span.set_name(name),
481 }
482 }
483
484 pub fn set_request(&self, request: protocol::Request) {
486 match self {
487 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
488 TransactionOrSpan::Span(span) => span.set_request(request),
489 }
490 }
491
492 pub fn iter_headers(&self) -> TraceHeadersIter {
496 match self {
497 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
498 TransactionOrSpan::Span(span) => span.iter_headers(),
499 }
500 }
501
502 pub fn is_sampled(&self) -> bool {
504 match self {
505 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
506 TransactionOrSpan::Span(span) => span.is_sampled(),
507 }
508 }
509
510 #[must_use = "a span must be explicitly closed via `finish()`"]
515 pub fn start_child(&self, op: &str, description: &str) -> Span {
516 match self {
517 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
518 TransactionOrSpan::Span(span) => span.start_child(op, description),
519 }
520 }
521
522 #[must_use = "a span must be explicitly closed via `finish()`"]
527 pub fn start_child_with_details(
528 &self,
529 op: &str,
530 description: &str,
531 id: SpanId,
532 timestamp: SystemTime,
533 ) -> Span {
534 match self {
535 TransactionOrSpan::Transaction(transaction) => {
536 transaction.start_child_with_details(op, description, id, timestamp)
537 }
538 TransactionOrSpan::Span(span) => {
539 span.start_child_with_details(op, description, id, timestamp)
540 }
541 }
542 }
543
544 #[cfg(feature = "client")]
545 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
546 if event.contexts.contains_key("trace") {
547 return;
548 }
549
550 let context = match self {
551 TransactionOrSpan::Transaction(transaction) => {
552 transaction.inner.lock().unwrap().context.clone()
553 }
554 TransactionOrSpan::Span(span) => {
555 let span = span.span.lock().unwrap();
556 protocol::TraceContext {
557 span_id: span.span_id,
558 trace_id: span.trace_id,
559 ..Default::default()
560 }
561 }
562 };
563 event.contexts.insert("trace".into(), context.into());
564 }
565
566 pub fn finish_with_timestamp(self, timestamp: SystemTime) {
571 match self {
572 TransactionOrSpan::Transaction(transaction) => {
573 transaction.finish_with_timestamp(timestamp)
574 }
575 TransactionOrSpan::Span(span) => span.finish_with_timestamp(timestamp),
576 }
577 }
578
579 pub fn finish(self) {
584 match self {
585 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
586 TransactionOrSpan::Span(span) => span.finish(),
587 }
588 }
589}
590
591#[derive(Debug)]
592pub(crate) struct TransactionInner {
593 #[cfg(feature = "client")]
594 client: Option<Arc<Client>>,
595 sampled: bool,
596 pub(crate) context: protocol::TraceContext,
597 pub(crate) transaction: Option<protocol::Transaction<'static>>,
598}
599
600type TransactionArc = Arc<Mutex<TransactionInner>>;
601
602#[cfg(feature = "client")]
606fn transaction_sample_rate(
607 traces_sampler: Option<&TracesSampler>,
608 ctx: &TransactionContext,
609 traces_sample_rate: f32,
610) -> f32 {
611 match (traces_sampler, traces_sample_rate) {
612 (Some(traces_sampler), _) => traces_sampler(ctx),
613 (None, traces_sample_rate) => ctx.sampled.map(f32::from).unwrap_or(traces_sample_rate),
614 }
615}
616
617#[cfg(feature = "client")]
619impl Client {
620 fn determine_sampling_decision(&self, ctx: &TransactionContext) -> (bool, f32) {
621 let client_options = self.options();
622 let sample_rate = transaction_sample_rate(
623 client_options.traces_sampler.as_deref(),
624 ctx,
625 client_options.traces_sample_rate,
626 );
627 let sampled = self.sample_should_send(sample_rate);
628 (sampled, sample_rate)
629 }
630}
631
632#[cfg(feature = "client")]
634#[derive(Clone, Debug)]
635struct TransactionMetadata {
636 sample_rate: f32,
638}
639
640#[derive(Clone, Debug)]
646pub struct Transaction {
647 pub(crate) inner: TransactionArc,
648 #[cfg(feature = "client")]
649 metadata: TransactionMetadata,
650}
651
652pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
654
655impl<'a> TransactionData<'a> {
656 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
663 if self.0.transaction.is_some() {
664 Box::new(self.0.context.data.iter())
665 } else {
666 Box::new(std::iter::empty())
667 }
668 }
669
670 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
672 if self.0.transaction.is_some() {
673 self.0.context.data.insert(key.into(), value);
674 }
675 }
676
677 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
679 if let Some(transaction) = self.0.transaction.as_mut() {
680 transaction.tags.insert(key.into(), value);
681 }
682 }
683}
684
685impl Transaction {
686 #[cfg(feature = "client")]
687 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
688 let ((sampled, sample_rate), transaction) = match client.as_ref() {
689 Some(client) => (
690 client.determine_sampling_decision(&ctx),
691 Some(protocol::Transaction {
692 name: Some(ctx.name),
693 ..Default::default()
694 }),
695 ),
696 None => (
697 (
698 ctx.sampled.unwrap_or(false),
699 ctx.sampled.map_or(0.0, f32::from),
700 ),
701 None,
702 ),
703 };
704
705 let context = protocol::TraceContext {
706 trace_id: ctx.trace_id,
707 parent_span_id: ctx.parent_span_id,
708 span_id: ctx.span_id,
709 op: Some(ctx.op),
710 ..Default::default()
711 };
712
713 Self {
714 inner: Arc::new(Mutex::new(TransactionInner {
715 client,
716 sampled,
717 context,
718 transaction,
719 })),
720 metadata: TransactionMetadata { sample_rate },
721 }
722 }
723
724 #[cfg(not(feature = "client"))]
725 fn new_noop(ctx: TransactionContext) -> Self {
726 let context = protocol::TraceContext {
727 trace_id: ctx.trace_id,
728 parent_span_id: ctx.parent_span_id,
729 op: Some(ctx.op),
730 ..Default::default()
731 };
732 let sampled = ctx.sampled.unwrap_or(false);
733
734 Self {
735 inner: Arc::new(Mutex::new(TransactionInner {
736 sampled,
737 context,
738 transaction: None,
739 })),
740 }
741 }
742
743 pub fn set_data(&self, key: &str, value: protocol::Value) {
745 let mut inner = self.inner.lock().unwrap();
746 if inner.transaction.is_some() {
747 inner.context.data.insert(key.into(), value);
748 }
749 }
750
751 pub fn set_extra(&self, key: &str, value: protocol::Value) {
753 let mut inner = self.inner.lock().unwrap();
754 if let Some(transaction) = inner.transaction.as_mut() {
755 transaction.extra.insert(key.into(), value);
756 }
757 }
758
759 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
761 let mut inner = self.inner.lock().unwrap();
762 if let Some(transaction) = inner.transaction.as_mut() {
763 transaction.tags.insert(key.into(), value.to_string());
764 }
765 }
766
767 pub fn data(&self) -> TransactionData<'_> {
777 TransactionData(self.inner.lock().unwrap())
778 }
779
780 pub fn get_trace_context(&self) -> protocol::TraceContext {
784 let inner = self.inner.lock().unwrap();
785 inner.context.clone()
786 }
787
788 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
790 let inner = self.inner.lock().unwrap();
791 inner.context.status
792 }
793
794 pub fn set_status(&self, status: protocol::SpanStatus) {
796 let mut inner = self.inner.lock().unwrap();
797 inner.context.status = Some(status);
798 }
799
800 pub fn set_op(&self, op: &str) {
802 let mut inner = self.inner.lock().unwrap();
803 inner.context.op = Some(op.to_string());
804 }
805
806 pub fn set_name(&self, name: &str) {
808 let mut inner = self.inner.lock().unwrap();
809 if let Some(transaction) = inner.transaction.as_mut() {
810 transaction.name = Some(name.to_string());
811 }
812 }
813
814 pub fn set_request(&self, request: protocol::Request) {
816 let mut inner = self.inner.lock().unwrap();
817 if let Some(transaction) = inner.transaction.as_mut() {
818 transaction.request = Some(request);
819 }
820 }
821
822 pub fn set_origin(&self, origin: &str) {
824 let mut inner = self.inner.lock().unwrap();
825 inner.context.origin = Some(origin.to_owned());
826 }
827
828 pub fn iter_headers(&self) -> TraceHeadersIter {
832 let inner = self.inner.lock().unwrap();
833 let trace = SentryTrace::new(
834 inner.context.trace_id,
835 inner.context.span_id,
836 Some(inner.sampled),
837 );
838 TraceHeadersIter {
839 sentry_trace: Some(trace.to_string()),
840 }
841 }
842
843 pub fn is_sampled(&self) -> bool {
845 self.inner.lock().unwrap().sampled
846 }
847
848 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
853 with_client_impl! {{
854 let mut inner = self.inner.lock().unwrap();
855
856 if !inner.sampled {
858 return;
859 }
860
861 if let Some(mut transaction) = inner.transaction.take() {
862 if let Some(client) = inner.client.take() {
863 transaction.finish_with_timestamp(_timestamp);
864 transaction
865 .contexts
866 .insert("trace".into(), inner.context.clone().into());
867
868 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
869 let opts = client.options();
870 transaction.release.clone_from(&opts.release);
871 transaction.environment.clone_from(&opts.environment);
872 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
873 transaction.server_name.clone_from(&opts.server_name);
874
875 let mut dsc = protocol::DynamicSamplingContext::new()
876 .with_trace_id(inner.context.trace_id)
877 .with_sample_rate(self.metadata.sample_rate)
878 .with_sampled(inner.sampled);
879 if let Some(public_key) = client.dsn().map(|dsn| dsn.public_key()) {
880 dsc = dsc.with_public_key(public_key.to_owned());
881 }
882
883 drop(inner);
884
885 let mut envelope = protocol::Envelope::new().with_headers(
886 protocol::EnvelopeHeaders::new().with_trace(dsc)
887 );
888 envelope.add_item(transaction);
889
890 client.send_envelope(envelope)
891 }
892 }
893 }}
894 }
895
896 pub fn finish(self) {
901 self.finish_with_timestamp(SystemTime::now());
902 }
903
904 #[must_use = "a span must be explicitly closed via `finish()`"]
908 pub fn start_child(&self, op: &str, description: &str) -> Span {
909 let inner = self.inner.lock().unwrap();
910 let span = protocol::Span {
911 trace_id: inner.context.trace_id,
912 parent_span_id: Some(inner.context.span_id),
913 op: Some(op.into()),
914 description: if description.is_empty() {
915 None
916 } else {
917 Some(description.into())
918 },
919 ..Default::default()
920 };
921 Span {
922 transaction: Arc::clone(&self.inner),
923 sampled: inner.sampled,
924 span: Arc::new(Mutex::new(span)),
925 }
926 }
927
928 #[must_use = "a span must be explicitly closed via `finish()`"]
932 pub fn start_child_with_details(
933 &self,
934 op: &str,
935 description: &str,
936 id: SpanId,
937 timestamp: SystemTime,
938 ) -> Span {
939 let inner = self.inner.lock().unwrap();
940 let span = protocol::Span {
941 trace_id: inner.context.trace_id,
942 parent_span_id: Some(inner.context.span_id),
943 op: Some(op.into()),
944 description: if description.is_empty() {
945 None
946 } else {
947 Some(description.into())
948 },
949 span_id: id,
950 start_timestamp: timestamp,
951 ..Default::default()
952 };
953 Span {
954 transaction: Arc::clone(&self.inner),
955 sampled: inner.sampled,
956 span: Arc::new(Mutex::new(span)),
957 }
958 }
959}
960
961pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
963
964impl Data<'_> {
965 pub fn set_data(&mut self, key: String, value: protocol::Value) {
967 self.0.data.insert(key, value);
968 }
969
970 pub fn set_tag(&mut self, key: String, value: String) {
972 self.0.tags.insert(key, value);
973 }
974}
975
976impl Deref for Data<'_> {
977 type Target = BTreeMap<String, protocol::Value>;
978
979 fn deref(&self) -> &Self::Target {
980 &self.0.data
981 }
982}
983
984impl DerefMut for Data<'_> {
985 fn deref_mut(&mut self) -> &mut Self::Target {
986 &mut self.0.data
987 }
988}
989
990#[derive(Clone, Debug)]
995pub struct Span {
996 pub(crate) transaction: TransactionArc,
997 sampled: bool,
998 span: SpanArc,
999}
1000
1001type SpanArc = Arc<Mutex<protocol::Span>>;
1002
1003impl Span {
1004 pub fn set_data(&self, key: &str, value: protocol::Value) {
1006 let mut span = self.span.lock().unwrap();
1007 span.data.insert(key.into(), value);
1008 }
1009
1010 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
1012 let mut span = self.span.lock().unwrap();
1013 span.tags.insert(key.into(), value.to_string());
1014 }
1015
1016 pub fn data(&self) -> Data<'_> {
1028 Data(self.span.lock().unwrap())
1029 }
1030
1031 pub fn get_trace_context(&self) -> protocol::TraceContext {
1035 let transaction = self.transaction.lock().unwrap();
1036 transaction.context.clone()
1037 }
1038
1039 pub fn get_span_id(&self) -> protocol::SpanId {
1041 let span = self.span.lock().unwrap();
1042 span.span_id
1043 }
1044
1045 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
1047 let span = self.span.lock().unwrap();
1048 span.status
1049 }
1050
1051 pub fn set_status(&self, status: protocol::SpanStatus) {
1053 let mut span = self.span.lock().unwrap();
1054 span.status = Some(status);
1055 }
1056
1057 pub fn set_op(&self, op: &str) {
1059 let mut span = self.span.lock().unwrap();
1060 span.op = Some(op.to_string());
1061 }
1062
1063 pub fn set_name(&self, name: &str) {
1065 let mut span = self.span.lock().unwrap();
1066 span.description = Some(name.to_string());
1067 }
1068
1069 pub fn set_request(&self, request: protocol::Request) {
1071 let mut span = self.span.lock().unwrap();
1072 if let Some(method) = request.method {
1074 span.data.insert("method".into(), method.into());
1075 }
1076 if let Some(url) = request.url {
1077 span.data.insert("url".into(), url.to_string().into());
1078 }
1079 if let Some(data) = request.data {
1080 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1081 span.data.insert("data".into(), data);
1082 } else {
1083 span.data.insert("data".into(), data.into());
1084 }
1085 }
1086 if let Some(query_string) = request.query_string {
1087 span.data.insert("query_string".into(), query_string.into());
1088 }
1089 if let Some(cookies) = request.cookies {
1090 span.data.insert("cookies".into(), cookies.into());
1091 }
1092 if !request.headers.is_empty() {
1093 if let Ok(headers) = serde_json::to_value(request.headers) {
1094 span.data.insert("headers".into(), headers);
1095 }
1096 }
1097 if !request.env.is_empty() {
1098 if let Ok(env) = serde_json::to_value(request.env) {
1099 span.data.insert("env".into(), env);
1100 }
1101 }
1102 }
1103
1104 pub fn iter_headers(&self) -> TraceHeadersIter {
1108 let span = self.span.lock().unwrap();
1109 let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
1110 TraceHeadersIter {
1111 sentry_trace: Some(trace.to_string()),
1112 }
1113 }
1114
1115 pub fn is_sampled(&self) -> bool {
1117 self.sampled
1118 }
1119
1120 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1125 with_client_impl! {{
1126 let mut span = self.span.lock().unwrap();
1127 if span.timestamp.is_some() {
1128 return;
1130 }
1131 span.finish_with_timestamp(_timestamp);
1132 let mut inner = self.transaction.lock().unwrap();
1133 if let Some(transaction) = inner.transaction.as_mut() {
1134 if transaction.spans.len() <= MAX_SPANS {
1135 transaction.spans.push(span.clone());
1136 }
1137 }
1138 }}
1139 }
1140
1141 pub fn finish(self) {
1146 self.finish_with_timestamp(SystemTime::now());
1147 }
1148
1149 #[must_use = "a span must be explicitly closed via `finish()`"]
1153 pub fn start_child(&self, op: &str, description: &str) -> Span {
1154 let span = self.span.lock().unwrap();
1155 let span = protocol::Span {
1156 trace_id: span.trace_id,
1157 parent_span_id: Some(span.span_id),
1158 op: Some(op.into()),
1159 description: if description.is_empty() {
1160 None
1161 } else {
1162 Some(description.into())
1163 },
1164 ..Default::default()
1165 };
1166 Span {
1167 transaction: self.transaction.clone(),
1168 sampled: self.sampled,
1169 span: Arc::new(Mutex::new(span)),
1170 }
1171 }
1172
1173 #[must_use = "a span must be explicitly closed via `finish()`"]
1177 fn start_child_with_details(
1178 &self,
1179 op: &str,
1180 description: &str,
1181 id: SpanId,
1182 timestamp: SystemTime,
1183 ) -> Span {
1184 let span = self.span.lock().unwrap();
1185 let span = protocol::Span {
1186 trace_id: span.trace_id,
1187 parent_span_id: Some(span.span_id),
1188 op: Some(op.into()),
1189 description: if description.is_empty() {
1190 None
1191 } else {
1192 Some(description.into())
1193 },
1194 span_id: id,
1195 start_timestamp: timestamp,
1196 ..Default::default()
1197 };
1198 Span {
1199 transaction: self.transaction.clone(),
1200 sampled: self.sampled,
1201 span: Arc::new(Mutex::new(span)),
1202 }
1203 }
1204}
1205
1206pub type TraceHeader = (&'static str, String);
1208
1209pub struct TraceHeadersIter {
1214 sentry_trace: Option<String>,
1215}
1216
1217impl TraceHeadersIter {
1218 #[cfg(feature = "client")]
1219 pub(crate) fn new(sentry_trace: String) -> Self {
1220 Self {
1221 sentry_trace: Some(sentry_trace),
1222 }
1223 }
1224}
1225
1226impl Iterator for TraceHeadersIter {
1227 type Item = (&'static str, String);
1228
1229 fn next(&mut self) -> Option<Self::Item> {
1230 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1231 }
1232}
1233
1234#[derive(Debug, PartialEq, Clone, Copy, Default)]
1237pub struct SentryTrace {
1238 pub(crate) trace_id: protocol::TraceId,
1239 pub(crate) span_id: protocol::SpanId,
1240 pub(crate) sampled: Option<bool>,
1241}
1242
1243impl SentryTrace {
1244 pub fn new(
1246 trace_id: protocol::TraceId,
1247 span_id: protocol::SpanId,
1248 sampled: Option<bool>,
1249 ) -> Self {
1250 SentryTrace {
1251 trace_id,
1252 span_id,
1253 sampled,
1254 }
1255 }
1256}
1257
1258fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1259 let header = header.trim();
1260 let mut parts = header.splitn(3, '-');
1261
1262 let trace_id = parts.next()?.parse().ok()?;
1263 let parent_span_id = parts.next()?.parse().ok()?;
1264 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1265 "1" => Some(true),
1266 "0" => Some(false),
1267 _ => None,
1268 });
1269
1270 Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
1271}
1272
1273pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1276 headers: I,
1277) -> Option<SentryTrace> {
1278 let mut trace = None;
1279 for (k, v) in headers.into_iter() {
1280 if k.eq_ignore_ascii_case("sentry-trace") {
1281 trace = parse_sentry_trace(v);
1282 break;
1283 }
1284 }
1285 trace
1286}
1287
1288impl std::fmt::Display for SentryTrace {
1289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1290 write!(f, "{}-{}", self.trace_id, self.span_id)?;
1291 if let Some(sampled) = self.sampled {
1292 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1293 }
1294 Ok(())
1295 }
1296}
1297
1298#[cfg(test)]
1299mod tests {
1300 use std::str::FromStr;
1301
1302 use super::*;
1303
1304 #[test]
1305 fn parses_sentry_trace() {
1306 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1307 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1308
1309 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1310 assert_eq!(
1311 trace,
1312 Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
1313 );
1314
1315 let trace = SentryTrace::new(Default::default(), Default::default(), None);
1316 let parsed = parse_sentry_trace(&trace.to_string());
1317 assert_eq!(parsed, Some(trace));
1318 }
1319
1320 #[test]
1321 fn disabled_forwards_trace_id() {
1322 let headers = [(
1323 "SenTrY-TRAce",
1324 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1325 )];
1326 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1327 let trx = start_transaction(ctx);
1328
1329 let span = trx.start_child("noop", "noop");
1330
1331 let header = span.iter_headers().next().unwrap().1;
1332 let parsed = parse_sentry_trace(&header).unwrap();
1333
1334 assert_eq!(
1335 &parsed.trace_id.to_string(),
1336 "09e04486820349518ac7b5d2adbf6ba5"
1337 );
1338 assert_eq!(parsed.sampled, Some(true));
1339 }
1340
1341 #[test]
1342 fn transaction_context_public_getters() {
1343 let mut ctx = TransactionContext::new("test-name", "test-operation");
1344 assert_eq!(ctx.name(), "test-name");
1345 assert_eq!(ctx.operation(), "test-operation");
1346 assert_eq!(ctx.sampled(), None);
1347
1348 ctx.set_sampled(true);
1349 assert_eq!(ctx.sampled(), Some(true));
1350 }
1351
1352 #[cfg(feature = "client")]
1353 #[test]
1354 fn compute_transaction_sample_rate() {
1355 let ctx = TransactionContext::new("noop", "noop");
1357 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1358 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1359
1360 let mut ctx = TransactionContext::new("noop", "noop");
1362 ctx.set_sampled(true);
1363 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1364 ctx.set_sampled(false);
1365 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1366
1367 let mut ctx = TransactionContext::new("noop", "noop");
1369 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1370 ctx.set_sampled(false);
1371 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1372 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1374 Some(true) => 0.8,
1375 Some(false) => 0.4,
1376 None => 0.6,
1377 };
1378 ctx.set_sampled(true);
1379 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1380 ctx.set_sampled(None);
1381 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1382
1383 let sampler = |ctx: &TransactionContext| {
1385 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1386 return 1.0;
1387 }
1388
1389 if let Some(custom) = ctx.custom() {
1390 if let Some(rate) = custom.get("rate") {
1391 if let Some(rate) = rate.as_f64() {
1392 return rate as f32;
1393 }
1394 }
1395 }
1396
1397 0.1
1398 };
1399 let ctx = TransactionContext::new("noop", "must-operation");
1401 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1402 let ctx = TransactionContext::new("must-name", "noop");
1403 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1404 let mut ctx = TransactionContext::new("noop", "noop");
1406 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1407 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1408 }
1409}