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, PartialEq)]
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
961impl PartialEq for Transaction {
962 fn eq(&self, other: &Self) -> bool {
963 Arc::ptr_eq(&self.inner, &other.inner)
964 }
965}
966
967pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
969
970impl Data<'_> {
971 pub fn set_data(&mut self, key: String, value: protocol::Value) {
973 self.0.data.insert(key, value);
974 }
975
976 pub fn set_tag(&mut self, key: String, value: String) {
978 self.0.tags.insert(key, value);
979 }
980}
981
982impl Deref for Data<'_> {
983 type Target = BTreeMap<String, protocol::Value>;
984
985 fn deref(&self) -> &Self::Target {
986 &self.0.data
987 }
988}
989
990impl DerefMut for Data<'_> {
991 fn deref_mut(&mut self) -> &mut Self::Target {
992 &mut self.0.data
993 }
994}
995
996#[derive(Clone, Debug)]
1001pub struct Span {
1002 pub(crate) transaction: TransactionArc,
1003 sampled: bool,
1004 span: SpanArc,
1005}
1006
1007type SpanArc = Arc<Mutex<protocol::Span>>;
1008
1009impl Span {
1010 pub fn set_data(&self, key: &str, value: protocol::Value) {
1012 let mut span = self.span.lock().unwrap();
1013 span.data.insert(key.into(), value);
1014 }
1015
1016 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
1018 let mut span = self.span.lock().unwrap();
1019 span.tags.insert(key.into(), value.to_string());
1020 }
1021
1022 pub fn data(&self) -> Data<'_> {
1034 Data(self.span.lock().unwrap())
1035 }
1036
1037 pub fn get_trace_context(&self) -> protocol::TraceContext {
1041 let transaction = self.transaction.lock().unwrap();
1042 transaction.context.clone()
1043 }
1044
1045 pub fn get_span_id(&self) -> protocol::SpanId {
1047 let span = self.span.lock().unwrap();
1048 span.span_id
1049 }
1050
1051 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
1053 let span = self.span.lock().unwrap();
1054 span.status
1055 }
1056
1057 pub fn set_status(&self, status: protocol::SpanStatus) {
1059 let mut span = self.span.lock().unwrap();
1060 span.status = Some(status);
1061 }
1062
1063 pub fn set_op(&self, op: &str) {
1065 let mut span = self.span.lock().unwrap();
1066 span.op = Some(op.to_string());
1067 }
1068
1069 pub fn set_name(&self, name: &str) {
1071 let mut span = self.span.lock().unwrap();
1072 span.description = Some(name.to_string());
1073 }
1074
1075 pub fn set_request(&self, request: protocol::Request) {
1077 let mut span = self.span.lock().unwrap();
1078 if let Some(method) = request.method {
1080 span.data.insert("method".into(), method.into());
1081 }
1082 if let Some(url) = request.url {
1083 span.data.insert("url".into(), url.to_string().into());
1084 }
1085 if let Some(data) = request.data {
1086 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1087 span.data.insert("data".into(), data);
1088 } else {
1089 span.data.insert("data".into(), data.into());
1090 }
1091 }
1092 if let Some(query_string) = request.query_string {
1093 span.data.insert("query_string".into(), query_string.into());
1094 }
1095 if let Some(cookies) = request.cookies {
1096 span.data.insert("cookies".into(), cookies.into());
1097 }
1098 if !request.headers.is_empty() {
1099 if let Ok(headers) = serde_json::to_value(request.headers) {
1100 span.data.insert("headers".into(), headers);
1101 }
1102 }
1103 if !request.env.is_empty() {
1104 if let Ok(env) = serde_json::to_value(request.env) {
1105 span.data.insert("env".into(), env);
1106 }
1107 }
1108 }
1109
1110 pub fn iter_headers(&self) -> TraceHeadersIter {
1114 let span = self.span.lock().unwrap();
1115 let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
1116 TraceHeadersIter {
1117 sentry_trace: Some(trace.to_string()),
1118 }
1119 }
1120
1121 pub fn is_sampled(&self) -> bool {
1123 self.sampled
1124 }
1125
1126 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1131 with_client_impl! {{
1132 let mut span = self.span.lock().unwrap();
1133 if span.timestamp.is_some() {
1134 return;
1136 }
1137 span.finish_with_timestamp(_timestamp);
1138 let mut inner = self.transaction.lock().unwrap();
1139 if let Some(transaction) = inner.transaction.as_mut() {
1140 if transaction.spans.len() <= MAX_SPANS {
1141 transaction.spans.push(span.clone());
1142 }
1143 }
1144 }}
1145 }
1146
1147 pub fn finish(self) {
1152 self.finish_with_timestamp(SystemTime::now());
1153 }
1154
1155 #[must_use = "a span must be explicitly closed via `finish()`"]
1159 pub fn start_child(&self, op: &str, description: &str) -> Span {
1160 let span = self.span.lock().unwrap();
1161 let span = protocol::Span {
1162 trace_id: span.trace_id,
1163 parent_span_id: Some(span.span_id),
1164 op: Some(op.into()),
1165 description: if description.is_empty() {
1166 None
1167 } else {
1168 Some(description.into())
1169 },
1170 ..Default::default()
1171 };
1172 Span {
1173 transaction: self.transaction.clone(),
1174 sampled: self.sampled,
1175 span: Arc::new(Mutex::new(span)),
1176 }
1177 }
1178
1179 #[must_use = "a span must be explicitly closed via `finish()`"]
1183 fn start_child_with_details(
1184 &self,
1185 op: &str,
1186 description: &str,
1187 id: SpanId,
1188 timestamp: SystemTime,
1189 ) -> Span {
1190 let span = self.span.lock().unwrap();
1191 let span = protocol::Span {
1192 trace_id: span.trace_id,
1193 parent_span_id: Some(span.span_id),
1194 op: Some(op.into()),
1195 description: if description.is_empty() {
1196 None
1197 } else {
1198 Some(description.into())
1199 },
1200 span_id: id,
1201 start_timestamp: timestamp,
1202 ..Default::default()
1203 };
1204 Span {
1205 transaction: self.transaction.clone(),
1206 sampled: self.sampled,
1207 span: Arc::new(Mutex::new(span)),
1208 }
1209 }
1210}
1211
1212impl PartialEq for Span {
1213 fn eq(&self, other: &Self) -> bool {
1214 Arc::ptr_eq(&self.span, &other.span)
1215 }
1216}
1217
1218pub type TraceHeader = (&'static str, String);
1220
1221pub struct TraceHeadersIter {
1226 sentry_trace: Option<String>,
1227}
1228
1229impl TraceHeadersIter {
1230 #[cfg(feature = "client")]
1231 pub(crate) fn new(sentry_trace: String) -> Self {
1232 Self {
1233 sentry_trace: Some(sentry_trace),
1234 }
1235 }
1236}
1237
1238impl Iterator for TraceHeadersIter {
1239 type Item = (&'static str, String);
1240
1241 fn next(&mut self) -> Option<Self::Item> {
1242 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1243 }
1244}
1245
1246#[derive(Debug, PartialEq, Clone, Copy, Default)]
1249pub struct SentryTrace {
1250 pub(crate) trace_id: protocol::TraceId,
1251 pub(crate) span_id: protocol::SpanId,
1252 pub(crate) sampled: Option<bool>,
1253}
1254
1255impl SentryTrace {
1256 pub fn new(
1258 trace_id: protocol::TraceId,
1259 span_id: protocol::SpanId,
1260 sampled: Option<bool>,
1261 ) -> Self {
1262 SentryTrace {
1263 trace_id,
1264 span_id,
1265 sampled,
1266 }
1267 }
1268}
1269
1270fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1271 let header = header.trim();
1272 let mut parts = header.splitn(3, '-');
1273
1274 let trace_id = parts.next()?.parse().ok()?;
1275 let parent_span_id = parts.next()?.parse().ok()?;
1276 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1277 "1" => Some(true),
1278 "0" => Some(false),
1279 _ => None,
1280 });
1281
1282 Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
1283}
1284
1285pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1288 headers: I,
1289) -> Option<SentryTrace> {
1290 let mut trace = None;
1291 for (k, v) in headers.into_iter() {
1292 if k.eq_ignore_ascii_case("sentry-trace") {
1293 trace = parse_sentry_trace(v);
1294 break;
1295 }
1296 }
1297 trace
1298}
1299
1300impl std::fmt::Display for SentryTrace {
1301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1302 write!(f, "{}-{}", self.trace_id, self.span_id)?;
1303 if let Some(sampled) = self.sampled {
1304 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1305 }
1306 Ok(())
1307 }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312 use std::str::FromStr;
1313
1314 use super::*;
1315
1316 #[test]
1317 fn parses_sentry_trace() {
1318 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1319 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1320
1321 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1322 assert_eq!(
1323 trace,
1324 Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
1325 );
1326
1327 let trace = SentryTrace::new(Default::default(), Default::default(), None);
1328 let parsed = parse_sentry_trace(&trace.to_string());
1329 assert_eq!(parsed, Some(trace));
1330 }
1331
1332 #[test]
1333 fn disabled_forwards_trace_id() {
1334 let headers = [(
1335 "SenTrY-TRAce",
1336 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1337 )];
1338 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1339 let trx = start_transaction(ctx);
1340
1341 let span = trx.start_child("noop", "noop");
1342
1343 let header = span.iter_headers().next().unwrap().1;
1344 let parsed = parse_sentry_trace(&header).unwrap();
1345
1346 assert_eq!(
1347 &parsed.trace_id.to_string(),
1348 "09e04486820349518ac7b5d2adbf6ba5"
1349 );
1350 assert_eq!(parsed.sampled, Some(true));
1351 }
1352
1353 #[test]
1354 fn transaction_context_public_getters() {
1355 let mut ctx = TransactionContext::new("test-name", "test-operation");
1356 assert_eq!(ctx.name(), "test-name");
1357 assert_eq!(ctx.operation(), "test-operation");
1358 assert_eq!(ctx.sampled(), None);
1359
1360 ctx.set_sampled(true);
1361 assert_eq!(ctx.sampled(), Some(true));
1362 }
1363
1364 #[cfg(feature = "client")]
1365 #[test]
1366 fn compute_transaction_sample_rate() {
1367 let ctx = TransactionContext::new("noop", "noop");
1369 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1370 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1371
1372 let mut ctx = TransactionContext::new("noop", "noop");
1374 ctx.set_sampled(true);
1375 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1376 ctx.set_sampled(false);
1377 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1378
1379 let mut ctx = TransactionContext::new("noop", "noop");
1381 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1382 ctx.set_sampled(false);
1383 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1384 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1386 Some(true) => 0.8,
1387 Some(false) => 0.4,
1388 None => 0.6,
1389 };
1390 ctx.set_sampled(true);
1391 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1392 ctx.set_sampled(None);
1393 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1394
1395 let sampler = |ctx: &TransactionContext| {
1397 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1398 return 1.0;
1399 }
1400
1401 if let Some(custom) = ctx.custom() {
1402 if let Some(rate) = custom.get("rate") {
1403 if let Some(rate) = rate.as_f64() {
1404 return rate as f32;
1405 }
1406 }
1407 }
1408
1409 0.1
1410 };
1411 let ctx = TransactionContext::new("noop", "must-operation");
1413 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1414 let ctx = TransactionContext::new("must-name", "noop");
1415 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1416 let mut ctx = TransactionContext::new("noop", "noop");
1418 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1419 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1420 }
1421}