use std::collections::HashMap;
pub mod exporter;
pub mod prefixer;
use opentelemetry::{
Context,
propagation::{
Extractor,
Injector,
},
};
pub const OTEL_NAME: &str = "otel.name";
pub const OTEL_ORIGINAL_NAME: &str = "otel.original_name";
pub const OTEL_KIND: &str = "otel.kind";
pub const OTEL_STATUS_CODE: &str = "otel.status_code";
pub const OTEL_STATUS_MESSAGE: &str = "otel.status_message";
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TraceContext {
pub traceparent: Option<String>,
pub tracestate: Option<String>,
}
impl TraceContext {
#[must_use]
pub fn new(traceparent: Option<String>, tracestate: Option<String>) -> Self {
Self {
traceparent,
tracestate,
}
}
#[must_use]
pub fn from_current_span() -> Self {
use opentelemetry::global;
let mut map = HashMap::new();
let cx = Context::current();
global::get_text_map_propagator(|propagator| {
propagator.inject_context(&cx, &mut JsonRpcInjector::new(&mut map));
});
Self {
traceparent: map.remove("traceparent"),
tracestate: map.remove("tracestate"),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.traceparent.is_none() && self.tracestate.is_none()
}
pub fn attach(&self) -> opentelemetry::ContextGuard {
use opentelemetry::global;
let mut map = HashMap::new();
if let Some(tp) = &self.traceparent {
map.insert("traceparent".to_string(), tp.clone());
}
if let Some(ts) = &self.tracestate {
map.insert("tracestate".to_string(), ts.clone());
}
let parent_cx = global::get_text_map_propagator(|propagator| {
propagator.extract(&JsonRpcExtractor::new(&map))
});
parent_cx.attach()
}
}
pub struct JsonRpcInjector<'a> {
map: &'a mut HashMap<String, String>,
}
impl<'a> JsonRpcInjector<'a> {
pub fn new(map: &'a mut HashMap<String, String>) -> Self {
Self { map }
}
}
impl Injector for JsonRpcInjector<'_> {
fn set(&mut self, key: &str, value: String) {
self.map.insert(key.to_string(), value);
}
}
pub struct JsonRpcExtractor<'a> {
map: &'a HashMap<String, String>,
}
impl<'a> JsonRpcExtractor<'a> {
pub fn new(map: &'a HashMap<String, String>) -> Self {
Self { map }
}
}
impl Extractor for JsonRpcExtractor<'_> {
fn get(&self, key: &str) -> Option<&str> {
self.map.get(key).map(|s| s.as_str())
}
fn keys(&self) -> Vec<&str> {
self.map.keys().map(|s| s.as_str()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trace_context_default_is_empty() {
let ctx = TraceContext::default();
assert!(ctx.is_empty());
assert_eq!(ctx.traceparent, None);
assert_eq!(ctx.tracestate, None);
}
#[test]
fn trace_context_with_traceparent_is_not_empty() {
let ctx = TraceContext::new(
Some(
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string(),
),
None,
);
assert!(!ctx.is_empty());
}
#[test]
fn trace_context_with_tracestate_is_not_empty() {
let ctx =
TraceContext::new(None, Some("rojo=00f067aa0ba902b7".to_string()));
assert!(!ctx.is_empty());
}
#[test]
fn trace_context_with_both_is_not_empty() {
let ctx = TraceContext::new(
Some(
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string(),
),
Some("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE".to_string()),
);
assert!(!ctx.is_empty());
assert_eq!(
ctx.traceparent,
Some(
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string()
)
);
assert_eq!(
ctx.tracestate,
Some("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE".to_string())
);
}
}