use std::any::type_name;
use axum::http::HeaderMap;
use axum::http::HeaderName;
use axum::http::HeaderValue;
use galvyn_core::re_exports::opentelemetry::propagation::Extractor;
use galvyn_core::re_exports::opentelemetry::propagation::Injector;
use galvyn_core::re_exports::opentelemetry::propagation::TextMapPropagator;
use galvyn_core::re_exports::opentelemetry::trace::TracerProvider;
use galvyn_core::re_exports::opentelemetry::Context;
use galvyn_core::re_exports::opentelemetry_otlp::ExporterBuildError;
use galvyn_core::re_exports::opentelemetry_otlp::SpanExporter;
use galvyn_core::re_exports::opentelemetry_otlp::WithExportConfig;
use galvyn_core::re_exports::opentelemetry_sdk::propagation::TraceContextPropagator;
use galvyn_core::re_exports::opentelemetry_sdk::trace::SdkTracerProvider;
use galvyn_core::re_exports::opentelemetry_sdk::Resource;
use galvyn_core::re_exports::tracing_opentelemetry::OpenTelemetrySpanExt;
use galvyn_core::re_exports::tracing_opentelemetry::SetParentError;
use tower_http::trace::MakeSpan;
use tower_http::trace::TraceLayer;
use tracing::debug;
use tracing::trace;
use tracing::warn;
use tracing::Span;
use tracing::Subscriber;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;
use crate::core::re_exports::tracing_opentelemetry;
pub struct OpenTelemetrySetup {
pub service_name: String,
pub exporter_otlp_endpoint: String,
}
impl OpenTelemetrySetup {
pub fn opentelemetry_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
self,
) -> Result<impl Layer<S>, ExporterBuildError> {
let exporter = SpanExporter::builder()
.with_tonic()
.with_endpoint(self.exporter_otlp_endpoint)
.build()?;
let resource = Resource::builder()
.with_service_name(self.service_name)
.build();
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(resource)
.build();
let tracer = provider.tracer("galvyn");
Ok(tracing_opentelemetry::layer()
.with_threads(false) .with_tracked_inactivity(false)
.with_tracer(tracer))
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct AttachTraces<T>(pub T);
impl<T: MakeSpan<B>, B> MakeSpan<B> for AttachTraces<T> {
fn make_span(&mut self, request: &axum::http::Request<B>) -> Span {
let span = self.0.make_span(request);
let context = headers_to_context(request.headers());
match span.set_parent(context) {
Ok(()) => {
trace!("Attached remote trace to request span");
}
Err(SetParentError::SpanDisabled) => {
debug!(
reason = "span-disabled",
"Can't attach remote trace to request span"
);
}
Err(SetParentError::LayerNotFound) => {
debug!(
reason = "layer-not-found",
"Can't attach remote trace to request span"
);
}
Err(SetParentError::AlreadyStarted) => {
warn!(
reason = "already-started",
explanation = format!(
"The `{}` which is wrapped by `AttachTraces` already started the span it created. They are not compatible.",
type_name::<T>()
),
"Can't attach remote trace to request span"
);
}
}
span
}
}
pub trait AttachTracesExt<Any> {
type Result;
fn attach_traces(self) -> Self::Result;
}
mod brands {
pub struct Layer;
}
impl<A, B, C, D, E, F, G> AttachTracesExt<brands::Layer> for TraceLayer<A, B, C, D, E, F, G>
where
B: Default,
{
type Result = TraceLayer<A, AttachTraces<B>, C, D, E, F, G>;
fn attach_traces(self) -> Self::Result {
self.make_span_with(Default::default())
}
}
impl<B, T: MakeSpan<B>> AttachTracesExt<B> for T {
type Result = AttachTraces<T>;
fn attach_traces(self) -> Self::Result {
AttachTraces(self)
}
}
pub fn context_to_headers(context: &Context) -> HeaderMap {
let mut map = HeaderMap::new();
TraceContextPropagator::new().inject_context(context, &mut HeaderMapWrite(&mut map));
map
}
pub fn headers_to_context(headers: &HeaderMap) -> Context {
TraceContextPropagator::new().extract(&HeaderMapRead(headers))
}
struct HeaderMapWrite<'a>(&'a mut HeaderMap);
impl Injector for HeaderMapWrite<'_> {
fn set(&mut self, key: &str, value: String) {
let Ok(name) = HeaderName::try_from(key) else {
warn!(key, value, "Opentelemetry produced an invalid header");
return;
};
let Ok(value) = HeaderValue::try_from(&value) else {
warn!(key, value, "Opentelemetry produced an invalid header");
return;
};
self.0.insert(name, value);
}
}
struct HeaderMapRead<'a>(&'a HeaderMap);
impl Extractor for HeaderMapRead<'_> {
fn get(&self, key: &str) -> Option<&str> {
let value = self.0.get(key)?;
value
.to_str()
.inspect_err(|_error| {
warn!(
key,
value = value.as_bytes(),
"Received invalid opentelemetry header"
)
})
.ok()
}
fn keys(&self) -> Vec<&str> {
warn!("Extractor::keys is not implemented");
vec![]
}
}