1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::ops::{Deref, DerefMut};
4use std::sync::{Arc, Mutex, MutexGuard};
5use std::time::SystemTime;
6
7#[cfg(feature = "client")]
8use sentry_types::protocol::v7::client_report::Reason as ClientReportReason;
9use sentry_types::protocol::v7::SpanId;
10
11use crate::{protocol, Hub};
12
13#[cfg(feature = "client")]
14use crate::Client;
15
16#[cfg(feature = "client")]
17const MAX_SPANS: usize = 1_000;
18
19pub fn start_transaction(ctx: TransactionContext) -> Transaction {
28 #[cfg(feature = "client")]
29 {
30 let client = Hub::with_active(|hub| hub.client());
31 Transaction::new(client, ctx)
32 }
33 #[cfg(not(feature = "client"))]
34 {
35 Transaction::new_noop(ctx)
36 }
37}
38
39pub fn start_transaction_with_timestamp(
46 ctx: TransactionContext,
47 timestamp: SystemTime,
48) -> Transaction {
49 let transaction = start_transaction(ctx);
50 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
51 tx.start_timestamp = timestamp;
52 }
53 transaction
54}
55
56impl Hub {
59 pub fn start_transaction(&self, ctx: TransactionContext) -> Transaction {
63 #[cfg(feature = "client")]
64 {
65 Transaction::new(self.client(), ctx)
66 }
67 #[cfg(not(feature = "client"))]
68 {
69 Transaction::new_noop(ctx)
70 }
71 }
72
73 pub fn start_transaction_with_timestamp(
77 &self,
78 ctx: TransactionContext,
79 timestamp: SystemTime,
80 ) -> Transaction {
81 let transaction = start_transaction(ctx);
82 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
83 tx.start_timestamp = timestamp;
84 }
85 transaction
86 }
87}
88
89pub type CustomTransactionContext = serde_json::Map<String, serde_json::Value>;
97
98#[derive(Debug, Clone)]
103pub struct TransactionContext {
104 #[cfg_attr(not(feature = "client"), allow(dead_code))]
105 name: String,
106 op: String,
107 trace_id: protocol::TraceId,
108 parent_span_id: Option<protocol::SpanId>,
109 span_id: protocol::SpanId,
110 sampled: Option<bool>,
111 custom: Option<CustomTransactionContext>,
112}
113
114impl TransactionContext {
115 #[must_use = "this must be used with `start_transaction`"]
127 pub fn new(name: &str, op: &str) -> Self {
128 Self::new_with_trace_id(name, op, protocol::TraceId::default())
129 }
130
131 #[must_use = "this must be used with `start_transaction`"]
138 pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self {
139 Self {
140 name: name.into(),
141 op: op.into(),
142 trace_id,
143 parent_span_id: None,
144 span_id: Default::default(),
145 sampled: None,
146 custom: None,
147 }
148 }
149
150 #[must_use = "this must be used with `start_transaction`"]
158 pub fn new_with_details(
159 name: &str,
160 op: &str,
161 trace_id: protocol::TraceId,
162 span_id: Option<protocol::SpanId>,
163 parent_span_id: Option<protocol::SpanId>,
164 ) -> Self {
165 let mut slf = Self::new_with_trace_id(name, op, trace_id);
166 if let Some(span_id) = span_id {
167 slf.span_id = span_id;
168 }
169 slf.parent_span_id = parent_span_id;
170 slf
171 }
172
173 #[must_use = "this must be used with `start_transaction`"]
178 pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
179 name: &str,
180 op: &str,
181 headers: I,
182 ) -> Self {
183 parse_headers(headers)
184 .map(|sentry_trace| Self::continue_from_sentry_trace(name, op, &sentry_trace, None))
185 .unwrap_or_else(|| Self {
186 name: name.into(),
187 op: op.into(),
188 trace_id: Default::default(),
189 parent_span_id: None,
190 span_id: Default::default(),
191 sampled: None,
192 custom: None,
193 })
194 }
195
196 pub fn continue_from_sentry_trace(
199 name: &str,
200 op: &str,
201 sentry_trace: &SentryTrace,
202 span_id: Option<SpanId>,
203 ) -> Self {
204 Self {
205 name: name.into(),
206 op: op.into(),
207 trace_id: sentry_trace.trace_id,
208 parent_span_id: Some(sentry_trace.span_id),
209 sampled: sentry_trace.sampled,
210 span_id: span_id.unwrap_or_default(),
211 custom: None,
212 }
213 }
214
215 pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
221 let span = match span {
222 Some(span) => span,
223 None => return Self::new(name, op),
224 };
225
226 let (trace_id, parent_span_id, sampled) = match span {
227 TransactionOrSpan::Transaction(transaction) => {
228 let inner = transaction.inner.lock().unwrap();
229 (
230 inner.context.trace_id,
231 inner.context.span_id,
232 Some(inner.sampled),
233 )
234 }
235 TransactionOrSpan::Span(span) => {
236 let sampled = span.sampled;
237 let span = span.span.lock().unwrap();
238 (span.trace_id, span.span_id, Some(sampled))
239 }
240 };
241
242 Self {
243 name: name.into(),
244 op: op.into(),
245 trace_id,
246 parent_span_id: Some(parent_span_id),
247 span_id: protocol::SpanId::default(),
248 sampled,
249 custom: None,
250 }
251 }
252
253 pub fn set_sampled(&mut self, sampled: impl Into<Option<bool>>) {
258 self.sampled = sampled.into();
259 }
260
261 pub fn sampled(&self) -> Option<bool> {
263 self.sampled
264 }
265
266 pub fn name(&self) -> &str {
268 &self.name
269 }
270
271 pub fn operation(&self) -> &str {
273 &self.op
274 }
275
276 pub fn trace_id(&self) -> protocol::TraceId {
278 self.trace_id
279 }
280
281 pub fn span_id(&self) -> protocol::SpanId {
283 self.span_id
284 }
285
286 pub fn custom(&self) -> Option<&CustomTransactionContext> {
288 self.custom.as_ref()
289 }
290
291 pub fn custom_mut(&mut self) -> &mut Option<CustomTransactionContext> {
295 &mut self.custom
296 }
297
298 pub fn custom_insert(
305 &mut self,
306 key: String,
307 value: serde_json::Value,
308 ) -> Option<serde_json::Value> {
309 let mut custom = None;
311 std::mem::swap(&mut self.custom, &mut custom);
312
313 let mut custom = custom.unwrap_or_default();
315
316 let existing_value = custom.insert(key, value);
318 self.custom = Some(custom);
319 existing_value
320 }
321
322 #[must_use]
329 pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
330 TransactionContextBuilder {
331 ctx: TransactionContext::new(name, op),
332 }
333 }
334}
335
336pub struct TransactionContextBuilder {
338 ctx: TransactionContext,
339}
340
341impl TransactionContextBuilder {
342 #[must_use]
344 pub fn with_name(mut self, name: String) -> Self {
345 self.ctx.name = name;
346 self
347 }
348
349 #[must_use]
351 pub fn with_op(mut self, op: String) -> Self {
352 self.ctx.op = op;
353 self
354 }
355
356 #[must_use]
358 pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
359 self.ctx.trace_id = trace_id;
360 self
361 }
362
363 #[must_use]
365 pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
366 self.ctx.parent_span_id = parent_span_id;
367 self
368 }
369
370 #[must_use]
372 pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
373 self.ctx.span_id = span_id;
374 self
375 }
376
377 #[must_use]
379 pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
380 self.ctx.sampled = sampled;
381 self
382 }
383
384 #[must_use]
386 pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
387 self.ctx.custom_insert(key, value);
388 self
389 }
390
391 pub fn finish(self) -> TransactionContext {
393 self.ctx
394 }
395}
396
397pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
403
404#[derive(Clone, Debug, PartialEq)]
408pub enum TransactionOrSpan {
409 Transaction(Transaction),
411 Span(Span),
413}
414
415impl From<Transaction> for TransactionOrSpan {
416 fn from(transaction: Transaction) -> Self {
417 Self::Transaction(transaction)
418 }
419}
420
421impl From<Span> for TransactionOrSpan {
422 fn from(span: Span) -> Self {
423 Self::Span(span)
424 }
425}
426
427impl TransactionOrSpan {
428 pub fn set_data(&self, key: &str, value: protocol::Value) {
430 match self {
431 TransactionOrSpan::Transaction(transaction) => transaction.set_data(key, value),
432 TransactionOrSpan::Span(span) => span.set_data(key, value),
433 }
434 }
435
436 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
438 match self {
439 TransactionOrSpan::Transaction(transaction) => transaction.set_tag(key, value),
440 TransactionOrSpan::Span(span) => span.set_tag(key, value),
441 }
442 }
443
444 pub fn get_trace_context(&self) -> protocol::TraceContext {
448 match self {
449 TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context(),
450 TransactionOrSpan::Span(span) => span.get_trace_context(),
451 }
452 }
453
454 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
456 match self {
457 TransactionOrSpan::Transaction(transaction) => transaction.get_status(),
458 TransactionOrSpan::Span(span) => span.get_status(),
459 }
460 }
461
462 pub fn set_status(&self, status: protocol::SpanStatus) {
464 match self {
465 TransactionOrSpan::Transaction(transaction) => transaction.set_status(status),
466 TransactionOrSpan::Span(span) => span.set_status(status),
467 }
468 }
469
470 pub fn set_op(&self, op: &str) {
472 match self {
473 TransactionOrSpan::Transaction(transaction) => transaction.set_op(op),
474 TransactionOrSpan::Span(span) => span.set_op(op),
475 }
476 }
477
478 pub fn set_name(&self, name: &str) {
480 match self {
481 TransactionOrSpan::Transaction(transaction) => transaction.set_name(name),
482 TransactionOrSpan::Span(span) => span.set_name(name),
483 }
484 }
485
486 pub fn set_request(&self, request: protocol::Request) {
488 match self {
489 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
490 TransactionOrSpan::Span(span) => span.set_request(request),
491 }
492 }
493
494 pub fn iter_headers(&self) -> TraceHeadersIter {
498 match self {
499 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
500 TransactionOrSpan::Span(span) => span.iter_headers(),
501 }
502 }
503
504 pub fn is_sampled(&self) -> bool {
506 match self {
507 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
508 TransactionOrSpan::Span(span) => span.is_sampled(),
509 }
510 }
511
512 #[must_use = "a span must be explicitly closed via `finish()`"]
517 pub fn start_child(&self, op: &str, description: &str) -> Span {
518 match self {
519 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
520 TransactionOrSpan::Span(span) => span.start_child(op, description),
521 }
522 }
523
524 #[must_use = "a span must be explicitly closed via `finish()`"]
529 pub fn start_child_with_details(
530 &self,
531 op: &str,
532 description: &str,
533 id: SpanId,
534 timestamp: SystemTime,
535 ) -> Span {
536 match self {
537 TransactionOrSpan::Transaction(transaction) => {
538 transaction.start_child_with_details(op, description, id, timestamp)
539 }
540 TransactionOrSpan::Span(span) => {
541 span.start_child_with_details(op, description, id, timestamp)
542 }
543 }
544 }
545
546 #[cfg(feature = "client")]
547 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
548 if event.contexts.contains_key("trace") {
549 return;
550 }
551
552 let context = match self {
553 TransactionOrSpan::Transaction(transaction) => {
554 transaction.inner.lock().unwrap().context.clone()
555 }
556 TransactionOrSpan::Span(span) => {
557 let span = span.span.lock().unwrap();
558 protocol::TraceContext {
559 span_id: span.span_id,
560 trace_id: span.trace_id,
561 ..Default::default()
562 }
563 }
564 };
565 event.contexts.insert("trace".into(), context.into());
566 }
567
568 pub fn finish_with_timestamp(self, timestamp: SystemTime) {
573 match self {
574 TransactionOrSpan::Transaction(transaction) => {
575 transaction.finish_with_timestamp(timestamp)
576 }
577 TransactionOrSpan::Span(span) => span.finish_with_timestamp(timestamp),
578 }
579 }
580
581 pub fn finish(self) {
586 match self {
587 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
588 TransactionOrSpan::Span(span) => span.finish(),
589 }
590 }
591}
592
593#[derive(Debug)]
594pub(crate) struct TransactionInner {
595 #[cfg(feature = "client")]
596 client: Option<Arc<Client>>,
597 sampled: bool,
598 pub(crate) context: protocol::TraceContext,
599 pub(crate) transaction: Option<protocol::Transaction<'static>>,
600}
601
602type TransactionArc = Arc<Mutex<TransactionInner>>;
603
604#[cfg(feature = "client")]
608fn transaction_sample_rate(
609 traces_sampler: Option<&TracesSampler>,
610 ctx: &TransactionContext,
611 traces_sample_rate: f32,
612) -> f32 {
613 match (traces_sampler, traces_sample_rate) {
614 (Some(traces_sampler), _) => traces_sampler(ctx),
615 (None, traces_sample_rate) => ctx.sampled.map(f32::from).unwrap_or(traces_sample_rate),
616 }
617}
618
619#[cfg(feature = "client")]
621impl Client {
622 fn determine_sampling_decision(&self, ctx: &TransactionContext) -> (bool, f32) {
623 let client_options = self.options();
624 let sample_rate = transaction_sample_rate(
625 client_options.traces_sampler.as_deref(),
626 ctx,
627 client_options.traces_sample_rate,
628 );
629 let sampled = self.sample_should_send(sample_rate);
630 (sampled, sample_rate)
631 }
632}
633
634#[cfg(feature = "client")]
636#[derive(Clone, Debug)]
637struct TransactionMetadata {
638 sample_rate: f32,
640}
641
642#[derive(Clone, Debug)]
648pub struct Transaction {
649 pub(crate) inner: TransactionArc,
650 #[cfg(feature = "client")]
651 metadata: TransactionMetadata,
652}
653
654pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
656
657impl<'a> TransactionData<'a> {
658 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
665 if self.0.transaction.is_some() {
666 Box::new(self.0.context.data.iter())
667 } else {
668 Box::new(std::iter::empty())
669 }
670 }
671
672 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
674 if self.0.transaction.is_some() {
675 self.0.context.data.insert(key.into(), value);
676 }
677 }
678
679 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
681 if let Some(transaction) = self.0.transaction.as_mut() {
682 transaction.tags.insert(key.into(), value);
683 }
684 }
685}
686
687impl Transaction {
688 #[cfg(feature = "client")]
689 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
690 let ((sampled, sample_rate), transaction) = match client.as_ref() {
691 Some(client) => (
692 client.determine_sampling_decision(&ctx),
693 Some(protocol::Transaction {
694 name: Some(ctx.name),
695 ..Default::default()
696 }),
697 ),
698 None => (
699 (
700 ctx.sampled.unwrap_or(false),
701 ctx.sampled.map_or(0.0, f32::from),
702 ),
703 None,
704 ),
705 };
706
707 let context = protocol::TraceContext {
708 trace_id: ctx.trace_id,
709 parent_span_id: ctx.parent_span_id,
710 span_id: ctx.span_id,
711 op: Some(ctx.op),
712 ..Default::default()
713 };
714
715 Self {
716 inner: Arc::new(Mutex::new(TransactionInner {
717 client,
718 sampled,
719 context,
720 transaction,
721 })),
722 metadata: TransactionMetadata { sample_rate },
723 }
724 }
725
726 #[cfg(not(feature = "client"))]
727 fn new_noop(ctx: TransactionContext) -> Self {
728 let context = protocol::TraceContext {
729 trace_id: ctx.trace_id,
730 parent_span_id: ctx.parent_span_id,
731 op: Some(ctx.op),
732 ..Default::default()
733 };
734 let sampled = ctx.sampled.unwrap_or(false);
735
736 Self {
737 inner: Arc::new(Mutex::new(TransactionInner {
738 sampled,
739 context,
740 transaction: None,
741 })),
742 }
743 }
744
745 pub fn set_data(&self, key: &str, value: protocol::Value) {
747 let mut inner = self.inner.lock().unwrap();
748 if inner.transaction.is_some() {
749 inner.context.data.insert(key.into(), value);
750 }
751 }
752
753 pub fn set_extra(&self, key: &str, value: protocol::Value) {
755 let mut inner = self.inner.lock().unwrap();
756 if let Some(transaction) = inner.transaction.as_mut() {
757 transaction.extra.insert(key.into(), value);
758 }
759 }
760
761 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
763 let mut inner = self.inner.lock().unwrap();
764 if let Some(transaction) = inner.transaction.as_mut() {
765 transaction.tags.insert(key.into(), value.to_string());
766 }
767 }
768
769 pub fn data(&self) -> TransactionData<'_> {
779 TransactionData(self.inner.lock().unwrap())
780 }
781
782 pub fn get_trace_context(&self) -> protocol::TraceContext {
786 let inner = self.inner.lock().unwrap();
787 inner.context.clone()
788 }
789
790 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
792 let inner = self.inner.lock().unwrap();
793 inner.context.status
794 }
795
796 pub fn set_status(&self, status: protocol::SpanStatus) {
798 let mut inner = self.inner.lock().unwrap();
799 inner.context.status = Some(status);
800 }
801
802 pub fn set_op(&self, op: &str) {
804 let mut inner = self.inner.lock().unwrap();
805 inner.context.op = Some(op.to_string());
806 }
807
808 pub fn set_name(&self, name: &str) {
810 let mut inner = self.inner.lock().unwrap();
811 if let Some(transaction) = inner.transaction.as_mut() {
812 transaction.name = Some(name.to_string());
813 }
814 }
815
816 pub fn set_request(&self, request: protocol::Request) {
818 let mut inner = self.inner.lock().unwrap();
819 if let Some(transaction) = inner.transaction.as_mut() {
820 transaction.request = Some(request);
821 }
822 }
823
824 pub fn set_origin(&self, origin: &str) {
826 let mut inner = self.inner.lock().unwrap();
827 inner.context.origin = Some(origin.to_owned());
828 }
829
830 pub fn iter_headers(&self) -> TraceHeadersIter {
834 let inner = self.inner.lock().unwrap();
835 let trace = SentryTrace::new(
836 inner.context.trace_id,
837 inner.context.span_id,
838 Some(inner.sampled),
839 );
840 TraceHeadersIter {
841 sentry_trace: Some(trace.to_string()),
842 }
843 }
844
845 pub fn is_sampled(&self) -> bool {
847 self.inner.lock().unwrap().sampled
848 }
849
850 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
855 with_client_impl! {{
856 let mut inner = self.inner.lock().unwrap();
857
858 if !inner.sampled {
860 if let Some(transaction) = inner.transaction.take() {
861 if let Some(client) = inner.client.as_ref() {
862 client.record_lost_data(&transaction, ClientReportReason::SampleRate);
863 }
864 }
865 return;
866 }
867
868 if let Some(mut transaction) = inner.transaction.take() {
869 if let Some(client) = inner.client.take() {
870 transaction.finish_with_timestamp(_timestamp);
871 transaction
872 .contexts
873 .insert("trace".into(), inner.context.clone().into());
874
875 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
876 let opts = client.options();
877 transaction.release.clone_from(&opts.release);
878 transaction.environment.clone_from(&opts.environment);
879 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
880 transaction.server_name.clone_from(&opts.server_name);
881
882 let mut dsc = protocol::DynamicSamplingContext::new()
883 .with_trace_id(inner.context.trace_id)
884 .with_sample_rate(self.metadata.sample_rate)
885 .with_sampled(inner.sampled);
886 if let Some(public_key) = client.dsn().map(|dsn| dsn.public_key()) {
887 dsc = dsc.with_public_key(public_key.to_owned());
888 }
889
890 drop(inner);
891
892 let mut envelope = protocol::Envelope::new().with_headers(
893 protocol::EnvelopeHeaders::new().with_trace(dsc)
894 );
895 envelope.add_item(transaction);
896
897 client.send_envelope(envelope)
898 }
899 }
900 }}
901 }
902
903 pub fn finish(self) {
908 self.finish_with_timestamp(SystemTime::now());
909 }
910
911 #[must_use = "a span must be explicitly closed via `finish()`"]
915 pub fn start_child(&self, op: &str, description: &str) -> Span {
916 let inner = self.inner.lock().unwrap();
917 let span = protocol::Span {
918 trace_id: inner.context.trace_id,
919 parent_span_id: Some(inner.context.span_id),
920 op: Some(op.into()),
921 description: if description.is_empty() {
922 None
923 } else {
924 Some(description.into())
925 },
926 ..Default::default()
927 };
928 Span {
929 transaction: Arc::clone(&self.inner),
930 sampled: inner.sampled,
931 span: Arc::new(Mutex::new(span)),
932 }
933 }
934
935 #[must_use = "a span must be explicitly closed via `finish()`"]
939 pub fn start_child_with_details(
940 &self,
941 op: &str,
942 description: &str,
943 id: SpanId,
944 timestamp: SystemTime,
945 ) -> Span {
946 let inner = self.inner.lock().unwrap();
947 let span = protocol::Span {
948 trace_id: inner.context.trace_id,
949 parent_span_id: Some(inner.context.span_id),
950 op: Some(op.into()),
951 description: if description.is_empty() {
952 None
953 } else {
954 Some(description.into())
955 },
956 span_id: id,
957 start_timestamp: timestamp,
958 ..Default::default()
959 };
960 Span {
961 transaction: Arc::clone(&self.inner),
962 sampled: inner.sampled,
963 span: Arc::new(Mutex::new(span)),
964 }
965 }
966}
967
968impl PartialEq for Transaction {
969 fn eq(&self, other: &Self) -> bool {
970 Arc::ptr_eq(&self.inner, &other.inner)
971 }
972}
973
974pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
976
977impl Data<'_> {
978 pub fn set_data(&mut self, key: String, value: protocol::Value) {
980 self.0.data.insert(key, value);
981 }
982
983 pub fn set_tag(&mut self, key: String, value: String) {
985 self.0.tags.insert(key, value);
986 }
987}
988
989impl Deref for Data<'_> {
990 type Target = BTreeMap<String, protocol::Value>;
991
992 fn deref(&self) -> &Self::Target {
993 &self.0.data
994 }
995}
996
997impl DerefMut for Data<'_> {
998 fn deref_mut(&mut self) -> &mut Self::Target {
999 &mut self.0.data
1000 }
1001}
1002
1003#[derive(Clone, Debug)]
1008pub struct Span {
1009 pub(crate) transaction: TransactionArc,
1010 sampled: bool,
1011 span: SpanArc,
1012}
1013
1014type SpanArc = Arc<Mutex<protocol::Span>>;
1015
1016impl Span {
1017 pub fn set_data(&self, key: &str, value: protocol::Value) {
1019 let mut span = self.span.lock().unwrap();
1020 span.data.insert(key.into(), value);
1021 }
1022
1023 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
1025 let mut span = self.span.lock().unwrap();
1026 span.tags.insert(key.into(), value.to_string());
1027 }
1028
1029 pub fn data(&self) -> Data<'_> {
1041 Data(self.span.lock().unwrap())
1042 }
1043
1044 pub fn get_trace_context(&self) -> protocol::TraceContext {
1048 let transaction = self.transaction.lock().unwrap();
1049 transaction.context.clone()
1050 }
1051
1052 pub fn get_span_id(&self) -> protocol::SpanId {
1054 let span = self.span.lock().unwrap();
1055 span.span_id
1056 }
1057
1058 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
1060 let span = self.span.lock().unwrap();
1061 span.status
1062 }
1063
1064 pub fn set_status(&self, status: protocol::SpanStatus) {
1066 let mut span = self.span.lock().unwrap();
1067 span.status = Some(status);
1068 }
1069
1070 pub fn set_op(&self, op: &str) {
1072 let mut span = self.span.lock().unwrap();
1073 span.op = Some(op.to_string());
1074 }
1075
1076 pub fn set_name(&self, name: &str) {
1078 let mut span = self.span.lock().unwrap();
1079 span.description = Some(name.to_string());
1080 }
1081
1082 pub fn set_request(&self, request: protocol::Request) {
1084 let mut span = self.span.lock().unwrap();
1085 if let Some(method) = request.method {
1087 span.data.insert("method".into(), method.into());
1088 }
1089 if let Some(url) = request.url {
1090 span.data.insert("url".into(), url.to_string().into());
1091 }
1092 if let Some(data) = request.data {
1093 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1094 span.data.insert("data".into(), data);
1095 } else {
1096 span.data.insert("data".into(), data.into());
1097 }
1098 }
1099 if let Some(query_string) = request.query_string {
1100 span.data.insert("query_string".into(), query_string.into());
1101 }
1102 if let Some(cookies) = request.cookies {
1103 span.data.insert("cookies".into(), cookies.into());
1104 }
1105 if !request.headers.is_empty() {
1106 if let Ok(headers) = serde_json::to_value(request.headers) {
1107 span.data.insert("headers".into(), headers);
1108 }
1109 }
1110 if !request.env.is_empty() {
1111 if let Ok(env) = serde_json::to_value(request.env) {
1112 span.data.insert("env".into(), env);
1113 }
1114 }
1115 }
1116
1117 pub fn iter_headers(&self) -> TraceHeadersIter {
1121 let span = self.span.lock().unwrap();
1122 let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
1123 TraceHeadersIter {
1124 sentry_trace: Some(trace.to_string()),
1125 }
1126 }
1127
1128 pub fn is_sampled(&self) -> bool {
1130 self.sampled
1131 }
1132
1133 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1138 with_client_impl! {{
1139 let mut span = self.span.lock().unwrap();
1140 if span.timestamp.is_some() {
1141 return;
1143 }
1144 span.finish_with_timestamp(_timestamp);
1145 let mut inner = self.transaction.lock().unwrap();
1146 if let Some(transaction) = inner.transaction.as_mut() {
1147 if transaction.spans.len() <= MAX_SPANS {
1148 transaction.spans.push(span.clone());
1149 } else if let Some(client) = inner.client.as_ref() {
1150 client.record_lost_data(&*span, ClientReportReason::BufferOverflow);
1151 }
1152 }
1153 }}
1154 }
1155
1156 pub fn finish(self) {
1161 self.finish_with_timestamp(SystemTime::now());
1162 }
1163
1164 #[must_use = "a span must be explicitly closed via `finish()`"]
1168 pub fn start_child(&self, op: &str, description: &str) -> Span {
1169 let span = self.span.lock().unwrap();
1170 let span = protocol::Span {
1171 trace_id: span.trace_id,
1172 parent_span_id: Some(span.span_id),
1173 op: Some(op.into()),
1174 description: if description.is_empty() {
1175 None
1176 } else {
1177 Some(description.into())
1178 },
1179 ..Default::default()
1180 };
1181 Span {
1182 transaction: self.transaction.clone(),
1183 sampled: self.sampled,
1184 span: Arc::new(Mutex::new(span)),
1185 }
1186 }
1187
1188 #[must_use = "a span must be explicitly closed via `finish()`"]
1192 fn start_child_with_details(
1193 &self,
1194 op: &str,
1195 description: &str,
1196 id: SpanId,
1197 timestamp: SystemTime,
1198 ) -> Span {
1199 let span = self.span.lock().unwrap();
1200 let span = protocol::Span {
1201 trace_id: span.trace_id,
1202 parent_span_id: Some(span.span_id),
1203 op: Some(op.into()),
1204 description: if description.is_empty() {
1205 None
1206 } else {
1207 Some(description.into())
1208 },
1209 span_id: id,
1210 start_timestamp: timestamp,
1211 ..Default::default()
1212 };
1213 Span {
1214 transaction: self.transaction.clone(),
1215 sampled: self.sampled,
1216 span: Arc::new(Mutex::new(span)),
1217 }
1218 }
1219}
1220
1221impl PartialEq for Span {
1222 fn eq(&self, other: &Self) -> bool {
1223 Arc::ptr_eq(&self.span, &other.span)
1224 }
1225}
1226
1227pub type TraceHeader = (&'static str, String);
1229
1230pub struct TraceHeadersIter {
1235 sentry_trace: Option<String>,
1236}
1237
1238impl TraceHeadersIter {
1239 #[cfg(feature = "client")]
1240 pub(crate) fn new(sentry_trace: String) -> Self {
1241 Self {
1242 sentry_trace: Some(sentry_trace),
1243 }
1244 }
1245}
1246
1247impl Iterator for TraceHeadersIter {
1248 type Item = (&'static str, String);
1249
1250 fn next(&mut self) -> Option<Self::Item> {
1251 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1252 }
1253}
1254
1255#[derive(Debug, PartialEq, Clone, Copy, Default)]
1258pub struct SentryTrace {
1259 pub(crate) trace_id: protocol::TraceId,
1260 pub(crate) span_id: protocol::SpanId,
1261 pub(crate) sampled: Option<bool>,
1262}
1263
1264impl SentryTrace {
1265 pub fn new(
1267 trace_id: protocol::TraceId,
1268 span_id: protocol::SpanId,
1269 sampled: Option<bool>,
1270 ) -> Self {
1271 SentryTrace {
1272 trace_id,
1273 span_id,
1274 sampled,
1275 }
1276 }
1277}
1278
1279fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1280 let header = header.trim();
1281 let mut parts = header.splitn(3, '-');
1282
1283 let trace_id = parts.next()?.parse().ok()?;
1284 let parent_span_id = parts.next()?.parse().ok()?;
1285 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1286 "1" => Some(true),
1287 "0" => Some(false),
1288 _ => None,
1289 });
1290
1291 Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
1292}
1293
1294pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1297 headers: I,
1298) -> Option<SentryTrace> {
1299 let mut trace = None;
1300 for (k, v) in headers.into_iter() {
1301 if k.eq_ignore_ascii_case("sentry-trace") {
1302 trace = parse_sentry_trace(v);
1303 break;
1304 }
1305 }
1306 trace
1307}
1308
1309impl std::fmt::Display for SentryTrace {
1310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1311 write!(f, "{}-{}", self.trace_id, self.span_id)?;
1312 if let Some(sampled) = self.sampled {
1313 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1314 }
1315 Ok(())
1316 }
1317}
1318
1319#[cfg(test)]
1320mod tests {
1321 use std::str::FromStr;
1322
1323 use super::*;
1324
1325 #[test]
1326 fn parses_sentry_trace() {
1327 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1328 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1329
1330 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1331 assert_eq!(
1332 trace,
1333 Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
1334 );
1335
1336 let trace = SentryTrace::new(Default::default(), Default::default(), None);
1337 let parsed = parse_sentry_trace(&trace.to_string());
1338 assert_eq!(parsed, Some(trace));
1339 }
1340
1341 #[test]
1342 fn disabled_forwards_trace_id() {
1343 let headers = [(
1344 "SenTrY-TRAce",
1345 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1346 )];
1347 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1348 let trx = start_transaction(ctx);
1349
1350 let span = trx.start_child("noop", "noop");
1351
1352 let header = span.iter_headers().next().unwrap().1;
1353 let parsed = parse_sentry_trace(&header).unwrap();
1354
1355 assert_eq!(
1356 &parsed.trace_id.to_string(),
1357 "09e04486820349518ac7b5d2adbf6ba5"
1358 );
1359 assert_eq!(parsed.sampled, Some(true));
1360 }
1361
1362 #[test]
1363 fn transaction_context_public_getters() {
1364 let mut ctx = TransactionContext::new("test-name", "test-operation");
1365 assert_eq!(ctx.name(), "test-name");
1366 assert_eq!(ctx.operation(), "test-operation");
1367 assert_eq!(ctx.sampled(), None);
1368
1369 ctx.set_sampled(true);
1370 assert_eq!(ctx.sampled(), Some(true));
1371 }
1372
1373 #[cfg(feature = "client")]
1374 #[test]
1375 fn compute_transaction_sample_rate() {
1376 let ctx = TransactionContext::new("noop", "noop");
1378 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1379 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1380
1381 let mut ctx = TransactionContext::new("noop", "noop");
1383 ctx.set_sampled(true);
1384 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1385 ctx.set_sampled(false);
1386 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1387
1388 let mut ctx = TransactionContext::new("noop", "noop");
1390 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1391 ctx.set_sampled(false);
1392 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1393 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1395 Some(true) => 0.8,
1396 Some(false) => 0.4,
1397 None => 0.6,
1398 };
1399 ctx.set_sampled(true);
1400 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1401 ctx.set_sampled(None);
1402 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1403
1404 let sampler = |ctx: &TransactionContext| {
1406 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1407 return 1.0;
1408 }
1409
1410 if let Some(custom) = ctx.custom() {
1411 if let Some(rate) = custom.get("rate") {
1412 if let Some(rate) = rate.as_f64() {
1413 return rate as f32;
1414 }
1415 }
1416 }
1417
1418 0.1
1419 };
1420 let ctx = TransactionContext::new("noop", "must-operation");
1422 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1423 let ctx = TransactionContext::new("must-name", "noop");
1424 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1425 let mut ctx = TransactionContext::new("noop", "noop");
1427 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1428 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1429 }
1430}