miden_node_utils/tracing/
grpc.rs1use http::header::HeaderName;
2use tower_governor::key_extractor::{KeyExtractor, SmartIpKeyExtractor};
3use tracing::field;
4
5#[track_caller]
13pub fn grpc_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
14 let mut path_segments = request.uri().path().rsplit('/');
16
17 let method = path_segments.next().unwrap_or_default();
18 let service = path_segments.next().unwrap_or_default();
19
20 let span = tracing::info_span!(
23 "rpc",
24 otel.name = field::Empty,
25 rpc.service = service,
26 rpc.method = method,
27 rpc.system = field::Empty,
28 rpc.request.size = field::Empty,
29 rpc.response.size = field::Empty,
30 server.address = field::Empty,
31 server.port = field::Empty,
32 client.address = field::Empty,
33 client.port = field::Empty,
34 network.peer.address = field::Empty,
35 network.peer.port = field::Empty,
36 network.transport = field::Empty,
37 network.type = field::Empty,
38 );
39
40 let otel_name = format!("{service}/{method}");
42 span.record("otel.name", otel_name);
43
44 let otel_ctx = opentelemetry::global::get_text_map_propagator(|propagator| {
46 propagator.extract(&MetadataExtractor(&tonic::metadata::MetadataMap::from_headers(
47 request.headers().clone(),
48 )))
49 });
50 let _ = tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, otel_ctx);
51
52 span.record("rpc.system", "grpc");
58 if let Some(host) = request.uri().host() {
59 span.record("server.address", host);
60 }
61 if let Some(host_port) = request.uri().port() {
62 span.record("server.port", host_port.as_u16());
63 }
64 let remote_addr = request
65 .extensions()
66 .get::<tonic::transport::server::TcpConnectInfo>()
67 .and_then(tonic::transport::server::TcpConnectInfo::remote_addr);
68
69 if let Ok(ip) = SmartIpKeyExtractor.extract(request) {
72 span.record("client.address", field::display(ip));
73 } else if let Some(addr) = remote_addr {
74 span.record("client.address", field::display(addr.ip()));
75 span.record("client.port", addr.port());
76 }
77
78 if let Some(addr) = remote_addr {
79 span.record("network.peer.address", field::display(addr.ip()));
80 span.record("network.peer.port", addr.port());
81 span.record("network.transport", "tcp");
82 match addr.ip() {
83 std::net::IpAddr::V4(_) => span.record("network.type", "ipv4"),
84 std::net::IpAddr::V6(_) => span.record("network.type", "ipv6"),
85 };
86 }
87
88 for header in [
89 http::header::ACCEPT,
90 http::header::ORIGIN,
91 http::header::USER_AGENT,
92 http::header::FORWARDED,
93 HeaderName::from_static("x-forwarded-for"),
94 HeaderName::from_static("x-real-ip"),
95 HeaderName::from_static("x-request-id"),
96 ] {
97 if let Some(value) = request.headers().get(&header) {
98 if let Ok(value) = value.to_str() {
99 tracing_opentelemetry::OpenTelemetrySpanExt::set_attribute(
100 &span,
101 format!("http.request.header.{header}"),
102 value.to_owned(),
103 );
104 }
105 }
106 }
107
108 span
109}
110
111#[derive(Copy, Clone)]
113pub struct OtelInterceptor;
114
115impl tonic::service::Interceptor for OtelInterceptor {
116 fn call(
117 &mut self,
118 mut request: tonic::Request<()>,
119 ) -> Result<tonic::Request<()>, tonic::Status> {
120 use tracing_opentelemetry::OpenTelemetrySpanExt;
121 let ctx = tracing::Span::current().context();
122 opentelemetry::global::get_text_map_propagator(|propagator| {
123 propagator.inject_context(&ctx, &mut MetadataInjector(request.metadata_mut()));
124 });
125
126 Ok(request)
127 }
128}
129
130struct MetadataExtractor<'a>(&'a tonic::metadata::MetadataMap);
131impl opentelemetry::propagation::Extractor for MetadataExtractor<'_> {
132 fn get(&self, key: &str) -> Option<&str> {
135 self.0.get(key).and_then(|metadata| metadata.to_str().ok())
136 }
137
138 fn keys(&self) -> Vec<&str> {
140 self.0
141 .keys()
142 .map(|key| match key {
143 tonic::metadata::KeyRef::Ascii(v) => v.as_str(),
144 tonic::metadata::KeyRef::Binary(v) => v.as_str(),
145 })
146 .collect::<Vec<_>>()
147 }
148}
149
150struct MetadataInjector<'a>(&'a mut tonic::metadata::MetadataMap);
151impl opentelemetry::propagation::Injector for MetadataInjector<'_> {
152 fn set(&mut self, key: &str, value: String) {
155 if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes())
156 && let Ok(val) = tonic::metadata::MetadataValue::try_from(&value)
157 {
158 self.0.insert(key, val);
159 }
160 }
161}