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 clone = self.inner.clone();
103 let mut inner = std::mem::replace(&mut self.inner, clone);
104 Box::pin(async move { inner.call(req).await })
105 }
106}
107
108#[derive(Clone, Debug, Default)]
114pub struct GrpcServerTraceLayer;
115
116impl<S> Layer<S> for GrpcServerTraceLayer {
117 type Service = GrpcServerTraceService<S>;
118
119 fn layer(&self, inner: S) -> Self::Service {
120 GrpcServerTraceService { inner }
121 }
122}
123
124#[derive(Clone, Debug)]
126pub struct GrpcServerTraceService<S> {
127 inner: S,
128}
129
130impl<S> Service<http::Request<Body>> for GrpcServerTraceService<S>
131where
132 S: Service<http::Request<Body>, Response = http::Response<Body>> + Clone + Send + 'static,
133 S::Future: Send + 'static,
134 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
135{
136 type Response = http::Response<Body>;
137 type Error = S::Error;
138 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
139
140 fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
141 self.inner.poll_ready(cx)
142 }
143
144 fn call(&mut self, req: http::Request<Body>) -> Self::Future {
145 let path = req.uri().path().to_string();
146
147 let parent_cx = global::get_text_map_propagator(|propagator| {
148 propagator.extract(&MetadataExtractor(req.headers()))
149 });
150
151 let tracer = global::tracer("otel-bootstrap");
152 let span = tracer
153 .span_builder(path)
154 .with_kind(SpanKind::Server)
155 .start_with_context(&tracer, &parent_cx);
156 let cx = parent_cx.with_span(span);
157
158 let clone = self.inner.clone();
161 let mut inner = std::mem::replace(&mut self.inner, clone);
162 Box::pin(async move {
163 let result = inner.call(req).await;
164
165 match &result {
166 Ok(resp) => {
167 if resp.status().is_server_error() {
173 cx.span().set_status(Status::Error {
174 description: resp.status().canonical_reason().unwrap_or("").into(),
175 });
176 }
177 }
178 Err(_) => {
179 cx.span().set_status(Status::Error {
180 description: "transport error".into(),
181 });
182 }
183 }
184
185 result
186 })
187 }
188}
189
190struct MetadataExtractor<'a>(&'a http::HeaderMap);
192
193impl Extractor for MetadataExtractor<'_> {
194 fn get(&self, key: &str) -> Option<&str> {
195 self.0.get(key).and_then(|v| v.to_str().ok())
196 }
197
198 fn keys(&self) -> Vec<&str> {
199 self.0.keys().map(http::HeaderName::as_str).collect()
200 }
201}
202
203struct MetadataInjector<'a>(&'a mut http::HeaderMap);
205
206impl Injector for MetadataInjector<'_> {
207 fn set(&mut self, key: &str, value: String) {
208 if let (Ok(name), Ok(val)) = (
209 http::HeaderName::from_bytes(key.as_bytes()),
210 http::HeaderValue::from_str(&value),
211 ) {
212 self.0.insert(name, val);
213 }
214 }
215}