The style guide imports types and uses them unqualified. 222 places wrote a path such as std::collections::HashMap or std::time::Duration inline; each file now imports the type once, one use a module, and names it bare. Done with a script, file by file, only where the name is unambiguous there: - one path for the name throughout the file, and not a name the file already binds otherwise (its own types, other imports, a function's local use); - not a name generic enough to mislead bare (Error, Result, Value, Mutex, Client, Request, Response, Command, Event, Context, Map, Ok); - only code a normal build compiles, so no import is unused outside tests or names a crate only tests depend on. 193 paths stay, those cases above. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.3 KiB
Rust
96 lines
4.3 KiB
Rust
//! Traces, for ipx and the directory both (#213): the exporter, and W3C `traceparent` in from a
|
|
//! caller and out to a callee, so one trace follows a request from the browser through ipx-web
|
|
//! and ipx into the directory.
|
|
|
|
use axum::http::HeaderMap;
|
|
|
|
/// Traces over OTLP, to Tempo for one, when OTEL_EXPORTER_OTLP_ENDPOINT names a collector
|
|
/// (`http://host:4318`: the exporter speaks OTLP over HTTP and adds `/v1/traces`). The exporter
|
|
/// reads that and the other `OTEL_` variables itself.
|
|
pub fn provider(service: &'static str) -> anyhow::Result<Option<opentelemetry_sdk::trace::SdkTracerProvider>> {
|
|
if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap_or_default().is_empty() {
|
|
return Ok(None);
|
|
}
|
|
let exporter = opentelemetry_otlp::SpanExporter::builder().with_http().build()?;
|
|
let resource = opentelemetry_sdk::Resource::builder().with_service_name(service).build();
|
|
Ok(Some(
|
|
opentelemetry_sdk::trace::SdkTracerProvider::builder()
|
|
.with_batch_exporter(exporter)
|
|
.with_resource(resource)
|
|
.build(),
|
|
))
|
|
}
|
|
|
|
/// The trace a request from the page belongs to, from its `traceparent` (#190), so the requests
|
|
/// one action sets off are one trace. None for a missing or malformed one. In the query where no
|
|
/// header can be set: a position saved by sendBeacon, and the playlist Safari's player fetches
|
|
/// itself (#192).
|
|
#[must_use]
|
|
pub fn remote_parent(headers: &HeaderMap, query: Option<&str>) -> Option<opentelemetry::Context> {
|
|
use opentelemetry::trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState};
|
|
let v = match headers.get("traceparent") {
|
|
Some(h) => h.to_str().ok()?,
|
|
None => query?.split('&').find_map(|kv| kv.strip_prefix("traceparent="))?,
|
|
};
|
|
let mut parts = v.split('-');
|
|
let (version, trace, span) = (parts.next()?, parts.next()?, parts.next()?);
|
|
if version != "00" || trace.len() != 32 || span.len() != 16 {
|
|
return None;
|
|
}
|
|
let sc = SpanContext::new(
|
|
TraceId::from_hex(trace).ok()?,
|
|
SpanId::from_hex(span).ok()?,
|
|
TraceFlags::SAMPLED,
|
|
true,
|
|
TraceState::default(),
|
|
);
|
|
sc.is_valid().then(|| opentelemetry::Context::new().with_remote_span_context(sc))
|
|
}
|
|
|
|
/// The current span as a `traceparent`, for a request to another service, so its spans join
|
|
/// this trace. None outside a traced span.
|
|
#[must_use]
|
|
pub fn traceparent() -> Option<String> {
|
|
use opentelemetry::trace::TraceContextExt;
|
|
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
|
let cx = tracing::Span::current().context();
|
|
let sc = cx.span().span_context().clone();
|
|
sc.is_valid().then(|| format!("00-{}-{}-{:02x}", sc.trace_id(), sc.span_id(), sc.trace_flags().to_u8()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn a_requests_trace_is_the_pages_from_its_traceparent() {
|
|
use opentelemetry::trace::TraceContextExt;
|
|
let with = |v: &str| {
|
|
let mut h = axum::http::HeaderMap::new();
|
|
h.insert("traceparent", v.parse().unwrap());
|
|
remote_parent(&h, None)
|
|
};
|
|
let cx = with("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01").expect("parsed");
|
|
assert_eq!(cx.span().span_context().trace_id().to_string(), "4bf92f3577b34da6a3ce929d0e0e4736");
|
|
assert!(cx.span().span_context().is_remote());
|
|
// Anything else from the browser starts a trace of its own.
|
|
for bad in [
|
|
"",
|
|
"00-xyz-00f067aa0ba902b7-01",
|
|
"01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
|
"00-00000000000000000000000000000000-00f067aa0ba902b7-01",
|
|
"00-4bf92f3577b34da6a3ce929d0e0e4736",
|
|
] {
|
|
assert!(with(bad).is_none(), "{bad}");
|
|
}
|
|
assert!(remote_parent(&axum::http::HeaderMap::new(), None).is_none());
|
|
// From the query, where a beacon or Safari's player cannot send a header (#192).
|
|
let q = remote_parent(
|
|
&axum::http::HeaderMap::new(),
|
|
Some("x=1&traceparent=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"),
|
|
);
|
|
assert_eq!(q.unwrap().span().span_context().trace_id().to_string(), "4bf92f3577b34da6a3ce929d0e0e4736");
|
|
assert!(remote_parent(&axum::http::HeaderMap::new(), Some("traceparent=nonsense")).is_none());
|
|
}
|
|
}
|