|
1 | | -use http::Request; |
2 | | -#[cfg(feature = "opentelemetry")] |
3 | | -use opentelemetry_sdk::propagation::TraceContextPropagator; |
| 1 | +use http::{header, HeaderMap, Request, Response}; |
| 2 | +use std::time::Duration; |
4 | 3 | use tower_http::{ |
5 | | - classify::{ServerErrorsAsFailures, SharedClassifier}, |
6 | | - trace::{DefaultOnRequest, DefaultOnResponse, MakeSpan, TraceLayer}, |
| 4 | + classify::{SharedClassifier, StatusInRangeAsFailures, StatusInRangeFailureClass}, |
| 5 | + trace::{self, TraceLayer}, |
7 | 6 | }; |
8 | | -use tracing::{span, Level, Span}; |
| 7 | +use tracing::{info, span, Level, Span}; |
9 | 8 | use uuid::Uuid; |
10 | 9 |
|
11 | 10 | /// Creates a custom tracing span |
12 | | -#[derive(Clone, Debug, Default)] |
13 | | -pub struct MakeSpanWithId { |
14 | | - #[cfg(feature = "opentelemetry")] |
15 | | - propagator: TraceContextPropagator, |
16 | | -} |
| 11 | +#[derive(Clone, Copy, Debug)] |
| 12 | +pub struct MakeSpan; |
17 | 13 |
|
18 | | -impl<B> MakeSpan<B> for MakeSpanWithId { |
| 14 | +impl<B> trace::MakeSpan<B> for MakeSpan { |
19 | 15 | fn make_span(&mut self, request: &Request<B>) -> Span { |
| 16 | + let uri = request.uri(); |
20 | 17 | let span = span!( |
21 | 18 | Level::INFO, |
22 | 19 | "request", |
23 | | - method = %request.method(), |
24 | | - uri = %request.uri(), |
25 | | - version = ?request.version(), |
| 20 | + span.kind = "server", |
| 21 | + http.request.method = %request.method(), |
| 22 | + network.protocol.version = ?request.version(), |
| 23 | + uri.path = uri.path(), |
| 24 | + uri.scheme = uri.scheme_str().unwrap_or_default(), |
| 25 | + uri.query = uri.query().unwrap_or_default(), |
26 | 26 | id = %Uuid::new_v4(), |
27 | 27 | ); |
28 | 28 |
|
| 29 | + let headers = request.headers(); |
| 30 | + if let Some(user_agent) = headers |
| 31 | + .get(header::USER_AGENT) |
| 32 | + .and_then(|h| h.to_str().ok()) |
| 33 | + { |
| 34 | + span.record("user_agent.original", user_agent); |
| 35 | + } |
| 36 | + |
| 37 | + record_headers("request", headers, &span); |
| 38 | + |
29 | 39 | #[cfg(feature = "opentelemetry")] |
30 | 40 | { |
31 | 41 | use opentelemetry::propagation::TextMapPropagator; |
32 | 42 | use opentelemetry_http::HeaderExtractor; |
| 43 | + use opentelemetry_sdk::propagation::{BaggagePropagator, TraceContextPropagator}; |
33 | 44 | use tracing_opentelemetry::OpenTelemetrySpanExt; |
34 | 45 |
|
35 | | - let context = self.propagator.extract(&HeaderExtractor(request.headers())); |
| 46 | + let extractor = HeaderExtractor(headers); |
| 47 | + let context = TraceContextPropagator::new().extract(&extractor); |
| 48 | + let context = BaggagePropagator::new().extract_with_context(&context, &extractor); |
36 | 49 | span.set_parent(context); |
37 | 50 | } |
38 | 51 |
|
39 | 52 | span |
40 | 53 | } |
41 | 54 | } |
42 | 55 |
|
| 56 | +/// Adds custom trace metadata from the request |
| 57 | +#[derive(Clone, Copy, Debug)] |
| 58 | +pub struct OnRequest; |
| 59 | + |
| 60 | +impl<B> trace::OnRequest<B> for OnRequest { |
| 61 | + fn on_request(&mut self, _request: &Request<B>, _span: &Span) { |
| 62 | + info!("started processing request"); |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +/// Adds custom trace metadata from the request |
| 67 | +#[derive(Clone, Copy, Debug)] |
| 68 | +pub struct OnResponse; |
| 69 | + |
| 70 | +impl<B> trace::OnResponse<B> for OnResponse { |
| 71 | + fn on_response(self, response: &Response<B>, _latency: Duration, span: &Span) { |
| 72 | + span.record("http.response.status", response.status().as_u16()); |
| 73 | + record_headers("response", response.headers(), span); |
| 74 | + info!("finished processing request"); |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/// Adds custom trace metadata when a failure occurs |
| 79 | +#[derive(Clone, Copy, Debug)] |
| 80 | +pub struct OnFailure; |
| 81 | + |
| 82 | +impl trace::OnFailure<StatusInRangeFailureClass> for OnFailure { |
| 83 | + fn on_failure(&mut self, class: StatusInRangeFailureClass, _latency: Duration, span: &Span) { |
| 84 | + if span.has_field("error") || span.has_field("error.type") { |
| 85 | + return; |
| 86 | + } |
| 87 | + |
| 88 | + span.record("error", true); |
| 89 | + |
| 90 | + let kind = match class { |
| 91 | + StatusInRangeFailureClass::StatusCode(status) => { |
| 92 | + if status.is_client_error() { |
| 93 | + "client_error" |
| 94 | + } else { |
| 95 | + "server_error" |
| 96 | + } |
| 97 | + } |
| 98 | + StatusInRangeFailureClass::Error(_) => "internal_error", |
| 99 | + }; |
| 100 | + span.record("error.type", kind); |
| 101 | + } |
| 102 | +} |
| 103 | + |
43 | 104 | /// Create a logging middleware layer |
44 | | -pub fn layer() -> TraceLayer<SharedClassifier<ServerErrorsAsFailures>, MakeSpanWithId> { |
45 | | - TraceLayer::new_for_http() |
46 | | - .make_span_with(MakeSpanWithId::default()) |
47 | | - .on_request(DefaultOnRequest::new().level(Level::INFO)) |
48 | | - .on_response(DefaultOnResponse::new().level(Level::INFO)) |
| 105 | +pub fn layer() -> TraceLayer< |
| 106 | + SharedClassifier<StatusInRangeAsFailures>, |
| 107 | + MakeSpan, |
| 108 | + OnRequest, |
| 109 | + OnResponse, |
| 110 | + (), |
| 111 | + (), |
| 112 | + OnFailure, |
| 113 | +> { |
| 114 | + let classifier = StatusInRangeAsFailures::new_for_client_and_server_errors(); |
| 115 | + TraceLayer::new(classifier.into_make_classifier()) |
| 116 | + .make_span_with(MakeSpan) |
| 117 | + .on_request(OnRequest) |
| 118 | + .on_response(OnResponse) |
| 119 | + .on_failure(OnFailure) |
| 120 | + .on_eos(()) |
| 121 | + .on_body_chunk(()) |
| 122 | +} |
| 123 | + |
| 124 | +fn record_headers(direction: &'static str, headers: &HeaderMap, span: &Span) { |
| 125 | + for (header, value) in headers |
| 126 | + .iter() |
| 127 | + .filter_map(|(header, value)| value.to_str().ok().map(|value| (header, value))) |
| 128 | + { |
| 129 | + let header = header.as_str().to_lowercase(); |
| 130 | + span.record(format!("http.{direction}.header.{header}").as_str(), value); |
| 131 | + } |
49 | 132 | } |
0 commit comments