use crate::telemetry::attributes;
use opentelemetry::trace::{Span as OtelSpan, SpanKind, Status, Tracer, TracerProvider};
use opentelemetry::Context;
use opentelemetry_sdk::trace::{SdkTracerProvider, Span, Tracer as SdkTracer};
use std::sync::Arc;
use std::sync::RwLock;
static TRACER_PROVIDER: RwLock<Option<Arc<SdkTracerProvider>>> = RwLock::new(None);
pub fn init_direct_tracer(tracer_provider: Arc<SdkTracerProvider>) {
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = Some(tracer_provider);
}
fn get_tracer() -> Option<SdkTracer> {
let provider = TRACER_PROVIDER.read().unwrap();
provider.as_ref().map(|p| p.tracer("molock-direct"))
}
#[must_use]
pub fn create_http_server_span(
name: &str,
method: &str,
target: &str,
route: &str,
parent_cx: &Context,
) -> Option<Span> {
let tracer = get_tracer()?;
let span = tracer
.span_builder(name.to_string())
.with_kind(SpanKind::Server)
.with_attributes(vec![
attributes::kv::http_method(method),
attributes::kv::http_target(target),
attributes::kv::http_route(route),
])
.start_with_context(&tracer, parent_cx);
Some(span)
}
pub fn set_http_response_status_code(span: &mut Span, status: u16) {
span.set_attribute(attributes::kv::http_response_status_code(status));
match status {
200..=299 => span.set_status(Status::Ok),
400..=499 => span.set_status(Status::error("Client error")),
500..=599 => span.set_status(Status::error("Server error")),
_ => span.set_status(Status::Unset),
}
}
pub fn end_span(mut span: Span) {
span.end();
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn test_create_http_server_span_without_initialization() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = None;
drop(provider);
let cx = Context::current();
let span = create_http_server_span("test-span", "GET", "/test", "/test", &cx);
assert!(span.is_none());
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_get_tracer_without_initialization() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = None;
drop(provider);
let tracer = get_tracer();
assert!(tracer.is_none());
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_init_direct_tracer() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let tracer = get_tracer();
assert!(tracer.is_some());
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_create_http_server_span_with_initialization() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let cx = Context::current();
let span = create_http_server_span("http.request", "GET", "/api/users", "/api/users", &cx);
assert!(span.is_some());
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_create_http_server_span_no_duplicate_span_kind_attribute() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let cx = Context::current();
let span = create_http_server_span("http.request", "GET", "/test", "/test", &cx);
assert!(span.is_some());
end_span(span.unwrap());
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_create_http_server_span_with_parent_context() {
use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let parent_span_ctx = SpanContext::new(
TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e4736").unwrap(),
SpanId::from_hex("00f067aa0ba902b7").unwrap(),
TraceFlags::SAMPLED,
true,
TraceState::default(),
);
use opentelemetry::trace::TraceContextExt;
let parent_cx = Context::current().with_remote_span_context(parent_span_ctx.clone());
let span = create_http_server_span(
"http.request",
"GET",
"/api/resource",
"/api/resource",
&parent_cx,
);
assert!(
span.is_some(),
"span should be created with a parent context"
);
let child_span = span.unwrap();
let child_ctx = child_span.span_context();
assert_eq!(
child_ctx.trace_id(),
parent_span_ctx.trace_id(),
"child span TraceId must match the parent TraceId for correct propagation"
);
end_span(child_span);
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_set_http_response_status_code() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let tracer = get_tracer().unwrap();
let mut span = tracer.start("test-span");
set_http_response_status_code(&mut span, 200);
let mut span = tracer.start("test-span-404");
set_http_response_status_code(&mut span, 404);
let mut span = tracer.start("test-span-500");
set_http_response_status_code(&mut span, 500);
let mut span = tracer.start("test-span-300");
set_http_response_status_code(&mut span, 300);
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_end_span() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let tracer = get_tracer().unwrap();
let span = tracer.start("test-span");
end_span(span);
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_create_http_server_span_with_different_methods() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let methods = vec!["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"];
let cx = Context::current();
for method in methods {
let span =
create_http_server_span("http.request", method, "/api/test", "/api/test", &cx);
assert!(span.is_some());
end_span(span.unwrap());
}
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_create_http_server_span_with_different_paths() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let paths = vec![
"/",
"/api/users",
"/api/users/123",
"/api/orders?page=1&limit=10",
"/api/search?q=test%20query",
];
let cx = Context::current();
for path in paths {
let span = create_http_server_span("http.request", "GET", path, path, &cx);
assert!(span.is_some());
end_span(span.unwrap());
}
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
#[test]
fn test_semantic_convention_usage() {
let _guard = TEST_LOCK.lock().unwrap();
let original_provider = {
let provider = TRACER_PROVIDER.read().unwrap();
provider.clone()
};
let tracer_provider = SdkTracerProvider::builder().build();
init_direct_tracer(Arc::new(tracer_provider));
let cx = Context::current();
let span = create_http_server_span("http.request", "POST", "/api/users", "/api/users", &cx)
.unwrap();
end_span(span);
let mut provider = TRACER_PROVIDER.write().unwrap();
*provider = original_provider;
}
}