cf_rustracing_jaeger/
span.rs

1//! Span.
2//!
3//! # How to inject/extract a span
4//!
5//! You can inject/extract the context of a span by using `SpanContext::inject_to_xxx` and
6//! `SpanContext::extract_from_xxx` methods respectively.
7//!
8//! The simplest way is to use `HashMap` as the carrier as follows:
9//!
10//! ```
11//! use std::collections::HashMap;
12//! use cf_rustracing_jaeger::span::SpanContext;
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! // Extraction
16//! let mut carrier = HashMap::new();
17//! carrier.insert(
18//!     "uber-trace-id".to_string(),  // NOTE: The key must be lower-case
19//!     "6309ab92c95468edea0dc1a9772ae2dc:409423a204bc17a8:0:1".to_string(),
20//! );
21//! let context = SpanContext::extract_from_text_map(&carrier)?.unwrap();
22//! let trace_id = context.state().trace_id();
23//! assert_eq!(trace_id.to_string(), "6309ab92c95468edea0dc1a9772ae2dc");
24//!
25//! // Injection
26//! let mut injected_carrier = HashMap::new();
27//! context.inject_to_text_map(&mut injected_carrier)?;
28//! assert_eq!(injected_carrier, carrier);
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! # References
34//!
35//! - [constants.go](https://github.com/uber/jaeger-client-go/tree/v2.9.0/constants.go)
36//! - [context.go](https://github.com/uber/jaeger-client-go/tree/v2.9.0/context.go)
37//! - [propagation.go](https://github.com/uber/jaeger-client-go/tree/v2.9.0/propagation.go)
38use crate::constants;
39use crate::error;
40use crate::{Error, ErrorKind, Result};
41use cf_rustracing::carrier::{
42    ExtractFromBinary, ExtractFromHttpHeader, ExtractFromTextMap, InjectToBinary,
43    InjectToHttpHeader, InjectToTextMap, IterHttpHeaderFields, SetHttpHeaderField, TextMap,
44};
45use cf_rustracing::sampler::BoxSampler;
46use percent_encoding::percent_decode;
47use std::fmt;
48use std::io::{Read, Write};
49use std::str::{self, FromStr};
50
51/// Span.
52pub type Span = cf_rustracing::span::Span<SpanContextState>;
53
54/// Span handle.
55pub type SpanHandle = cf_rustracing::span::SpanHandle<SpanContextState>;
56
57/// Finished span.
58pub type FinishedSpan = cf_rustracing::span::FinishedSpan<SpanContextState>;
59
60/// Span receiver.
61pub type SpanReceiver = cf_rustracing::span::SpanReceiver<SpanContextState>;
62
63/// Sender of finished spans to the destination channel.
64pub type SpanSender = cf_rustracing::span::SpanSender<SpanContextState>;
65
66/// Options for starting a span.
67pub type StartSpanOptions<'a> =
68    cf_rustracing::span::StartSpanOptions<'a, BoxSampler<SpanContextState>, SpanContextState>;
69
70/// Candidate span for tracing.
71pub type CandidateSpan<'a> = cf_rustracing::span::CandidateSpan<'a, SpanContextState>;
72
73/// Span context.
74pub type SpanContext = cf_rustracing::span::SpanContext<SpanContextState>;
75
76/// Span reference.
77pub type SpanReference = cf_rustracing::span::SpanReference<SpanContextState>;
78
79const FLAG_SAMPLED: u8 = 0b01;
80const FLAG_DEBUG: u8 = 0b10;
81
82/// Unique 128bit identifier of a trace.
83///
84/// ```
85/// use cf_rustracing_jaeger::span::TraceId;
86///
87/// let id = TraceId{ high: 0, low: 10 };
88/// assert_eq!(id.to_string(), "a");
89/// assert_eq!("a".parse::<TraceId>().unwrap(), id);
90///
91/// let id = TraceId{ high: 1, low: 2 };
92/// assert_eq!(id.to_string(), "10000000000000002");
93/// assert_eq!("10000000000000002".parse::<TraceId>().unwrap(), id);
94/// ```
95#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
96#[allow(missing_docs)]
97pub struct TraceId {
98    pub high: u64,
99    pub low: u64,
100}
101impl TraceId {
102    /// Makes a randomly generated `TraceId`.
103    pub fn new() -> Self {
104        TraceId::default()
105    }
106}
107impl Default for TraceId {
108    /// Makes a randomly generated `TraceId`.
109    fn default() -> Self {
110        TraceId {
111            high: rand::random(),
112            low: rand::random(),
113        }
114    }
115}
116impl fmt::Display for TraceId {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        if self.high == 0 {
119            write!(f, "{:x}", self.low)
120        } else {
121            write!(f, "{:x}{:016x}", self.high, self.low)
122        }
123    }
124}
125impl FromStr for TraceId {
126    type Err = Error;
127    fn from_str(s: &str) -> Result<Self> {
128        if s.len() <= 16 {
129            let low = u64::from_str_radix(s, 16).map_err(error::from_parse_int_error)?;
130            Ok(TraceId { high: 0, low })
131        } else if s.len() <= 32 {
132            let (high, low) = s.as_bytes().split_at(s.len() - 16);
133            let high = str::from_utf8(high).map_err(error::from_utf8_error)?;
134            let high = u64::from_str_radix(high, 16).map_err(error::from_parse_int_error)?;
135
136            let low = str::from_utf8(low).map_err(error::from_utf8_error)?;
137            let low = u64::from_str_radix(low, 16).map_err(error::from_parse_int_error)?;
138            Ok(TraceId { high, low })
139        } else {
140            track_panic!(ErrorKind::InvalidInput, "s={:?}", s)
141        }
142    }
143}
144
145/// `SpanContextState` builder.
146///
147/// Normally it is recommended to build `SpanContextState` using APIs provided by `Tracer` or `SpanContext`
148/// rather than via this.
149///
150/// But it may be useful, for example,
151/// if you want to handle custom carrier formats that are not defined in the OpenTracing [specification].
152///
153/// [specification]: https://github.com/opentracing/specification/blob/master/specification.md
154#[derive(Debug, Clone)]
155pub struct SpanContextStateBuilder {
156    trace_id: Option<TraceId>,
157    span_id: Option<u64>,
158    flags: u8,
159    debug_id: String,
160}
161impl SpanContextStateBuilder {
162    /// Makes a new `SpanContextStateBuilder` instance.
163    pub fn new() -> Self {
164        SpanContextStateBuilder {
165            trace_id: None,
166            span_id: None,
167            flags: FLAG_SAMPLED,
168            debug_id: String::new(),
169        }
170    }
171
172    /// Sets the trace identifier.
173    ///
174    /// The default value is `TraceId::new()`.
175    pub fn trace_id(mut self, trace_id: TraceId) -> Self {
176        self.trace_id = Some(trace_id);
177        self
178    }
179
180    /// Sets the span identifier.
181    ///
182    /// The default value is `rand::random()`.
183    pub fn span_id(mut self, span_id: u64) -> Self {
184        self.span_id = Some(span_id);
185        self
186    }
187
188    /// Sets the debug identifier.
189    ///
190    /// It is not set by default.
191    pub fn debug_id(mut self, debug_id: String) -> Self {
192        if !debug_id.is_empty() {
193            self.flags |= FLAG_DEBUG;
194            self.debug_id = debug_id;
195        }
196        self
197    }
198
199    /// Builds a `SpanContextState` instance with the specified parameters.
200    pub fn finish(self) -> SpanContextState {
201        SpanContextState {
202            trace_id: self.trace_id.unwrap_or_default(),
203            span_id: self.span_id.unwrap_or_else(rand::random),
204            flags: self.flags,
205            debug_id: self.debug_id,
206        }
207    }
208}
209impl Default for SpanContextStateBuilder {
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215/// Jaeger specific span context state.
216#[derive(Debug, Clone)]
217pub struct SpanContextState {
218    trace_id: TraceId,
219    span_id: u64,
220    flags: u8,
221    debug_id: String,
222}
223impl SpanContextState {
224    /// Creates new [`SpanContextState`].
225    pub fn new(trace_id: TraceId, span_id: u64, flags: u8, debug_id: String) -> Self {
226        Self {
227            trace_id,
228            span_id,
229            flags,
230            debug_id,
231        }
232    }
233
234    /// Returns the trace identifier of this span.
235    pub fn trace_id(&self) -> TraceId {
236        self.trace_id
237    }
238
239    /// Returns the identifier of this span.
240    pub fn span_id(&self) -> u64 {
241        self.span_id
242    }
243
244    /// Returns `true` if this span has been sampled (i.e., being traced).
245    pub fn is_sampled(&self) -> bool {
246        (self.flags & FLAG_SAMPLED) != 0
247    }
248
249    /// Returns the debug identifier of this span if exists.
250    pub fn debug_id(&self) -> Option<&str> {
251        if self.debug_id.is_empty() {
252            None
253        } else {
254            Some(&self.debug_id)
255        }
256    }
257
258    fn set_debug_id(&mut self, debug_id: String) {
259        if !debug_id.is_empty() {
260            self.flags |= FLAG_DEBUG;
261            self.debug_id = debug_id;
262        }
263    }
264
265    /// Returns the flags for this span.
266    pub fn flags(&self) -> u8 {
267        self.flags
268    }
269
270    fn root() -> Self {
271        Self::with_trace_id(TraceId::default())
272    }
273
274    fn with_trace_id(trace_id: TraceId) -> Self {
275        SpanContextState {
276            trace_id,
277            span_id: rand::random(),
278            flags: FLAG_SAMPLED,
279            debug_id: String::new(),
280        }
281    }
282}
283impl fmt::Display for SpanContextState {
284    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285        let dummy_parent_id = 0;
286        write!(
287            f,
288            "{}:{:x}:{:x}:{:x}",
289            self.trace_id, self.span_id, dummy_parent_id, self.flags
290        )
291    }
292}
293impl FromStr for SpanContextState {
294    type Err = Error;
295    fn from_str(s: &str) -> Result<Self> {
296        let mut tokens = s.splitn(4, ':');
297
298        macro_rules! token {
299            () => {
300                track_assert_some!(tokens.next(), ErrorKind::InvalidInput)
301            };
302        }
303        let trace_id = token!().parse()?;
304        let span_id = u64::from_str_radix(token!(), 16).map_err(error::from_parse_int_error)?;
305        let _parent_span_id =
306            u64::from_str_radix(token!(), 16).map_err(error::from_parse_int_error)?;
307        let flags = u8::from_str_radix(token!(), 16).map_err(error::from_parse_int_error)?;
308
309        Ok(SpanContextState {
310            trace_id,
311            span_id,
312            flags,
313            debug_id: String::new(),
314        })
315    }
316}
317impl<'a> From<CandidateSpan<'a>> for SpanContextState {
318    fn from(f: CandidateSpan<'a>) -> Self {
319        if let Some(primary) = f.references().first() {
320            Self::with_trace_id(primary.span().trace_id)
321        } else {
322            Self::root()
323        }
324    }
325}
326impl<T: TextMap> InjectToTextMap<T> for SpanContextState {
327    fn inject_to_text_map(context: &SpanContext, carrier: &mut T) -> Result<()> {
328        // TODO: Support baggage items
329        carrier.set(
330            constants::TRACER_CONTEXT_HEADER_NAME,
331            &context.state().to_string(),
332        );
333        Ok(())
334    }
335}
336impl<T: TextMap> ExtractFromTextMap<T> for SpanContextState {
337    fn extract_from_text_map(carrier: &T) -> Result<Option<SpanContext>> {
338        use std::collections::HashMap;
339
340        // FIXME: optimize
341        let mut map = HashMap::new();
342        if let Some(v) = carrier.get(constants::TRACER_CONTEXT_HEADER_NAME) {
343            map.insert(constants::TRACER_CONTEXT_HEADER_NAME, v);
344        }
345        if let Some(v) = carrier.get(constants::JAEGER_DEBUG_HEADER) {
346            map.insert(constants::JAEGER_DEBUG_HEADER, v);
347        }
348        Self::extract_from_http_header(&map)
349    }
350}
351impl<T> InjectToHttpHeader<T> for SpanContextState
352where
353    T: SetHttpHeaderField,
354{
355    fn inject_to_http_header(context: &SpanContext, carrier: &mut T) -> Result<()> {
356        // TODO: Support baggage items
357        carrier.set_http_header_field(
358            constants::TRACER_CONTEXT_HEADER_NAME,
359            &context.state().to_string(),
360        )?;
361        Ok(())
362    }
363}
364impl<'a, T> ExtractFromHttpHeader<'a, T> for SpanContextState
365where
366    T: IterHttpHeaderFields<'a>,
367{
368    fn extract_from_http_header(carrier: &'a T) -> Result<Option<SpanContext>> {
369        let mut state: Option<SpanContextState> = None;
370        let mut debug_id = None;
371        let baggage_items = Vec::new(); // TODO: Support baggage items
372        for (name, value) in carrier.fields() {
373            if name.eq_ignore_ascii_case(constants::TRACER_CONTEXT_HEADER_NAME) {
374                let value = percent_decode(value);
375                let value = value.decode_utf8().map_err(error::from_utf8_error)?;
376                state = Some(value.parse()?);
377            } else if name.eq_ignore_ascii_case(constants::JAEGER_DEBUG_HEADER) {
378                let value = str::from_utf8(value).map_err(error::from_utf8_error)?;
379                debug_id = Some(value.to_owned());
380            }
381        }
382        if let Some(mut state) = state {
383            if let Some(debug_id) = debug_id.take() {
384                state.set_debug_id(debug_id);
385            }
386            Ok(Some(SpanContext::new(state, baggage_items)))
387        } else if let Some(debug_id) = debug_id.take() {
388            let state = SpanContextState {
389                trace_id: TraceId { high: 0, low: 0 },
390                span_id: 0,
391                flags: FLAG_DEBUG,
392                debug_id,
393            };
394            Ok(Some(SpanContext::new(state, Vec::new())))
395        } else {
396            Ok(None)
397        }
398    }
399}
400impl<T> InjectToBinary<T> for SpanContextState
401where
402    T: Write,
403{
404    fn inject_to_binary(context: &SpanContext, carrier: &mut T) -> Result<()> {
405        let mut u64buf: [u8; 8] = context.state().trace_id.high.to_be_bytes();
406        let u32buf: [u8; 4] = [0; 4]; // TODO: Support baggage items
407        let u8buf: [u8; 1] = [context.state().flags];
408
409        carrier.write(&u64buf).map_err(error::from_io_error)?;
410        u64buf = context.state().trace_id.low.to_be_bytes();
411        carrier.write(&u64buf).map_err(error::from_io_error)?;
412        u64buf = context.state().span_id.to_be_bytes();
413        carrier.write(&u64buf).map_err(error::from_io_error)?;
414        // parent_span_id attribute is obsolete, write zeros.
415        u64buf = [0; 8];
416        carrier.write(&u64buf).map_err(error::from_io_error)?;
417        carrier.write(&u8buf).map_err(error::from_io_error)?;
418        carrier.write(&u32buf).map_err(error::from_io_error)?;
419
420        Ok(())
421    }
422}
423impl<T> ExtractFromBinary<T> for SpanContextState
424where
425    T: Read,
426{
427    fn extract_from_binary(carrier: &mut T) -> Result<Option<SpanContext>> {
428        let baggage_items = Vec::new(); // TODO: Support baggage items
429
430        let mut u64buf: [u8; 8] = [0; 8];
431        let mut u8buf: [u8; 1] = [0; 1];
432
433        carrier
434            .read(&mut u64buf[..])
435            .map_err(error::from_io_error)?;
436        let trace_id_high = u64::from_be_bytes(u64buf);
437        carrier
438            .read(&mut u64buf[..])
439            .map_err(error::from_io_error)?;
440        let trace_id_low = u64::from_be_bytes(u64buf);
441        carrier
442            .read(&mut u64buf[..])
443            .map_err(error::from_io_error)?;
444        let span_id = u64::from_be_bytes(u64buf);
445        carrier
446            .read(&mut u64buf[..])
447            .map_err(error::from_io_error)?;
448        // parent_span_id attribute is obsolete. Ignore storing it.
449        carrier.read(&mut u8buf[..]).map_err(error::from_io_error)?;
450        let flags = u8buf[0];
451
452        let state = SpanContextState {
453            trace_id: TraceId {
454                high: trace_id_high,
455                low: trace_id_low,
456            },
457            span_id,
458            flags,
459            debug_id: String::new(),
460        };
461        Ok(Some(SpanContext::new(state, baggage_items)))
462    }
463}
464
465#[cfg(test)]
466mod test {
467    use super::*;
468    use crate::Tracer;
469    use cf_rustracing::sampler::AllSampler;
470    use std::collections::HashMap;
471    use std::io::Cursor;
472    use trackable::error::Failed;
473    use trackable::result::TestResult;
474
475    #[test]
476    fn trace_id_conversion_works() {
477        let id = TraceId { high: 0, low: 10 };
478        assert_eq!(id.to_string(), "a");
479        assert_eq!("a".parse::<TraceId>().unwrap(), id);
480
481        let id = TraceId { high: 1, low: 2 };
482        assert_eq!(id.to_string(), "10000000000000002");
483        assert_eq!("10000000000000002".parse::<TraceId>().unwrap(), id);
484    }
485
486    #[test]
487    fn sampled_flag_works() {
488        let state: SpanContextState = "6309ab92c95468edea0dc1a9772ae2dc:409423a204bc17a8:0:1"
489            .parse()
490            .unwrap();
491
492        assert!(state.is_sampled());
493        assert_eq!(state.flags(), 1);
494
495        let state: SpanContextState = "6309ab92c95468edea0dc1a9772ae2dc:409423a204bc17a8:0:0"
496            .parse()
497            .unwrap();
498
499        assert!(!state.is_sampled());
500        assert_eq!(state.flags(), 0);
501    }
502
503    #[tokio::test]
504    async fn inject_to_text_map_works() -> TestResult {
505        let (tracer, _span_rx) = Tracer::new(AllSampler);
506        let span = tracer.span("test").start();
507        let context = track_assert_some!(span.context(), Failed);
508
509        let mut map = HashMap::new();
510        context.inject_to_text_map(&mut map)?;
511        assert!(map.contains_key(constants::TRACER_CONTEXT_HEADER_NAME));
512
513        Ok(())
514    }
515
516    #[test]
517    fn extract_from_text_map_works() -> TestResult {
518        let mut map = HashMap::new();
519        map.insert(
520            constants::TRACER_CONTEXT_HEADER_NAME.to_string(),
521            "6309ab92c95468edea0dc1a9772ae2dc:409423a204bc17a8:0:1".to_string(),
522        );
523        let context = SpanContext::extract_from_text_map(&map)?;
524        let context = track_assert_some!(context, Failed);
525        let trace_id = context.state().trace_id();
526        assert_eq!(trace_id.to_string(), "6309ab92c95468edea0dc1a9772ae2dc");
527
528        Ok(())
529    }
530
531    /// Official Java client `io.jaegertracing:jaeger-client:0.33.1`
532    /// sends HTTP header `uber-trace-id` with url-encoding.
533    #[test]
534    fn extract_from_urlencoded_text_map_works() -> TestResult {
535        let mut map = HashMap::new();
536        map.insert(
537            constants::TRACER_CONTEXT_HEADER_NAME.to_string(),
538            "6309ab92c95468edea0dc1a9772ae2dc%3A409423a204bc17a8%3A0%3A1".to_string(),
539        );
540        let context = SpanContext::extract_from_text_map(&map)?;
541        let context = track_assert_some!(context, Failed);
542        let trace_id = context.state().trace_id();
543        assert_eq!(trace_id.to_string(), "6309ab92c95468edea0dc1a9772ae2dc");
544
545        Ok(())
546    }
547
548    #[test]
549    fn extract_debug_id_works() -> TestResult {
550        let mut map = HashMap::new();
551        map.insert(
552            constants::JAEGER_DEBUG_HEADER.to_string(),
553            "abcdef".to_string(),
554        );
555        let context = SpanContext::extract_from_text_map(&map)?;
556        let context = track_assert_some!(context, Failed);
557        let debug_id = context.state().debug_id();
558        assert_eq!(debug_id, Some("abcdef"));
559
560        Ok(())
561    }
562
563    #[test]
564    fn inject_to_binary_works() -> TestResult {
565        let (tracer, _span_rx) = Tracer::new(AllSampler);
566        let parent_span = tracer.span("parent_span_test").start();
567        let span = tracer
568            .span("span_to_be_injected_test")
569            .child_of(parent_span.context().unwrap())
570            .start();
571        let context = track_assert_some!(span.context(), Failed);
572
573        let mut span_buf: Cursor<Vec<u8>> = Cursor::new(vec![]);
574        context.clone().inject_to_binary(&mut span_buf)?;
575
576        // deliberately convert io::Cursor<Vec<u8>> to Vec<u8> and re-read elements
577        let sbv = span_buf.get_ref().to_vec();
578        let mut u64buf: [u8; 8] = [0; 8];
579        let mut u32buf: [u8; 4] = [0; 4];
580        let mut u8buf: [u8; 1] = [0; 1];
581
582        u64buf.copy_from_slice(&sbv[0..8]);
583        assert_eq!(context.state().trace_id().high, u64::from_be_bytes(u64buf));
584        u64buf.copy_from_slice(&sbv[8..16]);
585        assert_eq!(context.state().trace_id().low, u64::from_be_bytes(u64buf));
586        u64buf.copy_from_slice(&sbv[16..24]);
587        assert_eq!(context.state().span_id(), u64::from_be_bytes(u64buf));
588        u64buf.copy_from_slice(&sbv[24..32]);
589        assert_eq!(0, u64::from_be_bytes(u64buf)); // parent_span_id attribute is obsolete.
590        u8buf.copy_from_slice(&sbv[32..33]);
591        assert_eq!(context.state().flags(), u8buf[0]);
592        u32buf.copy_from_slice(&sbv[33..37]);
593        assert_eq!(0, u32::from_be_bytes(u32buf)); // no baggage item length
594
595        Ok(())
596    }
597
598    #[test]
599    fn extract_from_binary_works() -> TestResult {
600        let mut span_buf: Cursor<Vec<u8>> = Cursor::new(vec![
601            0xab, 0xcd, 0xef, 0xed, 0xcb, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0xbc, 0xde, 0xfe,
602            0xdc, 0xba, // trace_id=abcdefedcbabcdef fedcbabcdefedcba
603            0, 0, 0, 0, 0, 0, 0, 11, // span_id=11
604            0, 0, 0, 0, 0, 0, 0, 12, // parent_span_id=12
605            1,  // flags=1
606            0, 0, 0, 0, // baggage item length=0
607        ]);
608
609        let context = SpanContext::extract_from_binary(&mut span_buf)?;
610        let context = track_assert_some!(context, Failed);
611        assert_eq!(
612            context.state().trace_id().to_string(),
613            "abcdefedcbabcdeffedcbabcdefedcba"
614        );
615        assert_eq!(context.state().span_id(), 11);
616        assert_eq!(context.state().flags(), 1);
617
618        // make a span from this context
619        let (tracer, _span_rx) = Tracer::new(AllSampler);
620        tracer
621            .span("test_from_spancontext")
622            .child_of(&context)
623            .start();
624
625        Ok(())
626    }
627}