diff --git a/crates/http/src/layers/otel/layer.rs b/crates/http/src/layers/otel/layer.rs index de245954..48c0a891 100644 --- a/crates/http/src/layers/otel/layer.rs +++ b/crates/http/src/layers/otel/layer.rs @@ -1,12 +1,15 @@ use std::sync::Arc; -use opentelemetry::metrics::{Counter, Histogram, UpDownCounter}; +use opentelemetry::{ + metrics::{Counter, Histogram, UpDownCounter}, + KeyValue, +}; use tower::Layer; use super::{ extract_context::DefaultExtractContext, inject_context::DefaultInjectContext, - make_span_builder::DefaultMakeSpanBuilder, on_error::DefaultOnError, - on_response::DefaultOnResponse, service::Trace, + make_metrics_labels::DefaultMakeMetricsLabels, make_span_builder::DefaultMakeSpanBuilder, + on_error::DefaultOnError, on_response::DefaultOnResponse, service::Trace, }; #[derive(Debug, Clone)] @@ -14,6 +17,7 @@ pub struct TraceLayer< ExtractContext = DefaultExtractContext, InjectContext = DefaultInjectContext, MakeSpanBuilder = DefaultMakeSpanBuilder, + MakeMetricsLabels = DefaultMakeMetricsLabels, OnResponse = DefaultOnResponse, OnError = DefaultOnError, > { @@ -21,12 +25,14 @@ pub struct TraceLayer< extract_context: ExtractContext, inject_context: InjectContext, make_span_builder: MakeSpanBuilder, + make_metrics_labels: MakeMetricsLabels, on_response: OnResponse, on_error: OnError, inflight_requests: UpDownCounter, request_counter: Counter, request_histogram: Histogram, + static_attributes: Vec, } impl Default for TraceLayer { @@ -63,8 +69,15 @@ impl TraceLayer { } } -impl - TraceLayer +impl + TraceLayer< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + > { #[must_use] pub fn new( @@ -77,6 +90,7 @@ impl ExtractContext: Default, InjectContext: Default, MakeSpanBuilder: Default, + MakeMetricsLabels: Default, OnResponse: Default, OnError: Default, { @@ -85,29 +99,55 @@ impl extract_context: ExtractContext::default(), inject_context: InjectContext::default(), make_span_builder: MakeSpanBuilder::default(), + make_metrics_labels: MakeMetricsLabels::default(), on_response: OnResponse::default(), on_error: OnError::default(), inflight_requests, request_counter, request_histogram, + static_attributes: Vec::new(), } } + #[must_use] + pub fn with_static_attribute(mut self, attribute: KeyValue) -> Self { + self.static_attributes.push(attribute); + self + } + + #[must_use] + pub fn with_static_attributes( + mut self, + attributes: impl IntoIterator, + ) -> Self { + self.static_attributes.extend(attributes); + self + } + #[must_use] pub fn extract_context( self, extract_context: NewExtractContext, - ) -> TraceLayer { + ) -> TraceLayer< + NewExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + > { TraceLayer { tracer: self.tracer, extract_context, inject_context: self.inject_context, make_span_builder: self.make_span_builder, + make_metrics_labels: self.make_metrics_labels, on_response: self.on_response, on_error: self.on_error, inflight_requests: self.inflight_requests, request_counter: self.request_counter, request_histogram: self.request_histogram, + static_attributes: self.static_attributes, } } @@ -115,17 +155,26 @@ impl pub fn inject_context( self, inject_context: NewInjectContext, - ) -> TraceLayer { + ) -> TraceLayer< + ExtractContext, + NewInjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + > { TraceLayer { tracer: self.tracer, extract_context: self.extract_context, inject_context, make_span_builder: self.make_span_builder, + make_metrics_labels: self.make_metrics_labels, on_response: self.on_response, on_error: self.on_error, inflight_requests: self.inflight_requests, request_counter: self.request_counter, request_histogram: self.request_histogram, + static_attributes: self.static_attributes, } } @@ -133,17 +182,53 @@ impl pub fn make_span_builder( self, make_span_builder: NewMakeSpanBuilder, - ) -> TraceLayer { + ) -> TraceLayer< + ExtractContext, + InjectContext, + NewMakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + > { TraceLayer { tracer: self.tracer, extract_context: self.extract_context, inject_context: self.inject_context, make_span_builder, + make_metrics_labels: self.make_metrics_labels, on_response: self.on_response, on_error: self.on_error, inflight_requests: self.inflight_requests, request_counter: self.request_counter, request_histogram: self.request_histogram, + static_attributes: self.static_attributes, + } + } + + #[must_use] + pub fn make_metrics_labels( + self, + make_metrics_labels: NewMakeMetricsLabels, + ) -> TraceLayer< + ExtractContext, + InjectContext, + MakeSpanBuilder, + NewMakeMetricsLabels, + OnResponse, + OnError, + > { + TraceLayer { + tracer: self.tracer, + extract_context: self.extract_context, + inject_context: self.inject_context, + make_span_builder: self.make_span_builder, + make_metrics_labels, + on_response: self.on_response, + on_error: self.on_error, + inflight_requests: self.inflight_requests, + request_counter: self.request_counter, + request_histogram: self.request_histogram, + static_attributes: self.static_attributes, } } @@ -151,17 +236,26 @@ impl pub fn on_response( self, on_response: NewOnResponse, - ) -> TraceLayer { + ) -> TraceLayer< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + NewOnResponse, + OnError, + > { TraceLayer { tracer: self.tracer, extract_context: self.extract_context, inject_context: self.inject_context, make_span_builder: self.make_span_builder, + make_metrics_labels: self.make_metrics_labels, on_response, on_error: self.on_error, inflight_requests: self.inflight_requests, request_counter: self.request_counter, request_histogram: self.request_histogram, + static_attributes: self.static_attributes, } } @@ -169,31 +263,57 @@ impl pub fn on_error( self, on_error: NewOnError, - ) -> TraceLayer { + ) -> TraceLayer< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + NewOnError, + > { TraceLayer { tracer: self.tracer, extract_context: self.extract_context, inject_context: self.inject_context, make_span_builder: self.make_span_builder, + make_metrics_labels: self.make_metrics_labels, on_response: self.on_response, on_error, inflight_requests: self.inflight_requests, request_counter: self.request_counter, request_histogram: self.request_histogram, + static_attributes: self.static_attributes, } } } -impl Layer - for TraceLayer +impl + Layer + for TraceLayer< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + > where ExtractContext: Clone, InjectContext: Clone, MakeSpanBuilder: Clone, + MakeMetricsLabels: Clone, OnResponse: Clone, OnError: Clone, { - type Service = Trace; + type Service = Trace< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + S, + >; fn layer(&self, inner: S) -> Self::Service { Trace::new( @@ -202,11 +322,13 @@ where self.extract_context.clone(), self.inject_context.clone(), self.make_span_builder.clone(), + self.make_metrics_labels.clone(), self.on_response.clone(), self.on_error.clone(), self.inflight_requests.clone(), self.request_counter.clone(), self.request_histogram.clone(), + self.static_attributes.clone(), ) } } diff --git a/crates/http/src/layers/otel/make_metrics_labels.rs b/crates/http/src/layers/otel/make_metrics_labels.rs new file mode 100644 index 00000000..132f1260 --- /dev/null +++ b/crates/http/src/layers/otel/make_metrics_labels.rs @@ -0,0 +1,61 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::borrow::Cow; + +use http::Request; +use opentelemetry::KeyValue; + +use super::utils::http_method_str; + +pub trait MakeMetricsLabels { + fn make_metrics_labels(&self, request: &R) -> Vec; +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct DefaultMakeMetricsLabels; + +impl MakeMetricsLabels for DefaultMakeMetricsLabels { + fn make_metrics_labels(&self, _request: &R) -> Vec { + Vec::new() + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct MetricsLabelsFromHttpRequest; + +impl MakeMetricsLabels> for MetricsLabelsFromHttpRequest { + fn make_metrics_labels(&self, request: &Request) -> Vec { + vec![KeyValue::new("method", http_method_str(request.method()))] + } +} + +#[cfg(feature = "axum")] +#[derive(Debug, Clone, Copy, Default)] +pub struct MetricsLabelsFromAxumRequest; + +#[cfg(feature = "axum")] +impl MakeMetricsLabels> for MetricsLabelsFromAxumRequest { + fn make_metrics_labels(&self, request: &Request) -> Vec { + let path: Cow<'static, str> = request + .extensions() + .get::() + .map_or("FALLBACK".into(), |path| path.as_str().to_owned().into()); + + vec![ + KeyValue::new("method", http_method_str(request.method())), + KeyValue::new("route", path), + ] + } +} diff --git a/crates/http/src/layers/otel/make_span_builder.rs b/crates/http/src/layers/otel/make_span_builder.rs index 94177fb4..3115ff43 100644 --- a/crates/http/src/layers/otel/make_span_builder.rs +++ b/crates/http/src/layers/otel/make_span_builder.rs @@ -12,22 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{borrow::Cow, vec::Vec}; +use std::borrow::Cow; #[cfg(feature = "axum")] use axum::extract::{ConnectInfo, MatchedPath}; use headers::{ContentLength, HeaderMapExt, Host, UserAgent}; -use http::{Method, Request, Version}; +use http::Request; #[cfg(feature = "client")] use hyper::client::connect::dns::Name; -use opentelemetry::{ - trace::{SpanBuilder, SpanKind}, - KeyValue, -}; +use opentelemetry::trace::{SpanBuilder, SpanKind}; use opentelemetry_semantic_conventions::trace as SC; +use super::utils::{http_flavor, http_method_str}; + pub trait MakeSpanBuilder { - fn make_span_builder(&self, request: &R) -> (SpanBuilder, Vec); + fn make_span_builder(&self, request: &R) -> SpanBuilder; } #[derive(Debug, Clone, Copy)] @@ -51,36 +50,8 @@ impl Default for DefaultMakeSpanBuilder { } impl MakeSpanBuilder for DefaultMakeSpanBuilder { - fn make_span_builder(&self, _request: &R) -> (SpanBuilder, Vec) { - (SpanBuilder::from_name(self.operation), Vec::new()) - } -} - -#[inline] -fn http_method_str(method: &Method) -> Cow<'static, str> { - match method { - &Method::OPTIONS => "OPTIONS".into(), - &Method::GET => "GET".into(), - &Method::POST => "POST".into(), - &Method::PUT => "PUT".into(), - &Method::DELETE => "DELETE".into(), - &Method::HEAD => "HEAD".into(), - &Method::TRACE => "TRACE".into(), - &Method::CONNECT => "CONNECT".into(), - &Method::PATCH => "PATCH".into(), - other => other.to_string().into(), - } -} - -#[inline] -fn http_flavor(version: Version) -> Cow<'static, str> { - match version { - Version::HTTP_09 => "0.9".into(), - Version::HTTP_10 => "1.0".into(), - Version::HTTP_11 => "1.1".into(), - Version::HTTP_2 => "2.0".into(), - Version::HTTP_3 => "3.0".into(), - other => format!("{:?}", other).into(), + fn make_span_builder(&self, _request: &R) -> SpanBuilder { + SpanBuilder::from_name(self.operation) } } @@ -117,10 +88,9 @@ impl SpanFromHttpRequest { } impl MakeSpanBuilder> for SpanFromHttpRequest { - fn make_span_builder(&self, request: &Request) -> (SpanBuilder, Vec) { - let method = SC::HTTP_METHOD.string(http_method_str(request.method())); + fn make_span_builder(&self, request: &Request) -> SpanBuilder { let mut attributes = vec![ - method.clone(), + SC::HTTP_METHOD.string(http_method_str(request.method())), SC::HTTP_FLAVOR.string(http_flavor(request.version())), SC::HTTP_TARGET.string(request.uri().to_string()), ]; @@ -141,12 +111,9 @@ impl MakeSpanBuilder> for SpanFromHttpRequest { } } - let span_builder = SpanBuilder::from_name(self.operation) + SpanBuilder::from_name(self.operation) .with_kind(self.span_kind.clone()) - .with_attributes(attributes); - - let metrics_labels = vec![method]; - (span_builder, metrics_labels) + .with_attributes(attributes) } } @@ -156,15 +123,21 @@ pub struct SpanFromAxumRequest; #[cfg(feature = "axum")] impl MakeSpanBuilder> for SpanFromAxumRequest { - fn make_span_builder(&self, request: &Request) -> (SpanBuilder, Vec) { - let method = SC::HTTP_METHOD.string(http_method_str(request.method())); - - let mut metrics_labels = vec![method.clone()]; + fn make_span_builder(&self, request: &Request) -> SpanBuilder { + let (name, route): (String, Cow<'static, str>) = + if let Some(path) = request.extensions().get::() { + let path = path.as_str().to_owned(); + let name = path.clone(); + (name, path.into()) + } else { + (request.uri().path().to_owned(), Cow::Borrowed("FALLBACK")) + }; let mut attributes = vec![ - method, + SC::HTTP_METHOD.string(http_method_str(request.method())), SC::HTTP_FLAVOR.string(http_flavor(request.version())), SC::HTTP_TARGET.string(request.uri().to_string()), + SC::HTTP_ROUTE.string(route), ]; let headers = request.headers(); @@ -192,23 +165,9 @@ impl MakeSpanBuilder> for SpanFromAxumRequest { attributes.push(SC::NET_PEER_PORT.i64(addr.port().into())); } - let (name, route) = if let Some(path) = request.extensions().get::() { - let path = path.as_str(); - (path, path) - } else { - (request.uri().path(), "FALLBACK") - }; - - let route = SC::HTTP_ROUTE.string(route.to_owned()); - attributes.push(route.clone()); - metrics_labels.push(route); - - ( - SpanBuilder::from_name(name.to_owned()) - .with_kind(SpanKind::Server) - .with_attributes(attributes), - metrics_labels, - ) + SpanBuilder::from_name(name) + .with_kind(SpanKind::Server) + .with_attributes(attributes) } } @@ -218,14 +177,11 @@ pub struct SpanFromDnsRequest; #[cfg(feature = "client")] impl MakeSpanBuilder for SpanFromDnsRequest { - fn make_span_builder(&self, request: &Name) -> (SpanBuilder, Vec) { + fn make_span_builder(&self, request: &Name) -> SpanBuilder { let attributes = vec![SC::NET_HOST_NAME.string(request.as_str().to_owned())]; - ( - SpanBuilder::from_name("resolve") - .with_kind(SpanKind::Client) - .with_attributes(attributes.clone()), - attributes, - ) + SpanBuilder::from_name("resolve") + .with_kind(SpanKind::Client) + .with_attributes(attributes) } } diff --git a/crates/http/src/layers/otel/mod.rs b/crates/http/src/layers/otel/mod.rs index 45dc7a16..7dd074bd 100644 --- a/crates/http/src/layers/otel/mod.rs +++ b/crates/http/src/layers/otel/mod.rs @@ -15,15 +15,18 @@ mod extract_context; mod inject_context; mod layer; +mod make_metrics_labels; mod make_span_builder; mod on_error; mod on_response; mod service; +mod utils; pub type TraceHttpServerLayer = TraceLayer< ExtractFromHttpRequest, DefaultInjectContext, SpanFromHttpRequest, + MetricsLabelsFromHttpRequest, OnHttpResponse, DefaultOnError, >; @@ -32,6 +35,7 @@ pub type TraceHttpServer = Trace< ExtractFromHttpRequest, DefaultInjectContext, SpanFromHttpRequest, + MetricsLabelsFromHttpRequest, OnHttpResponse, DefaultOnError, S, @@ -42,6 +46,7 @@ pub type TraceAxumServerLayer = TraceLayer< ExtractFromHttpRequest, DefaultInjectContext, SpanFromAxumRequest, + MetricsLabelsFromAxumRequest, OnHttpResponse, DefaultOnError, >; @@ -51,6 +56,7 @@ pub type TraceAxumServer = Trace< ExtractFromHttpRequest, DefaultInjectContext, SpanFromAxumRequest, + MetricsLabelsFromAxumRequest, OnHttpResponse, DefaultOnError, S, @@ -60,6 +66,7 @@ pub type TraceHttpClientLayer = TraceLayer< DefaultExtractContext, InjectInHttpRequest, SpanFromHttpRequest, + MetricsLabelsFromHttpRequest, OnHttpResponse, DefaultOnError, >; @@ -68,6 +75,7 @@ pub type TraceHttpClient = Trace< DefaultExtractContext, InjectInHttpRequest, SpanFromHttpRequest, + MetricsLabelsFromHttpRequest, OnHttpResponse, DefaultOnError, S, @@ -78,6 +86,7 @@ pub type TraceDnsLayer = TraceLayer< DefaultExtractContext, DefaultInjectContext, SpanFromDnsRequest, + DefaultMakeMetricsLabels, DefaultOnResponse, DefaultOnError, >; @@ -87,6 +96,7 @@ pub type TraceDns = Trace< DefaultExtractContext, DefaultInjectContext, SpanFromDnsRequest, + DefaultMakeMetricsLabels, DefaultOnResponse, DefaultOnError, S, @@ -97,6 +107,7 @@ impl TraceHttpServerLayer { pub fn http_server() -> Self { TraceLayer::with_namespace("http_server") .make_span_builder(SpanFromHttpRequest::server()) + .make_metrics_labels(MetricsLabelsFromHttpRequest::default()) .on_response(OnHttpResponse) .extract_context(ExtractFromHttpRequest) } @@ -108,6 +119,7 @@ impl TraceAxumServerLayer { pub fn axum() -> Self { TraceLayer::with_namespace("http_server") .make_span_builder(SpanFromAxumRequest) + .make_metrics_labels(MetricsLabelsFromAxumRequest::default()) .on_response(OnHttpResponse) .extract_context(ExtractFromHttpRequest) } @@ -118,6 +130,7 @@ impl TraceHttpClientLayer { pub fn http_client(operation: &'static str) -> Self { TraceLayer::with_namespace("http_client") .make_span_builder(SpanFromHttpRequest::client(operation)) + .make_metrics_labels(MetricsLabelsFromHttpRequest::default()) .on_response(OnHttpResponse) .inject_context(InjectInHttpRequest) } @@ -126,6 +139,7 @@ impl TraceHttpClientLayer { pub fn inner_http_client() -> Self { TraceLayer::with_namespace("inner_http_client") .make_span_builder(SpanFromHttpRequest::inner_client()) + .make_metrics_labels(MetricsLabelsFromHttpRequest::default()) .on_response(OnHttpResponse) .inject_context(InjectInHttpRequest) } @@ -139,6 +153,9 @@ impl TraceDnsLayer { } } +use self::make_metrics_labels::{ + DefaultMakeMetricsLabels, MetricsLabelsFromAxumRequest, MetricsLabelsFromHttpRequest, +}; pub use self::{ extract_context::*, inject_context::*, layer::*, make_span_builder::*, on_error::*, on_response::*, service::*, diff --git a/crates/http/src/layers/otel/on_error.rs b/crates/http/src/layers/otel/on_error.rs index 12930545..2c441115 100644 --- a/crates/http/src/layers/otel/on_error.rs +++ b/crates/http/src/layers/otel/on_error.rs @@ -16,7 +16,7 @@ use opentelemetry::{trace::SpanRef, KeyValue}; use opentelemetry_semantic_conventions::trace::EXCEPTION_MESSAGE; pub trait OnError { - fn on_error(&self, span: &SpanRef<'_>, err: &E) -> Vec; + fn on_error(&self, span: &SpanRef<'_>, metrics_labels: &mut Vec, err: &E); } #[derive(Debug, Clone, Copy, Default)] @@ -26,10 +26,8 @@ impl OnError for DefaultOnError where E: std::fmt::Display, { - fn on_error(&self, span: &SpanRef<'_>, err: &E) -> Vec { + fn on_error(&self, span: &SpanRef<'_>, _metrics_labels: &mut Vec, err: &E) { let attributes = vec![EXCEPTION_MESSAGE.string(err.to_string())]; span.add_event("exception".to_owned(), attributes); - - Vec::new() } } diff --git a/crates/http/src/layers/otel/on_response.rs b/crates/http/src/layers/otel/on_response.rs index 0c104e0f..59aae359 100644 --- a/crates/http/src/layers/otel/on_response.rs +++ b/crates/http/src/layers/otel/on_response.rs @@ -20,15 +20,14 @@ use opentelemetry::{trace::SpanRef, KeyValue}; use opentelemetry_semantic_conventions::trace as SC; pub trait OnResponse { - fn on_response(&self, span: &SpanRef<'_>, response: &R) -> Vec; + fn on_response(&self, span: &SpanRef<'_>, metrics_labels: &mut Vec, response: &R); } #[derive(Debug, Clone, Copy, Default)] pub struct DefaultOnResponse; impl OnResponse for DefaultOnResponse { - fn on_response(&self, _span: &SpanRef<'_>, _response: &R) -> Vec { - Vec::new() + fn on_response(&self, _span: &SpanRef<'_>, _metrics_labels: &mut Vec, _response: &R) { } } @@ -36,9 +35,15 @@ impl OnResponse for DefaultOnResponse { pub struct OnHttpResponse; impl OnResponse> for OnHttpResponse { - fn on_response(&self, span: &SpanRef<'_>, response: &Response) -> Vec { + fn on_response( + &self, + span: &SpanRef<'_>, + metrics_labels: &mut Vec, + response: &Response, + ) { let status_code = i64::from(response.status().as_u16()); span.set_attribute(SC::HTTP_STATUS_CODE.i64(status_code)); + metrics_labels.push(KeyValue::new("status_code", status_code)); if let Some(ContentLength(content_length)) = response.headers().typed_get() { if let Ok(content_length) = content_length.try_into() { @@ -55,7 +60,5 @@ impl OnResponse> for OnHttpResponse { span.set_attribute(SC::NET_HOST_IP.string(info.local_addr().ip().to_string())); span.set_attribute(SC::NET_HOST_PORT.i64(info.local_addr().port().into())); } - - vec![KeyValue::new("status_code", status_code)] } } diff --git a/crates/http/src/layers/otel/service.rs b/crates/http/src/layers/otel/service.rs index 84102c40..8dc49d6c 100644 --- a/crates/http/src/layers/otel/service.rs +++ b/crates/http/src/layers/otel/service.rs @@ -24,26 +24,37 @@ use tower::Service; use super::{ extract_context::ExtractContext, inject_context::InjectContext, - make_span_builder::MakeSpanBuilder, on_error::OnError, on_response::OnResponse, + make_metrics_labels::MakeMetricsLabels, make_span_builder::MakeSpanBuilder, on_error::OnError, + on_response::OnResponse, }; #[derive(Debug, Clone)] -pub struct Trace { +pub struct Trace< + ExtractContext, + InjectContext, + MakeSpanBuilder, + MakeMetricsLabels, + OnResponse, + OnError, + S, +> { inner: S, tracer: Arc, extract_context: ExtractContext, inject_context: InjectContext, make_span_builder: MakeSpanBuilder, + make_metrics_labels: MakeMetricsLabels, on_response: OnResponse, on_error: OnError, inflight_requests: UpDownCounter, request_counter: Counter, request_histogram: Histogram, + static_attributes: Vec, } -impl - Trace +impl + Trace { #[allow(clippy::too_many_arguments)] pub fn new( @@ -52,11 +63,13 @@ impl extract_context: ExtractContext, inject_context: InjectContext, make_span_builder: MakeSpanBuilder, + make_metrics_labels: MakeMetricsLabels, on_response: OnResponse, on_error: OnError, inflight_requests: UpDownCounter, request_counter: Counter, request_histogram: Histogram, + static_attributes: Vec, ) -> Self { Self { inner: service, @@ -65,12 +78,14 @@ impl extract_context, inject_context, make_span_builder, + make_metrics_labels, on_response, on_error, inflight_requests, request_counter, request_histogram, + static_attributes, } } } @@ -98,8 +113,25 @@ impl Drop for InFlightGuard { } } -impl Service - for Trace +impl< + Req, + S, + ExtractContextT, + InjectContextT, + MakeSpanBuilderT, + MakeMetricsLabelsT, + OnResponseT, + OnErrorT, + > Service + for Trace< + ExtractContextT, + InjectContextT, + MakeSpanBuilderT, + MakeMetricsLabelsT, + OnResponseT, + OnErrorT, + S, + > where ExtractContextT: ExtractContext + Send, InjectContextT: InjectContext + Send, @@ -107,6 +139,7 @@ where OnResponseT: OnResponse + Send + Clone + 'static, OnErrorT: OnError + Send + Clone + 'static, MakeSpanBuilderT: MakeSpanBuilder + Send, + MakeMetricsLabelsT: MakeMetricsLabels + Send, S::Future: Send + 'static, { type Response = S::Response; @@ -123,7 +156,15 @@ where let start_time = SystemTime::now(); let cx = self.extract_context.extract_context(&request); - let (span_builder, mut metrics_labels) = self.make_span_builder.make_span_builder(&request); + let mut span_builder = self.make_span_builder.make_span_builder(&request); + let mut metrics_labels = self.make_metrics_labels.make_metrics_labels(&request); + + // Add the static attributes to the metrics and the span + metrics_labels.extend_from_slice(&self.static_attributes[..]); + let mut span_attributes = span_builder.attributes.unwrap_or_default(); + span_attributes.extend(self.static_attributes.iter().cloned()); + span_builder.attributes = Some(span_attributes); + let span = span_builder.start_with_context(self.tracer.as_ref(), &cx); let cx = cx.with_span(span); @@ -144,11 +185,10 @@ where let _guard = guard; let span = cx.span(); - let extra_labels = match r { - Ok(response) => on_response.on_response(&span, response), - Err(err) => on_error.on_error(&span, err), + match r { + Ok(response) => on_response.on_response(&span, &mut metrics_labels, response), + Err(err) => on_error.on_error(&span, &mut metrics_labels, err), }; - metrics_labels.extend_from_slice(&extra_labels); request_counter.add(&cx, 1, &metrics_labels); request_histogram.record( diff --git a/crates/http/src/layers/otel/utils.rs b/crates/http/src/layers/otel/utils.rs new file mode 100644 index 00000000..d7606f55 --- /dev/null +++ b/crates/http/src/layers/otel/utils.rs @@ -0,0 +1,45 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::borrow::Cow; + +use http::{Method, Version}; + +#[inline] +pub(super) fn http_method_str(method: &Method) -> Cow<'static, str> { + match method { + &Method::OPTIONS => "OPTIONS".into(), + &Method::GET => "GET".into(), + &Method::POST => "POST".into(), + &Method::PUT => "PUT".into(), + &Method::DELETE => "DELETE".into(), + &Method::HEAD => "HEAD".into(), + &Method::TRACE => "TRACE".into(), + &Method::CONNECT => "CONNECT".into(), + &Method::PATCH => "PATCH".into(), + other => other.to_string().into(), + } +} + +#[inline] +pub(super) fn http_flavor(version: Version) -> Cow<'static, str> { + match version { + Version::HTTP_09 => "0.9".into(), + Version::HTTP_10 => "1.0".into(), + Version::HTTP_11 => "1.1".into(), + Version::HTTP_2 => "2.0".into(), + Version::HTTP_3 => "3.0".into(), + other => format!("{:?}", other).into(), + } +} diff --git a/crates/http/src/layers/server.rs b/crates/http/src/layers/server.rs index 8dc45171..c4d0e9cb 100644 --- a/crates/http/src/layers/server.rs +++ b/crates/http/src/layers/server.rs @@ -15,6 +15,7 @@ use std::marker::PhantomData; use http::{Request, Response}; +use opentelemetry::KeyValue; use tower::{util::BoxCloneService, Layer, Service, ServiceBuilder, ServiceExt}; use tower_http::{compression::CompressionBody, ServiceBuilderExt}; @@ -51,10 +52,17 @@ where let builder = ServiceBuilder::new().compression(); #[cfg(feature = "axum")] - let builder = builder.layer(TraceLayer::axum()); + let mut trace_layer = TraceLayer::axum(); #[cfg(not(feature = "axum"))] - let builder = builder.layer(TraceLayer::http_server()); + let mut trace_layer = TraceLayer::http_server(); + + if let Some(name) = &self.listener_name { + trace_layer = + trace_layer.with_static_attribute(KeyValue::new("listener", name.clone())); + } + + let builder = builder.layer(trace_layer); builder.service(inner).boxed_clone() }