1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::ops::{Deref, DerefMut};
4use std::sync::{Arc, Mutex, MutexGuard};
5use std::time::SystemTime;
6
7use sentry_types::protocol::v7::SpanId;
8
9use crate::{protocol, Hub};
10
11#[cfg(feature = "client")]
12use crate::Client;
13
14#[cfg(feature = "client")]
15const MAX_SPANS: usize = 1_000;
16
17pub fn start_transaction(ctx: TransactionContext) -> Transaction {
26 #[cfg(feature = "client")]
27 {
28 let client = Hub::with_active(|hub| hub.client());
29 Transaction::new(client, ctx)
30 }
31 #[cfg(not(feature = "client"))]
32 {
33 Transaction::new_noop(ctx)
34 }
35}
36
37pub fn start_transaction_with_timestamp(
44 ctx: TransactionContext,
45 timestamp: SystemTime,
46) -> Transaction {
47 let transaction = start_transaction(ctx);
48 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
49 tx.start_timestamp = timestamp;
50 }
51 transaction
52}
53
54impl Hub {
57 pub fn start_transaction(&self, ctx: TransactionContext) -> Transaction {
61 #[cfg(feature = "client")]
62 {
63 Transaction::new(self.client(), ctx)
64 }
65 #[cfg(not(feature = "client"))]
66 {
67 Transaction::new_noop(ctx)
68 }
69 }
70
71 pub fn start_transaction_with_timestamp(
75 &self,
76 ctx: TransactionContext,
77 timestamp: SystemTime,
78 ) -> Transaction {
79 let transaction = start_transaction(ctx);
80 if let Some(tx) = transaction.inner.lock().unwrap().transaction.as_mut() {
81 tx.start_timestamp = timestamp;
82 }
83 transaction
84 }
85}
86
87pub type CustomTransactionContext = serde_json::Map<String, serde_json::Value>;
95
96#[derive(Debug, Clone)]
101pub struct TransactionContext {
102 #[cfg_attr(not(feature = "client"), allow(dead_code))]
103 name: String,
104 op: String,
105 trace_id: protocol::TraceId,
106 parent_span_id: Option<protocol::SpanId>,
107 span_id: protocol::SpanId,
108 sampled: Option<bool>,
109 custom: Option<CustomTransactionContext>,
110}
111
112impl TransactionContext {
113 #[must_use = "this must be used with `start_transaction`"]
125 pub fn new(name: &str, op: &str) -> Self {
126 Self::new_with_trace_id(name, op, protocol::TraceId::default())
127 }
128
129 #[must_use = "this must be used with `start_transaction`"]
136 pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self {
137 Self {
138 name: name.into(),
139 op: op.into(),
140 trace_id,
141 parent_span_id: None,
142 span_id: Default::default(),
143 sampled: None,
144 custom: None,
145 }
146 }
147
148 #[must_use = "this must be used with `start_transaction`"]
156 pub fn new_with_details(
157 name: &str,
158 op: &str,
159 trace_id: protocol::TraceId,
160 span_id: Option<protocol::SpanId>,
161 parent_span_id: Option<protocol::SpanId>,
162 ) -> Self {
163 let mut slf = Self::new_with_trace_id(name, op, trace_id);
164 if let Some(span_id) = span_id {
165 slf.span_id = span_id;
166 }
167 slf.parent_span_id = parent_span_id;
168 slf
169 }
170
171 #[must_use = "this must be used with `start_transaction`"]
176 pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
177 name: &str,
178 op: &str,
179 headers: I,
180 ) -> Self {
181 parse_headers(headers)
182 .map(|sentry_trace| Self::continue_from_sentry_trace(name, op, &sentry_trace, None))
183 .unwrap_or_else(|| Self {
184 name: name.into(),
185 op: op.into(),
186 trace_id: Default::default(),
187 parent_span_id: None,
188 span_id: Default::default(),
189 sampled: None,
190 custom: None,
191 })
192 }
193
194 pub fn continue_from_sentry_trace(
197 name: &str,
198 op: &str,
199 sentry_trace: &SentryTrace,
200 span_id: Option<SpanId>,
201 ) -> Self {
202 Self {
203 name: name.into(),
204 op: op.into(),
205 trace_id: sentry_trace.trace_id,
206 parent_span_id: Some(sentry_trace.span_id),
207 sampled: sentry_trace.sampled,
208 span_id: span_id.unwrap_or_default(),
209 custom: None,
210 }
211 }
212
213 pub fn continue_from_span(name: &str, op: &str, span: Option<TransactionOrSpan>) -> Self {
219 let span = match span {
220 Some(span) => span,
221 None => return Self::new(name, op),
222 };
223
224 let (trace_id, parent_span_id, sampled) = match span {
225 TransactionOrSpan::Transaction(transaction) => {
226 let inner = transaction.inner.lock().unwrap();
227 (
228 inner.context.trace_id,
229 inner.context.span_id,
230 Some(inner.sampled),
231 )
232 }
233 TransactionOrSpan::Span(span) => {
234 let sampled = span.sampled;
235 let span = span.span.lock().unwrap();
236 (span.trace_id, span.span_id, Some(sampled))
237 }
238 };
239
240 Self {
241 name: name.into(),
242 op: op.into(),
243 trace_id,
244 parent_span_id: Some(parent_span_id),
245 span_id: protocol::SpanId::default(),
246 sampled,
247 custom: None,
248 }
249 }
250
251 pub fn set_sampled(&mut self, sampled: impl Into<Option<bool>>) {
256 self.sampled = sampled.into();
257 }
258
259 pub fn sampled(&self) -> Option<bool> {
261 self.sampled
262 }
263
264 pub fn name(&self) -> &str {
266 &self.name
267 }
268
269 pub fn operation(&self) -> &str {
271 &self.op
272 }
273
274 pub fn trace_id(&self) -> protocol::TraceId {
276 self.trace_id
277 }
278
279 pub fn span_id(&self) -> protocol::SpanId {
281 self.span_id
282 }
283
284 pub fn custom(&self) -> Option<&CustomTransactionContext> {
286 self.custom.as_ref()
287 }
288
289 pub fn custom_mut(&mut self) -> &mut Option<CustomTransactionContext> {
293 &mut self.custom
294 }
295
296 pub fn custom_insert(
303 &mut self,
304 key: String,
305 value: serde_json::Value,
306 ) -> Option<serde_json::Value> {
307 let mut custom = None;
309 std::mem::swap(&mut self.custom, &mut custom);
310
311 let mut custom = custom.unwrap_or_default();
313
314 let existing_value = custom.insert(key, value);
316 self.custom = Some(custom);
317 existing_value
318 }
319
320 #[must_use]
327 pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
328 TransactionContextBuilder {
329 ctx: TransactionContext::new(name, op),
330 }
331 }
332}
333
334pub struct TransactionContextBuilder {
336 ctx: TransactionContext,
337}
338
339impl TransactionContextBuilder {
340 #[must_use]
342 pub fn with_name(mut self, name: String) -> Self {
343 self.ctx.name = name;
344 self
345 }
346
347 #[must_use]
349 pub fn with_op(mut self, op: String) -> Self {
350 self.ctx.op = op;
351 self
352 }
353
354 #[must_use]
356 pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
357 self.ctx.trace_id = trace_id;
358 self
359 }
360
361 #[must_use]
363 pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
364 self.ctx.parent_span_id = parent_span_id;
365 self
366 }
367
368 #[must_use]
370 pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
371 self.ctx.span_id = span_id;
372 self
373 }
374
375 #[must_use]
377 pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
378 self.ctx.sampled = sampled;
379 self
380 }
381
382 #[must_use]
384 pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
385 self.ctx.custom_insert(key, value);
386 self
387 }
388
389 pub fn finish(self) -> TransactionContext {
391 self.ctx
392 }
393}
394
395pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
401
402#[derive(Clone, Debug)]
406pub enum TransactionOrSpan {
407 Transaction(Transaction),
409 Span(Span),
411}
412
413impl From<Transaction> for TransactionOrSpan {
414 fn from(transaction: Transaction) -> Self {
415 Self::Transaction(transaction)
416 }
417}
418
419impl From<Span> for TransactionOrSpan {
420 fn from(span: Span) -> Self {
421 Self::Span(span)
422 }
423}
424
425impl TransactionOrSpan {
426 pub fn set_data(&self, key: &str, value: protocol::Value) {
428 match self {
429 TransactionOrSpan::Transaction(transaction) => transaction.set_data(key, value),
430 TransactionOrSpan::Span(span) => span.set_data(key, value),
431 }
432 }
433
434 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
436 match self {
437 TransactionOrSpan::Transaction(transaction) => transaction.set_tag(key, value),
438 TransactionOrSpan::Span(span) => span.set_tag(key, value),
439 }
440 }
441
442 pub fn get_trace_context(&self) -> protocol::TraceContext {
446 match self {
447 TransactionOrSpan::Transaction(transaction) => transaction.get_trace_context(),
448 TransactionOrSpan::Span(span) => span.get_trace_context(),
449 }
450 }
451
452 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
454 match self {
455 TransactionOrSpan::Transaction(transaction) => transaction.get_status(),
456 TransactionOrSpan::Span(span) => span.get_status(),
457 }
458 }
459
460 pub fn set_status(&self, status: protocol::SpanStatus) {
462 match self {
463 TransactionOrSpan::Transaction(transaction) => transaction.set_status(status),
464 TransactionOrSpan::Span(span) => span.set_status(status),
465 }
466 }
467
468 pub fn set_request(&self, request: protocol::Request) {
470 match self {
471 TransactionOrSpan::Transaction(transaction) => transaction.set_request(request),
472 TransactionOrSpan::Span(span) => span.set_request(request),
473 }
474 }
475
476 pub fn iter_headers(&self) -> TraceHeadersIter {
480 match self {
481 TransactionOrSpan::Transaction(transaction) => transaction.iter_headers(),
482 TransactionOrSpan::Span(span) => span.iter_headers(),
483 }
484 }
485
486 pub fn is_sampled(&self) -> bool {
488 match self {
489 TransactionOrSpan::Transaction(transaction) => transaction.is_sampled(),
490 TransactionOrSpan::Span(span) => span.is_sampled(),
491 }
492 }
493
494 #[must_use = "a span must be explicitly closed via `finish()`"]
499 pub fn start_child(&self, op: &str, description: &str) -> Span {
500 match self {
501 TransactionOrSpan::Transaction(transaction) => transaction.start_child(op, description),
502 TransactionOrSpan::Span(span) => span.start_child(op, description),
503 }
504 }
505
506 #[must_use = "a span must be explicitly closed via `finish()`"]
511 pub fn start_child_with_details(
512 &self,
513 op: &str,
514 description: &str,
515 id: SpanId,
516 timestamp: SystemTime,
517 ) -> Span {
518 match self {
519 TransactionOrSpan::Transaction(transaction) => {
520 transaction.start_child_with_details(op, description, id, timestamp)
521 }
522 TransactionOrSpan::Span(span) => {
523 span.start_child_with_details(op, description, id, timestamp)
524 }
525 }
526 }
527
528 #[cfg(feature = "client")]
529 pub(crate) fn apply_to_event(&self, event: &mut protocol::Event<'_>) {
530 if event.contexts.contains_key("trace") {
531 return;
532 }
533
534 let context = match self {
535 TransactionOrSpan::Transaction(transaction) => {
536 transaction.inner.lock().unwrap().context.clone()
537 }
538 TransactionOrSpan::Span(span) => {
539 let span = span.span.lock().unwrap();
540 protocol::TraceContext {
541 span_id: span.span_id,
542 trace_id: span.trace_id,
543 ..Default::default()
544 }
545 }
546 };
547 event.contexts.insert("trace".into(), context.into());
548 }
549
550 pub fn finish_with_timestamp(self, timestamp: SystemTime) {
555 match self {
556 TransactionOrSpan::Transaction(transaction) => {
557 transaction.finish_with_timestamp(timestamp)
558 }
559 TransactionOrSpan::Span(span) => span.finish_with_timestamp(timestamp),
560 }
561 }
562
563 pub fn finish(self) {
568 match self {
569 TransactionOrSpan::Transaction(transaction) => transaction.finish(),
570 TransactionOrSpan::Span(span) => span.finish(),
571 }
572 }
573}
574
575#[derive(Debug)]
576pub(crate) struct TransactionInner {
577 #[cfg(feature = "client")]
578 client: Option<Arc<Client>>,
579 sampled: bool,
580 pub(crate) context: protocol::TraceContext,
581 pub(crate) transaction: Option<protocol::Transaction<'static>>,
582}
583
584type TransactionArc = Arc<Mutex<TransactionInner>>;
585
586#[cfg(feature = "client")]
590fn transaction_sample_rate(
591 traces_sampler: Option<&TracesSampler>,
592 ctx: &TransactionContext,
593 traces_sample_rate: f32,
594) -> f32 {
595 match (traces_sampler, traces_sample_rate) {
596 (Some(traces_sampler), _) => traces_sampler(ctx),
597 (None, traces_sample_rate) => ctx.sampled.map(f32::from).unwrap_or(traces_sample_rate),
598 }
599}
600
601#[cfg(feature = "client")]
603impl Client {
604 fn determine_sampling_decision(&self, ctx: &TransactionContext) -> (bool, f32) {
605 let client_options = self.options();
606 let sample_rate = transaction_sample_rate(
607 client_options.traces_sampler.as_deref(),
608 ctx,
609 client_options.traces_sample_rate,
610 );
611 let sampled = self.sample_should_send(sample_rate);
612 (sampled, sample_rate)
613 }
614}
615
616#[cfg(feature = "client")]
618#[derive(Clone, Debug)]
619struct TransactionMetadata {
620 sample_rate: f32,
622}
623
624#[derive(Clone, Debug)]
630pub struct Transaction {
631 pub(crate) inner: TransactionArc,
632 #[cfg(feature = "client")]
633 metadata: TransactionMetadata,
634}
635
636pub struct TransactionData<'a>(MutexGuard<'a, TransactionInner>);
638
639impl<'a> TransactionData<'a> {
640 pub fn iter(&self) -> Box<dyn Iterator<Item = (&String, &protocol::Value)> + '_> {
647 if self.0.transaction.is_some() {
648 Box::new(self.0.context.data.iter())
649 } else {
650 Box::new(std::iter::empty())
651 }
652 }
653
654 pub fn set_data(&mut self, key: Cow<'a, str>, value: protocol::Value) {
656 if self.0.transaction.is_some() {
657 self.0.context.data.insert(key.into(), value);
658 }
659 }
660
661 pub fn set_tag(&mut self, key: Cow<'_, str>, value: String) {
663 if let Some(transaction) = self.0.transaction.as_mut() {
664 transaction.tags.insert(key.into(), value);
665 }
666 }
667}
668
669impl Transaction {
670 #[cfg(feature = "client")]
671 fn new(client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
672 let ((sampled, sample_rate), transaction) = match client.as_ref() {
673 Some(client) => (
674 client.determine_sampling_decision(&ctx),
675 Some(protocol::Transaction {
676 name: Some(ctx.name),
677 ..Default::default()
678 }),
679 ),
680 None => (
681 (
682 ctx.sampled.unwrap_or(false),
683 ctx.sampled.map_or(0.0, f32::from),
684 ),
685 None,
686 ),
687 };
688
689 let context = protocol::TraceContext {
690 trace_id: ctx.trace_id,
691 parent_span_id: ctx.parent_span_id,
692 span_id: ctx.span_id,
693 op: Some(ctx.op),
694 ..Default::default()
695 };
696
697 Self {
698 inner: Arc::new(Mutex::new(TransactionInner {
699 client,
700 sampled,
701 context,
702 transaction,
703 })),
704 metadata: TransactionMetadata { sample_rate },
705 }
706 }
707
708 #[cfg(not(feature = "client"))]
709 fn new_noop(ctx: TransactionContext) -> Self {
710 let context = protocol::TraceContext {
711 trace_id: ctx.trace_id,
712 parent_span_id: ctx.parent_span_id,
713 op: Some(ctx.op),
714 ..Default::default()
715 };
716 let sampled = ctx.sampled.unwrap_or(false);
717
718 Self {
719 inner: Arc::new(Mutex::new(TransactionInner {
720 sampled,
721 context,
722 transaction: None,
723 })),
724 }
725 }
726
727 pub fn set_data(&self, key: &str, value: protocol::Value) {
729 let mut inner = self.inner.lock().unwrap();
730 if inner.transaction.is_some() {
731 inner.context.data.insert(key.into(), value);
732 }
733 }
734
735 pub fn set_extra(&self, key: &str, value: protocol::Value) {
737 let mut inner = self.inner.lock().unwrap();
738 if let Some(transaction) = inner.transaction.as_mut() {
739 transaction.extra.insert(key.into(), value);
740 }
741 }
742
743 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
745 let mut inner = self.inner.lock().unwrap();
746 if let Some(transaction) = inner.transaction.as_mut() {
747 transaction.tags.insert(key.into(), value.to_string());
748 }
749 }
750
751 pub fn data(&self) -> TransactionData {
761 TransactionData(self.inner.lock().unwrap())
762 }
763
764 pub fn get_trace_context(&self) -> protocol::TraceContext {
768 let inner = self.inner.lock().unwrap();
769 inner.context.clone()
770 }
771
772 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
774 let inner = self.inner.lock().unwrap();
775 inner.context.status
776 }
777
778 pub fn set_status(&self, status: protocol::SpanStatus) {
780 let mut inner = self.inner.lock().unwrap();
781 inner.context.status = Some(status);
782 }
783
784 pub fn set_request(&self, request: protocol::Request) {
786 let mut inner = self.inner.lock().unwrap();
787 if let Some(transaction) = inner.transaction.as_mut() {
788 transaction.request = Some(request);
789 }
790 }
791
792 pub fn iter_headers(&self) -> TraceHeadersIter {
796 let inner = self.inner.lock().unwrap();
797 let trace = SentryTrace::new(
798 inner.context.trace_id,
799 inner.context.span_id,
800 Some(inner.sampled),
801 );
802 TraceHeadersIter {
803 sentry_trace: Some(trace.to_string()),
804 }
805 }
806
807 pub fn is_sampled(&self) -> bool {
809 self.inner.lock().unwrap().sampled
810 }
811
812 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
817 with_client_impl! {{
818 let mut inner = self.inner.lock().unwrap();
819
820 if !inner.sampled {
822 return;
823 }
824
825 if let Some(mut transaction) = inner.transaction.take() {
826 if let Some(client) = inner.client.take() {
827 transaction.finish_with_timestamp(_timestamp);
828 transaction
829 .contexts
830 .insert("trace".into(), inner.context.clone().into());
831
832 Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction));
833 let opts = client.options();
834 transaction.release.clone_from(&opts.release);
835 transaction.environment.clone_from(&opts.environment);
836 transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone()));
837 transaction.server_name.clone_from(&opts.server_name);
838
839 let mut dsc = protocol::DynamicSamplingContext::new()
840 .with_trace_id(inner.context.trace_id)
841 .with_sample_rate(self.metadata.sample_rate)
842 .with_sampled(inner.sampled);
843 if let Some(public_key) = client.dsn().map(|dsn| dsn.public_key()) {
844 dsc = dsc.with_public_key(public_key.to_owned());
845 }
846
847 drop(inner);
848
849 let mut envelope = protocol::Envelope::new().with_headers(
850 protocol::EnvelopeHeaders::new().with_trace(dsc)
851 );
852 envelope.add_item(transaction);
853
854 client.send_envelope(envelope)
855 }
856 }
857 }}
858 }
859
860 pub fn finish(self) {
865 self.finish_with_timestamp(SystemTime::now());
866 }
867
868 #[must_use = "a span must be explicitly closed via `finish()`"]
872 pub fn start_child(&self, op: &str, description: &str) -> Span {
873 let inner = self.inner.lock().unwrap();
874 let span = protocol::Span {
875 trace_id: inner.context.trace_id,
876 parent_span_id: Some(inner.context.span_id),
877 op: Some(op.into()),
878 description: if description.is_empty() {
879 None
880 } else {
881 Some(description.into())
882 },
883 ..Default::default()
884 };
885 Span {
886 transaction: Arc::clone(&self.inner),
887 sampled: inner.sampled,
888 span: Arc::new(Mutex::new(span)),
889 }
890 }
891
892 #[must_use = "a span must be explicitly closed via `finish()`"]
896 pub fn start_child_with_details(
897 &self,
898 op: &str,
899 description: &str,
900 id: SpanId,
901 timestamp: SystemTime,
902 ) -> Span {
903 let inner = self.inner.lock().unwrap();
904 let span = protocol::Span {
905 trace_id: inner.context.trace_id,
906 parent_span_id: Some(inner.context.span_id),
907 op: Some(op.into()),
908 description: if description.is_empty() {
909 None
910 } else {
911 Some(description.into())
912 },
913 span_id: id,
914 start_timestamp: timestamp,
915 ..Default::default()
916 };
917 Span {
918 transaction: Arc::clone(&self.inner),
919 sampled: inner.sampled,
920 span: Arc::new(Mutex::new(span)),
921 }
922 }
923}
924
925pub struct Data<'a>(MutexGuard<'a, protocol::Span>);
927
928impl Data<'_> {
929 pub fn set_data(&mut self, key: String, value: protocol::Value) {
931 self.0.data.insert(key, value);
932 }
933
934 pub fn set_tag(&mut self, key: String, value: String) {
936 self.0.tags.insert(key, value);
937 }
938}
939
940impl Deref for Data<'_> {
941 type Target = BTreeMap<String, protocol::Value>;
942
943 fn deref(&self) -> &Self::Target {
944 &self.0.data
945 }
946}
947
948impl DerefMut for Data<'_> {
949 fn deref_mut(&mut self) -> &mut Self::Target {
950 &mut self.0.data
951 }
952}
953
954#[derive(Clone, Debug)]
959pub struct Span {
960 pub(crate) transaction: TransactionArc,
961 sampled: bool,
962 span: SpanArc,
963}
964
965type SpanArc = Arc<Mutex<protocol::Span>>;
966
967impl Span {
968 pub fn set_data(&self, key: &str, value: protocol::Value) {
970 let mut span = self.span.lock().unwrap();
971 span.data.insert(key.into(), value);
972 }
973
974 pub fn set_tag<V: ToString>(&self, key: &str, value: V) {
976 let mut span = self.span.lock().unwrap();
977 span.tags.insert(key.into(), value.to_string());
978 }
979
980 pub fn data(&self) -> Data {
992 Data(self.span.lock().unwrap())
993 }
994
995 pub fn get_trace_context(&self) -> protocol::TraceContext {
999 let transaction = self.transaction.lock().unwrap();
1000 transaction.context.clone()
1001 }
1002
1003 pub fn get_span_id(&self) -> protocol::SpanId {
1005 let span = self.span.lock().unwrap();
1006 span.span_id
1007 }
1008
1009 pub fn get_status(&self) -> Option<protocol::SpanStatus> {
1011 let span = self.span.lock().unwrap();
1012 span.status
1013 }
1014
1015 pub fn set_status(&self, status: protocol::SpanStatus) {
1017 let mut span = self.span.lock().unwrap();
1018 span.status = Some(status);
1019 }
1020
1021 pub fn set_request(&self, request: protocol::Request) {
1023 let mut span = self.span.lock().unwrap();
1024 if let Some(method) = request.method {
1026 span.data.insert("method".into(), method.into());
1027 }
1028 if let Some(url) = request.url {
1029 span.data.insert("url".into(), url.to_string().into());
1030 }
1031 if let Some(data) = request.data {
1032 if let Ok(data) = serde_json::from_str::<serde_json::Value>(&data) {
1033 span.data.insert("data".into(), data);
1034 } else {
1035 span.data.insert("data".into(), data.into());
1036 }
1037 }
1038 if let Some(query_string) = request.query_string {
1039 span.data.insert("query_string".into(), query_string.into());
1040 }
1041 if let Some(cookies) = request.cookies {
1042 span.data.insert("cookies".into(), cookies.into());
1043 }
1044 if !request.headers.is_empty() {
1045 if let Ok(headers) = serde_json::to_value(request.headers) {
1046 span.data.insert("headers".into(), headers);
1047 }
1048 }
1049 if !request.env.is_empty() {
1050 if let Ok(env) = serde_json::to_value(request.env) {
1051 span.data.insert("env".into(), env);
1052 }
1053 }
1054 }
1055
1056 pub fn iter_headers(&self) -> TraceHeadersIter {
1060 let span = self.span.lock().unwrap();
1061 let trace = SentryTrace::new(span.trace_id, span.span_id, Some(self.sampled));
1062 TraceHeadersIter {
1063 sentry_trace: Some(trace.to_string()),
1064 }
1065 }
1066
1067 pub fn is_sampled(&self) -> bool {
1069 self.sampled
1070 }
1071
1072 pub fn finish_with_timestamp(self, _timestamp: SystemTime) {
1077 with_client_impl! {{
1078 let mut span = self.span.lock().unwrap();
1079 if span.timestamp.is_some() {
1080 return;
1082 }
1083 span.finish_with_timestamp(_timestamp);
1084 let mut inner = self.transaction.lock().unwrap();
1085 if let Some(transaction) = inner.transaction.as_mut() {
1086 if transaction.spans.len() <= MAX_SPANS {
1087 transaction.spans.push(span.clone());
1088 }
1089 }
1090 }}
1091 }
1092
1093 pub fn finish(self) {
1098 self.finish_with_timestamp(SystemTime::now());
1099 }
1100
1101 #[must_use = "a span must be explicitly closed via `finish()`"]
1105 pub fn start_child(&self, op: &str, description: &str) -> Span {
1106 let span = self.span.lock().unwrap();
1107 let span = protocol::Span {
1108 trace_id: span.trace_id,
1109 parent_span_id: Some(span.span_id),
1110 op: Some(op.into()),
1111 description: if description.is_empty() {
1112 None
1113 } else {
1114 Some(description.into())
1115 },
1116 ..Default::default()
1117 };
1118 Span {
1119 transaction: self.transaction.clone(),
1120 sampled: self.sampled,
1121 span: Arc::new(Mutex::new(span)),
1122 }
1123 }
1124
1125 #[must_use = "a span must be explicitly closed via `finish()`"]
1129 fn start_child_with_details(
1130 &self,
1131 op: &str,
1132 description: &str,
1133 id: SpanId,
1134 timestamp: SystemTime,
1135 ) -> Span {
1136 let span = self.span.lock().unwrap();
1137 let span = protocol::Span {
1138 trace_id: span.trace_id,
1139 parent_span_id: Some(span.span_id),
1140 op: Some(op.into()),
1141 description: if description.is_empty() {
1142 None
1143 } else {
1144 Some(description.into())
1145 },
1146 span_id: id,
1147 start_timestamp: timestamp,
1148 ..Default::default()
1149 };
1150 Span {
1151 transaction: self.transaction.clone(),
1152 sampled: self.sampled,
1153 span: Arc::new(Mutex::new(span)),
1154 }
1155 }
1156}
1157
1158pub type TraceHeader = (&'static str, String);
1160
1161pub struct TraceHeadersIter {
1166 sentry_trace: Option<String>,
1167}
1168
1169impl TraceHeadersIter {
1170 #[cfg(feature = "client")]
1171 pub(crate) fn new(sentry_trace: String) -> Self {
1172 Self {
1173 sentry_trace: Some(sentry_trace),
1174 }
1175 }
1176}
1177
1178impl Iterator for TraceHeadersIter {
1179 type Item = (&'static str, String);
1180
1181 fn next(&mut self) -> Option<Self::Item> {
1182 self.sentry_trace.take().map(|st| ("sentry-trace", st))
1183 }
1184}
1185
1186#[derive(Debug, PartialEq, Clone, Copy, Default)]
1189pub struct SentryTrace {
1190 pub(crate) trace_id: protocol::TraceId,
1191 pub(crate) span_id: protocol::SpanId,
1192 pub(crate) sampled: Option<bool>,
1193}
1194
1195impl SentryTrace {
1196 pub fn new(
1198 trace_id: protocol::TraceId,
1199 span_id: protocol::SpanId,
1200 sampled: Option<bool>,
1201 ) -> Self {
1202 SentryTrace {
1203 trace_id,
1204 span_id,
1205 sampled,
1206 }
1207 }
1208}
1209
1210fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
1211 let header = header.trim();
1212 let mut parts = header.splitn(3, '-');
1213
1214 let trace_id = parts.next()?.parse().ok()?;
1215 let parent_span_id = parts.next()?.parse().ok()?;
1216 let parent_sampled = parts.next().and_then(|sampled| match sampled {
1217 "1" => Some(true),
1218 "0" => Some(false),
1219 _ => None,
1220 });
1221
1222 Some(SentryTrace::new(trace_id, parent_span_id, parent_sampled))
1223}
1224
1225pub fn parse_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
1228 headers: I,
1229) -> Option<SentryTrace> {
1230 let mut trace = None;
1231 for (k, v) in headers.into_iter() {
1232 if k.eq_ignore_ascii_case("sentry-trace") {
1233 trace = parse_sentry_trace(v);
1234 break;
1235 }
1236 }
1237 trace
1238}
1239
1240impl std::fmt::Display for SentryTrace {
1241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1242 write!(f, "{}-{}", self.trace_id, self.span_id)?;
1243 if let Some(sampled) = self.sampled {
1244 write!(f, "-{}", if sampled { '1' } else { '0' })?;
1245 }
1246 Ok(())
1247 }
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use std::str::FromStr;
1253
1254 use super::*;
1255
1256 #[test]
1257 fn parses_sentry_trace() {
1258 let trace_id = protocol::TraceId::from_str("09e04486820349518ac7b5d2adbf6ba5").unwrap();
1259 let parent_trace_id = protocol::SpanId::from_str("9cf635fa5b870b3a").unwrap();
1260
1261 let trace = parse_sentry_trace("09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-0");
1262 assert_eq!(
1263 trace,
1264 Some(SentryTrace::new(trace_id, parent_trace_id, Some(false)))
1265 );
1266
1267 let trace = SentryTrace::new(Default::default(), Default::default(), None);
1268 let parsed = parse_sentry_trace(&trace.to_string());
1269 assert_eq!(parsed, Some(trace));
1270 }
1271
1272 #[test]
1273 fn disabled_forwards_trace_id() {
1274 let headers = [(
1275 "SenTrY-TRAce",
1276 "09e04486820349518ac7b5d2adbf6ba5-9cf635fa5b870b3a-1",
1277 )];
1278 let ctx = TransactionContext::continue_from_headers("noop", "noop", headers);
1279 let trx = start_transaction(ctx);
1280
1281 let span = trx.start_child("noop", "noop");
1282
1283 let header = span.iter_headers().next().unwrap().1;
1284 let parsed = parse_sentry_trace(&header).unwrap();
1285
1286 assert_eq!(
1287 &parsed.trace_id.to_string(),
1288 "09e04486820349518ac7b5d2adbf6ba5"
1289 );
1290 assert_eq!(parsed.sampled, Some(true));
1291 }
1292
1293 #[test]
1294 fn transaction_context_public_getters() {
1295 let mut ctx = TransactionContext::new("test-name", "test-operation");
1296 assert_eq!(ctx.name(), "test-name");
1297 assert_eq!(ctx.operation(), "test-operation");
1298 assert_eq!(ctx.sampled(), None);
1299
1300 ctx.set_sampled(true);
1301 assert_eq!(ctx.sampled(), Some(true));
1302 }
1303
1304 #[cfg(feature = "client")]
1305 #[test]
1306 fn compute_transaction_sample_rate() {
1307 let ctx = TransactionContext::new("noop", "noop");
1309 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
1310 assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
1311
1312 let mut ctx = TransactionContext::new("noop", "noop");
1314 ctx.set_sampled(true);
1315 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
1316 ctx.set_sampled(false);
1317 assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
1318
1319 let mut ctx = TransactionContext::new("noop", "noop");
1321 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1322 ctx.set_sampled(false);
1323 assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
1324 let sampler = |ctx: &TransactionContext| match ctx.sampled() {
1326 Some(true) => 0.8,
1327 Some(false) => 0.4,
1328 None => 0.6,
1329 };
1330 ctx.set_sampled(true);
1331 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
1332 ctx.set_sampled(None);
1333 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
1334
1335 let sampler = |ctx: &TransactionContext| {
1337 if ctx.name() == "must-name" || ctx.operation() == "must-operation" {
1338 return 1.0;
1339 }
1340
1341 if let Some(custom) = ctx.custom() {
1342 if let Some(rate) = custom.get("rate") {
1343 if let Some(rate) = rate.as_f64() {
1344 return rate as f32;
1345 }
1346 }
1347 }
1348
1349 0.1
1350 };
1351 let ctx = TransactionContext::new("noop", "must-operation");
1353 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1354 let ctx = TransactionContext::new("must-name", "noop");
1355 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 1.0);
1356 let mut ctx = TransactionContext::new("noop", "noop");
1358 ctx.custom_insert("rate".to_owned(), serde_json::json!(0.7));
1359 assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.7);
1360 }
1361}