otel_bootstrap/
grpc_middleware.rs1use opentelemetry::{
33 global,
34 propagation::{Extractor, Injector},
35 trace::{SpanKind, Status, TraceContextExt, Tracer},
36};
37use std::{
38 future::Future,
39 pin::Pin,
40 task::{self, Poll},
41};
42use tonic::body::Body;
43use tower::{Layer, Service};
44
45#[derive(Clone, Debug, Default)]
51pub struct GrpcClientTraceLayer;
52
53impl<S> Layer<S> for GrpcClientTraceLayer {
54 type Service = GrpcClientTraceService<S>;
55
56 fn layer(&self, inner: S) -> Self::Service {
57 GrpcClientTraceService { inner }
58 }
59}
60
61#[derive(Clone, Debug)]
63pub struct GrpcClientTraceService<S> {
64 inner: S,
65}
66
67impl<S> Service<http::Request<Body>> for GrpcClientTraceService<S>
68where
69 S: Service<http::Request<Body>> + Clone + Send + 'static,
70 S::Future: Send + 'static,
71{
72 type Response = S::Response;
73 type Error = S::Error;
74 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
75
76 fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
77 self.inner.poll_ready(cx)
78 }
79
80 fn call(&mut self, mut req: http::Request<Body>) -> Self::Future {
81 let path = req.uri().path().to_string();
82
83 let tracer = global::tracer("otel-bootstrap");
84 let span = tracer
85 .span_builder(path)
86 .with_kind(SpanKind::Client)
87 .start(&tracer);
88 let cx = opentelemetry::Context::current_with_span(span);
89
90 global::get_text_map_propagator(|propagator| {
91 propagator.inject_context(&cx, &mut MetadataInjector(req.headers_mut()));
92 });
93
94 let mut inner = self.inner.clone();
95 Box::pin(async move { inner.call(req).await })
96 }
97}
98
99#[derive(Clone, Debug, Default)]
105pub struct GrpcServerTraceLayer;
106
107impl<S> Layer<S> for GrpcServerTraceLayer {
108 type Service = GrpcServerTraceService<S>;
109
110 fn layer(&self, inner: S) -> Self::Service {
111 GrpcServerTraceService { inner }
112 }
113}
114
115#[derive(Clone, Debug)]
117pub struct GrpcServerTraceService<S> {
118 inner: S,
119}
120
121impl<S> Service<http::Request<Body>> for GrpcServerTraceService<S>
122where
123 S: Service<http::Request<Body>, Response = http::Response<Body>> + Clone + Send + 'static,
124 S::Future: Send + 'static,
125 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
126{
127 type Response = http::Response<Body>;
128 type Error = S::Error;
129 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
130
131 fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
132 self.inner.poll_ready(cx)
133 }
134
135 fn call(&mut self, req: http::Request<Body>) -> Self::Future {
136 let path = req.uri().path().to_string();
137
138 let parent_cx = global::get_text_map_propagator(|propagator| {
139 propagator.extract(&MetadataExtractor(req.headers()))
140 });
141
142 let tracer = global::tracer("otel-bootstrap");
143 let span = tracer
144 .span_builder(path)
145 .with_kind(SpanKind::Server)
146 .start_with_context(&tracer, &parent_cx);
147 let cx = parent_cx.with_span(span);
148
149 let mut inner = self.inner.clone();
150 Box::pin(async move {
151 let result = inner.call(req).await;
152
153 match &result {
154 Ok(resp) => {
155 if resp.status().is_server_error() {
161 cx.span().set_status(Status::Error {
162 description: resp.status().canonical_reason().unwrap_or("").into(),
163 });
164 }
165 }
166 Err(_) => {
167 cx.span().set_status(Status::Error {
168 description: "transport error".into(),
169 });
170 }
171 }
172
173 result
174 })
175 }
176}
177
178struct MetadataExtractor<'a>(&'a http::HeaderMap);
180
181impl Extractor for MetadataExtractor<'_> {
182 fn get(&self, key: &str) -> Option<&str> {
183 self.0.get(key).and_then(|v| v.to_str().ok())
184 }
185
186 fn keys(&self) -> Vec<&str> {
187 self.0.keys().map(http::HeaderName::as_str).collect()
188 }
189}
190
191struct MetadataInjector<'a>(&'a mut http::HeaderMap);
193
194impl Injector for MetadataInjector<'_> {
195 fn set(&mut self, key: &str, value: String) {
196 if let (Ok(name), Ok(val)) = (
197 http::HeaderName::from_bytes(key.as_bytes()),
198 http::HeaderValue::from_str(&value),
199 ) {
200 self.0.insert(name, val);
201 }
202 }
203}