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