#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
unreachable_pub,
unused
)]
#![cfg_attr(
docsrs,
feature(doc_cfg, doc_auto_cfg),
deny(rustdoc::broken_intra_doc_links)
)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo.svg"
)]
#![cfg_attr(test, deny(warnings))]
pub use exporter::config;
#[cfg(feature = "collector_client")]
pub use exporter::config::collector::new_collector_pipeline;
#[cfg(feature = "wasm_collector_client")]
pub use exporter::config::collector::new_wasm_collector_pipeline;
pub use exporter::{
config::agent::new_agent_pipeline, runtime::JaegerTraceRuntime, Error, Exporter, Process,
};
pub use propagator::Propagator;
mod exporter;
#[cfg(feature = "integration_test")]
#[doc(hidden)]
pub mod testing;
mod propagator {
use opentelemetry::{
global::{self, Error},
propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
trace::{
SpanContext, SpanId, TraceContextExt, TraceError, TraceFlags, TraceId, TraceState,
},
Context,
};
use std::borrow::Cow;
use std::str::FromStr;
const JAEGER_HEADER: &str = "uber-trace-id";
const JAEGER_BAGGAGE_PREFIX: &str = "uberctx-";
const DEPRECATED_PARENT_SPAN: &str = "0";
const TRACE_FLAG_DEBUG: TraceFlags = TraceFlags::new(0x04);
#[derive(Clone, Debug)]
pub struct Propagator {
baggage_prefix: &'static str,
header_name: &'static str,
fields: [String; 1],
}
impl Default for Propagator {
fn default() -> Self {
Propagator::new()
}
}
impl Propagator {
pub fn new() -> Self {
Self::with_custom_header_and_baggage(JAEGER_HEADER, JAEGER_BAGGAGE_PREFIX)
}
pub fn with_custom_header(custom_header_name: &'static str) -> Self {
Self::with_custom_header_and_baggage(custom_header_name, JAEGER_BAGGAGE_PREFIX)
}
pub fn with_custom_header_and_baggage(
custom_header_name: &'static str,
custom_baggage_prefix: &'static str,
) -> Self {
let custom_header_name = if custom_header_name.trim().is_empty() {
JAEGER_HEADER
} else {
custom_header_name
};
let custom_baggage_prefix = if custom_baggage_prefix.trim().is_empty() {
JAEGER_BAGGAGE_PREFIX
} else {
custom_baggage_prefix
};
Propagator {
baggage_prefix: custom_baggage_prefix.trim(),
header_name: custom_header_name.trim(),
fields: [custom_header_name.to_owned()],
}
}
fn extract_span_context(&self, extractor: &dyn Extractor) -> Result<SpanContext, ()> {
let mut header_value = Cow::from(extractor.get(self.header_name).unwrap_or(""));
if !header_value.contains(':') {
header_value = Cow::from(header_value.replace("%3A", ":"));
}
let parts = header_value.split_terminator(':').collect::<Vec<&str>>();
if parts.len() != 4 {
return Err(());
}
let trace_id = self.extract_trace_id(parts[0])?;
let span_id = self.extract_span_id(parts[1])?;
let flags = self.extract_trace_flags(parts[3])?;
let state = self.extract_trace_state(extractor)?;
Ok(SpanContext::new(trace_id, span_id, flags, true, state))
}
fn extract_trace_id(&self, trace_id: &str) -> Result<TraceId, ()> {
if trace_id.len() > 32 {
return Err(());
}
TraceId::from_hex(trace_id).map_err(|_| ())
}
fn extract_span_id(&self, span_id: &str) -> Result<SpanId, ()> {
if span_id.len() != 16 {
return Err(());
}
SpanId::from_hex(span_id).map_err(|_| ())
}
fn extract_trace_flags(&self, flag: &str) -> Result<TraceFlags, ()> {
if flag.len() > 2 {
return Err(());
}
let flag = u8::from_str(flag).map_err(|_| ())?;
if flag & 0x01 == 0x01 {
if flag & 0x02 == 0x02 {
Ok(TraceFlags::SAMPLED | TRACE_FLAG_DEBUG)
} else {
Ok(TraceFlags::SAMPLED)
}
} else {
Ok(TraceFlags::default())
}
}
fn extract_trace_state(&self, extractor: &dyn Extractor) -> Result<TraceState, ()> {
let baggage_keys = extractor
.keys()
.into_iter()
.filter(|key| key.starts_with(self.baggage_prefix))
.filter_map(|key| {
extractor
.get(key)
.map(|value| (key.to_string(), value.to_string()))
});
match TraceState::from_key_value(baggage_keys) {
Ok(trace_state) => Ok(trace_state),
Err(trace_state_err) => {
global::handle_error(Error::Trace(TraceError::Other(Box::new(
trace_state_err,
))));
Err(()) }
}
}
}
impl TextMapPropagator for Propagator {
fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
let span = cx.span();
let span_context = span.span_context();
if span_context.is_valid() {
let flag: u8 = if span_context.is_sampled() {
if span_context.trace_flags() & TRACE_FLAG_DEBUG == TRACE_FLAG_DEBUG {
0x03
} else {
0x01
}
} else {
0x00
};
let header_value = format!(
"{:032x}:{:016x}:{:01}:{:01x}",
span_context.trace_id(),
span_context.span_id(),
DEPRECATED_PARENT_SPAN,
flag,
);
injector.set(self.header_name, header_value);
}
}
fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
self.extract_span_context(extractor)
.map(|sc| cx.with_remote_span_context(sc))
.unwrap_or_else(|_| cx.clone())
}
fn fields(&self) -> FieldIter<'_> {
FieldIter::new(self.fields.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
use opentelemetry::{
propagation::{Injector, TextMapPropagator},
testing::trace::TestSpan,
trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
Context,
};
use std::collections::HashMap;
const LONG_TRACE_ID_STR: &str = "000000000000004d0000000000000016";
const SHORT_TRACE_ID_STR: &str = "4d0000000000000016";
const TRACE_ID: u128 = 0x0000_0000_0000_004d_0000_0000_0000_0016;
const SPAN_ID_STR: &str = "0000000000017c29";
const SPAN_ID: u64 = 0x0000_0000_0001_7c29;
fn get_extract_data() -> Vec<(&'static str, &'static str, u8, SpanContext)> {
vec![
(
LONG_TRACE_ID_STR,
SPAN_ID_STR,
1,
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::SAMPLED,
true,
TraceState::default(),
),
),
(
SHORT_TRACE_ID_STR,
SPAN_ID_STR,
1,
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::SAMPLED,
true,
TraceState::default(),
),
),
(
LONG_TRACE_ID_STR,
SPAN_ID_STR,
3,
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TRACE_FLAG_DEBUG | TraceFlags::SAMPLED,
true,
TraceState::default(),
),
),
(
LONG_TRACE_ID_STR,
SPAN_ID_STR,
0,
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::default(),
true,
TraceState::default(),
),
),
(
"invalidtractid",
SPAN_ID_STR,
0,
SpanContext::empty_context(),
),
(
LONG_TRACE_ID_STR,
"invalidspanID",
0,
SpanContext::empty_context(),
),
(
LONG_TRACE_ID_STR,
SPAN_ID_STR,
120,
SpanContext::empty_context(),
),
]
}
fn get_inject_data() -> Vec<(SpanContext, String)> {
vec![
(
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::SAMPLED,
true,
TraceState::default(),
),
format!("{}:{}:0:1", LONG_TRACE_ID_STR, SPAN_ID_STR),
),
(
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::default(),
true,
TraceState::default(),
),
format!("{}:{}:0:0", LONG_TRACE_ID_STR, SPAN_ID_STR),
),
(
SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TRACE_FLAG_DEBUG | TraceFlags::SAMPLED,
true,
TraceState::default(),
),
format!("{}:{}:0:3", LONG_TRACE_ID_STR, SPAN_ID_STR),
),
]
}
fn _test_extract_with_header(construct_header: &'static str, context_key: &'static str) {
let propagator = Propagator::with_custom_header(construct_header);
for (trace_id, span_id, flag, expected) in get_extract_data() {
let mut map: HashMap<String, String> = HashMap::new();
map.set(context_key, format!("{}:{}:0:{}", trace_id, span_id, flag));
let context = propagator.extract(&map);
assert_eq!(context.span().span_context(), &expected);
}
}
fn _test_inject_with_header(construct_header: &'static str, expect_header: &'static str) {
let propagator = Propagator::with_custom_header(construct_header);
for (span_context, header_value) in get_inject_data() {
let mut injector = HashMap::new();
propagator.inject_context(
&Context::current_with_span(TestSpan(span_context)),
&mut injector,
);
assert_eq!(injector.get(expect_header), Some(&header_value));
}
}
#[test]
fn test_extract_empty() {
let map: HashMap<String, String> = HashMap::new();
let propagator = Propagator::new();
let context = propagator.extract(&map);
assert_eq!(context.span().span_context(), &SpanContext::empty_context())
}
#[test]
fn test_inject_extract_with_default() {
let propagator = Propagator::default();
for (span_context, header_value) in get_inject_data() {
let mut injector = HashMap::new();
propagator.inject_context(
&Context::current_with_span(TestSpan(span_context)),
&mut injector,
);
assert_eq!(injector.get(JAEGER_HEADER), Some(&header_value));
}
for (trace_id, span_id, flag, expected) in get_extract_data() {
let mut map: HashMap<String, String> = HashMap::new();
map.set(
JAEGER_HEADER,
format!("{}:{}:0:{}", trace_id, span_id, flag),
);
let context = propagator.extract(&map);
assert_eq!(context.span().span_context(), &expected);
}
}
#[test]
fn test_extract_too_many_parts() {
let mut map: HashMap<String, String> = HashMap::new();
map.set(
JAEGER_HEADER,
format!("{}:{}:0:1:aa", LONG_TRACE_ID_STR, SPAN_ID_STR),
);
let propagator = Propagator::new();
let context = propagator.extract(&map);
assert_eq!(context.span().span_context(), &SpanContext::empty_context());
}
#[test]
fn test_extract_invalid_flag() {
let mut map: HashMap<String, String> = HashMap::new();
map.set(
JAEGER_HEADER,
format!("{}:{}:0:aa", LONG_TRACE_ID_STR, SPAN_ID_STR),
);
let propagator = Propagator::new();
let context = propagator.extract(&map);
assert_eq!(context.span().span_context(), &SpanContext::empty_context());
}
#[test]
fn test_extract_from_url() {
let mut map: HashMap<String, String> = HashMap::new();
map.set(
JAEGER_HEADER,
format!("{}%3A{}%3A0%3A1", LONG_TRACE_ID_STR, SPAN_ID_STR),
);
let propagator = Propagator::new();
let context = propagator.extract(&map);
assert_eq!(
context.span().span_context(),
&SpanContext::new(
TraceId::from_u128(TRACE_ID),
SpanId::from_u64(SPAN_ID),
TraceFlags::SAMPLED,
true,
TraceState::default(),
)
);
}
#[test]
fn test_extract() {
_test_extract_with_header(JAEGER_HEADER, JAEGER_HEADER)
}
#[test]
fn test_inject() {
_test_inject_with_header(JAEGER_HEADER, JAEGER_HEADER)
}
#[test]
fn test_extract_with_invalid_header() {
for construct in &["", " "] {
_test_extract_with_header(construct, JAEGER_HEADER)
}
}
#[test]
fn test_extract_with_valid_header() {
for construct in &["custom-header", "custom-header ", " custom-header "] {
_test_extract_with_header(construct, "custom-header")
}
}
#[test]
fn test_inject_with_invalid_header() {
for construct in &["", " "] {
_test_inject_with_header(construct, JAEGER_HEADER)
}
}
#[test]
fn test_inject_with_valid_header() {
for construct in &["custom-header", "custom-header ", " custom-header "] {
_test_inject_with_header(construct, "custom-header")
}
}
}
}