1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
use crate::sdk;
use crate::trace::{IdGenerator, SpanId, TraceId};
use std::time::{Duration, UNIX_EPOCH};

/// Generates AWS X-Ray compliant Trace and Span ids.
///
/// Generates OpenTelemetry formatted `TraceId`'s and `SpanId`'s. The `TraceId`'s are generated so
/// they can be backed out into X-Ray format by the [AWS X-Ray Exporter][xray-exporter] in the
/// [OpenTelemetry Collector][otel-collector].
///
/// ## Trace ID Format
///
/// A `trace_id` consists of three numbers separated by hyphens. For example, `1-58406520-a006649127e371903a2de979`.
/// This includes:
///
/// * The version number, that is, 1.
/// * The time of the original request, in Unix epoch time, in 8 hexadecimal digits.
/// * For example, 10:00AM December 1st, 2016 PST in epoch time is 1480615200 seconds, or 58406520 in hexadecimal digits.
/// * A 96-bit identifier for the trace, globally unique, in 24 hexadecimal digits.
///
/// See the [AWS X-Ray Documentation][xray-trace-id] for more details.
///
/// ## Example
///
/// ```
/// use opentelemetry::trace::NoopSpanExporter;
/// use opentelemetry::sdk::trace::{Config, TracerProvider, XrayIdGenerator};
///
/// let _provider: TracerProvider = TracerProvider::builder()
///     .with_simple_exporter(NoopSpanExporter::new())
///     .with_config(Config {
///          id_generator: Box::new(XrayIdGenerator::default()),
///          ..Default::default()
///     })
///     .build();
/// ```
///
/// [otel-collector]: https://github.com/open-telemetry/opentelemetry-collector-contrib#opentelemetry-collector-contrib
/// [xray-exporter]: https://godoc.org/github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter
/// [xray-trace-id]: https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids
#[derive(Debug, Default)]
pub struct XrayIdGenerator {
    sdk_default_generator: sdk::trace::IdGenerator,
}

impl IdGenerator for XrayIdGenerator {
    /// Generates a new `TraceId` that can be converted to an X-Ray Trace ID
    fn new_trace_id(&self) -> TraceId {
        let mut default_trace_id: String = format!(
            "{:024x}",
            self.sdk_default_generator.new_trace_id().to_u128()
        );

        default_trace_id.truncate(24);

        let epoch_time_seconds: u64 = crate::time::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_else(|_| Duration::from_secs(0))
            .as_secs();

        TraceId::from_hex(format!("{:08x}{}", epoch_time_seconds, default_trace_id).as_str())
    }

    /// Generates a new `SpanId` that can be converted to an X-Ray Segment ID
    fn new_span_id(&self) -> SpanId {
        self.sdk_default_generator.new_span_id()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread::sleep;

    #[test]
    fn test_trace_id_generation() {
        let before: u64 = crate::time::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        sleep(Duration::from_secs(1));

        let generator: XrayIdGenerator = XrayIdGenerator::default();
        let trace_id: TraceId = generator.new_trace_id();

        sleep(Duration::from_secs(1));
        let after: u64 = crate::time::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        let trace_as_hex: String = format!("{:032x}", trace_id.to_u128());
        let (timestamp, _xray_id) = trace_as_hex.split_at(8_usize);

        let trace_time: u64 = u64::from_str_radix(timestamp, 16).unwrap();

        assert!(before <= trace_time);
        assert!(after >= trace_time);
    }
}