Skip to main content

miden_node_utils/tracing/
grpc.rs

1use tracing::field;
2
3use crate::tracing::OpenTelemetrySpanExt;
4
5/// Returns a [`trace_fn`](tonic::transport::server::Server) implementation for gRPC requests
6/// which adds open-telemetry information to the span.
7///
8/// Creates an `info` span following the open-telemetry standard: `{service}/{method}`.
9/// The span name is dynamically set using the HTTP path via the `otel.name` field.
10/// Additionally also pulls in remote tracing context which allows the server trace to be connected
11/// to the client's origin trace.
12#[track_caller]
13pub fn grpc_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
14    // A gRPC request's path ends with `../<service>/<method>`.
15    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    // Create a span with a generic, static name. Fields to be recorded after needs to be
21    // initialized as empty since otherwise the assignment will have no effect.
22    let span = match method {
23        "SyncState" | "SyncNullifiers" => tracing::debug_span!(
24            "rpc",
25            otel.name = field::Empty,
26            rpc.service = service,
27            rpc.method = method
28        ),
29        _ => tracing::info_span!(
30            "rpc",
31            otel.name = field::Empty,
32            rpc.service = service,
33            rpc.method = method
34        ),
35    };
36
37    // Set the span name via otel.name
38    let otel_name = format!("{service}/{method}");
39    span.record("otel.name", otel_name);
40
41    // Pull the open-telemetry parent context using the HTTP extractor
42    let otel_ctx = opentelemetry::global::get_text_map_propagator(|propagator| {
43        propagator.extract(&MetadataExtractor(&tonic::metadata::MetadataMap::from_headers(
44            request.headers().clone(),
45        )))
46    });
47    let _ = tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, otel_ctx);
48
49    // Adds various network attributes to the span, including remote address and port.
50    //
51    // See [server attributes](https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#server-attributes).
52
53    // Set HTTP attributes.
54    span.set_attribute("rpc.system", "grpc");
55    if let Some(host) = request.uri().host() {
56        span.set_attribute("server.address", host);
57    }
58    if let Some(host_port) = request.uri().port() {
59        span.set_attribute("server.port", host_port.as_u16());
60    }
61    let remote_addr = request
62        .extensions()
63        .get::<tonic::transport::server::TcpConnectInfo>()
64        .and_then(tonic::transport::server::TcpConnectInfo::remote_addr);
65    if let Some(addr) = remote_addr {
66        span.set_attribute("client.address", addr.ip());
67        span.set_attribute("client.port", addr.port());
68        span.set_attribute("network.peer.address", addr.ip());
69        span.set_attribute("network.peer.port", addr.port());
70        span.set_attribute("network.transport", "tcp");
71        match addr.ip() {
72            std::net::IpAddr::V4(_) => span.set_attribute("network.type", "ipv4"),
73            std::net::IpAddr::V6(_) => span.set_attribute("network.type", "ipv6"),
74        }
75    }
76
77    span
78}
79
80/// Injects open-telemetry remote context into traces.
81#[derive(Copy, Clone)]
82pub struct OtelInterceptor;
83
84impl tonic::service::Interceptor for OtelInterceptor {
85    fn call(
86        &mut self,
87        mut request: tonic::Request<()>,
88    ) -> Result<tonic::Request<()>, tonic::Status> {
89        use tracing_opentelemetry::OpenTelemetrySpanExt;
90        let ctx = tracing::Span::current().context();
91        opentelemetry::global::get_text_map_propagator(|propagator| {
92            propagator.inject_context(&ctx, &mut MetadataInjector(request.metadata_mut()));
93        });
94
95        Ok(request)
96    }
97}
98
99struct MetadataExtractor<'a>(&'a tonic::metadata::MetadataMap);
100impl opentelemetry::propagation::Extractor for MetadataExtractor<'_> {
101    /// Get a value for a key from the `MetadataMap`.  If the value can't be converted to &str,
102    /// returns None
103    fn get(&self, key: &str) -> Option<&str> {
104        self.0.get(key).and_then(|metadata| metadata.to_str().ok())
105    }
106
107    /// Collect all the keys from the `MetadataMap`.
108    fn keys(&self) -> Vec<&str> {
109        self.0
110            .keys()
111            .map(|key| match key {
112                tonic::metadata::KeyRef::Ascii(v) => v.as_str(),
113                tonic::metadata::KeyRef::Binary(v) => v.as_str(),
114            })
115            .collect::<Vec<_>>()
116    }
117}
118
119struct MetadataInjector<'a>(&'a mut tonic::metadata::MetadataMap);
120impl opentelemetry::propagation::Injector for MetadataInjector<'_> {
121    /// Set a key and value in the `MetadataMap`.  Does nothing if the key or value are not valid
122    /// inputs
123    fn set(&mut self, key: &str, value: String) {
124        if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes())
125            && let Ok(val) = tonic::metadata::MetadataValue::try_from(&value)
126        {
127            self.0.insert(key, val);
128        }
129    }
130}