use opentelemetry::global;
use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use tracing::warn;
pub struct ClientMetadata {
pub session_id: Option<String>,
pub client_name: Option<String>,
pub client_version: Option<String>,
}
pub fn extract_and_set_trace_context(
meta: Option<&rmcp::model::MetaObject>,
client_meta: ClientMetadata,
) {
use tracing_opentelemetry::OpenTelemetrySpanExt as _;
let span = tracing::Span::current();
if let Some(sid) = client_meta.session_id {
span.record("mcp.session.id", &sid);
}
if let Some(cn) = client_meta.client_name {
span.record("client.name", &cn);
}
if let Some(cv) = client_meta.client_version {
span.record("client.version", &cv);
}
if let Some(asi_str) = meta.and_then(|m| m.0.get("agent-session-id").and_then(|v| v.as_str())) {
span.record("mcp.client.session.id", asi_str);
}
let Some(meta) = meta else { return };
let mut propagation_map = std::collections::HashMap::new();
if let Some(traceparent) = meta.0.get("traceparent")
&& let Some(tp_str) = traceparent.as_str()
{
propagation_map.insert("traceparent".to_string(), tp_str.to_string());
}
if let Some(tracestate) = meta.0.get("tracestate")
&& let Some(ts_str) = tracestate.as_str()
{
propagation_map.insert("tracestate".to_string(), ts_str.to_string());
}
if propagation_map.is_empty() {
return;
}
let parent_cx = opentelemetry::global::get_text_map_propagator(|propagator| {
propagator.extract(&ExtractMap(&propagation_map))
});
let _ = span.set_parent(parent_cx);
}
struct ExtractMap<'a>(&'a std::collections::HashMap<String, String>);
impl<'a> opentelemetry::propagation::Extractor for ExtractMap<'a> {
fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).map(|s| s.as_str())
}
fn keys(&self) -> Vec<&str> {
self.0.keys().map(|k| k.as_str()).collect()
}
}
fn service_resource() -> Resource {
Resource::builder()
.with_attribute(opentelemetry::KeyValue::new("service.name", "aptu-coder"))
.with_attribute(opentelemetry::KeyValue::new(
"service.version",
env!("CARGO_PKG_VERSION"),
))
.build()
}
pub fn init_otel() -> Option<SdkTracerProvider> {
let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok()?;
let exporter = match opentelemetry_otlp::SpanExporter::builder()
.with_http()
.with_protocol(Protocol::HttpBinary)
.with_endpoint(&endpoint)
.build()
{
Ok(exp) => exp,
Err(e) => {
warn!("Failed to build OTLP exporter: {}", e);
return None;
}
};
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(service_resource())
.build();
global::set_tracer_provider(provider.clone());
Some(provider)
}
pub fn init_log_appender() -> Option<SdkLoggerProvider> {
let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok()?;
let exporter = match opentelemetry_otlp::LogExporter::builder()
.with_http()
.with_protocol(Protocol::HttpBinary)
.with_endpoint(&endpoint)
.build()
{
Ok(exp) => exp,
Err(e) => {
warn!("Failed to build OTLP log exporter: {}", e);
return None;
}
};
let provider = SdkLoggerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(service_resource())
.build();
Some(provider)
}
pub fn init_meter() -> Option<SdkMeterProvider> {
let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok()?;
let exporter = match opentelemetry_otlp::MetricExporter::builder()
.with_http()
.with_protocol(Protocol::HttpBinary)
.with_endpoint(&endpoint)
.build()
{
Ok(exp) => exp,
Err(e) => {
warn!("Failed to build OTLP metrics exporter: {}", e);
return None;
}
};
let provider = SdkMeterProvider::builder()
.with_reader(opentelemetry_sdk::metrics::PeriodicReader::builder(exporter).build())
.with_resource(service_resource())
.build();
global::set_meter_provider(provider.clone());
Some(provider)
}