miden_node_utils/tracing/
grpc.rs

1/// Creates a [`tracing::Span`] based on RPC service and method name.
2macro_rules! rpc_span {
3    ($service:literal, $method:literal) => {
4        tracing::info_span!(
5            concat!($service, "/", $method),
6            rpc.service = $service,
7            rpc.method = $method
8        )
9    };
10}
11
12/// A [`trace_fn`](tonic::transport::server::Server) implementation for the block producer which
13/// adds open-telemetry information to the span.
14///
15/// Creates an `info` span following the open-telemetry standard: `block-producer.rpc/{method}`.
16/// Additionally also pulls in remote tracing context which allows the server trace to be connected
17/// to the client's origin trace.
18pub fn block_producer_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
19    let span = if let Some("SubmitProvenTransaction") = request.uri().path().rsplit('/').next() {
20        rpc_span!("block-producer.rpc", "SubmitProvenTransaction")
21    } else {
22        rpc_span!("block-producer.rpc", "Unknown")
23    };
24
25    add_otel_span_attributes(span, request)
26}
27
28/// A [`trace_fn`](tonic::transport::server::Server) implementation for the store which adds
29/// open-telemetry information to the span.
30///
31/// Creates an `info` span following the open-telemetry standard: `store.rpc/{method}`. Additionally
32/// also pulls in remote tracing context which allows the server trace to be connected to the
33/// client's origin trace.
34pub fn store_trace_fn<T>(request: &http::Request<T>) -> tracing::Span {
35    let span = match request.uri().path().rsplit('/').next() {
36        Some("ApplyBlock") => rpc_span!("store.rpc", "ApplyBlock"),
37        Some("CheckNullifiers") => rpc_span!("store.rpc", "CheckNullifiers"),
38        Some("CheckNullifiersByPrefix") => rpc_span!("store.rpc", "CheckNullifiersByPrefix"),
39        Some("GetAccountDetails") => rpc_span!("store.rpc", "GetAccountDetails"),
40        Some("GetAccountProofs") => rpc_span!("store.rpc", "GetAccountProofs"),
41        Some("GetAccountStateDelta") => rpc_span!("store.rpc", "GetAccountStateDelta"),
42        Some("GetBlockByNumber") => rpc_span!("store.rpc", "GetBlockByNumber"),
43        Some("GetBlockHeaderByNumber") => rpc_span!("store.rpc", "GetBlockHeaderByNumber"),
44        Some("GetBlockInputs") => rpc_span!("store.rpc", "GetBlockInputs"),
45        Some("GetBatchInputs") => rpc_span!("store.rpc", "GetBatchInputs"),
46        Some("GetNotesById") => rpc_span!("store.rpc", "GetNotesById"),
47        Some("GetTransactionInputs") => rpc_span!("store.rpc", "GetTransactionInputs"),
48        Some("SyncNotes") => rpc_span!("store.rpc", "SyncNotes"),
49        Some("SyncState") => rpc_span!("store.rpc", "SyncState"),
50        _ => rpc_span!("store.rpc", "Unknown"),
51    };
52
53    add_otel_span_attributes(span, request)
54}
55
56/// Adds remote tracing context to the span.
57///
58/// Could be expanded in the future by adding in more open-telemetry properties.
59fn add_otel_span_attributes<T>(span: tracing::Span, request: &http::Request<T>) -> tracing::Span {
60    use super::OpenTelemetrySpanExt;
61    // Pull the open-telemetry parent context using the HTTP extractor. We could make a more
62    // generic gRPC extractor by utilising the gRPC metadata. However that
63    //     (a) requires cloning headers,
64    //     (b) we would have to write this ourselves, and
65    //     (c) gRPC metadata is transferred using HTTP headers in any case.
66    let otel_ctx = opentelemetry::global::get_text_map_propagator(|propagator| {
67        propagator.extract(&MetadataExtractor(&tonic::metadata::MetadataMap::from_headers(
68            request.headers().clone(),
69        )))
70    });
71    tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, otel_ctx);
72
73    // Set HTTP attributes.
74    // See https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#server-attributes.
75    span.set_attribute("rpc.system", "grpc");
76    if let Some(host) = request.uri().host() {
77        span.set_attribute("server.address", host);
78    }
79    if let Some(host_port) = request.uri().port() {
80        span.set_attribute("server.port", host_port.as_u16());
81    }
82    let remote_addr = request
83        .extensions()
84        .get::<tonic::transport::server::TcpConnectInfo>()
85        .and_then(tonic::transport::server::TcpConnectInfo::remote_addr);
86    if let Some(addr) = remote_addr {
87        span.set_attribute("client.address", addr.ip());
88        span.set_attribute("client.port", addr.port());
89        span.set_attribute("network.peer.address", addr.ip());
90        span.set_attribute("network.peer.port", addr.port());
91        span.set_attribute("network.transport", "tcp");
92        match addr.ip() {
93            std::net::IpAddr::V4(_) => span.set_attribute("network.type", "ipv4"),
94            std::net::IpAddr::V6(_) => span.set_attribute("network.type", "ipv6"),
95        }
96    }
97
98    span
99}
100
101/// Injects open-telemetry remote context into traces.
102#[derive(Copy, Clone)]
103pub struct OtelInterceptor;
104
105impl tonic::service::Interceptor for OtelInterceptor {
106    fn call(
107        &mut self,
108        mut request: tonic::Request<()>,
109    ) -> Result<tonic::Request<()>, tonic::Status> {
110        use tracing_opentelemetry::OpenTelemetrySpanExt;
111        let ctx = tracing::Span::current().context();
112        opentelemetry::global::get_text_map_propagator(|propagator| {
113            propagator.inject_context(&ctx, &mut MetadataInjector(request.metadata_mut()));
114        });
115
116        Ok(request)
117    }
118}
119
120struct MetadataExtractor<'a>(&'a tonic::metadata::MetadataMap);
121impl opentelemetry::propagation::Extractor for MetadataExtractor<'_> {
122    /// Get a value for a key from the `MetadataMap`.  If the value can't be converted to &str,
123    /// returns None
124    fn get(&self, key: &str) -> Option<&str> {
125        self.0.get(key).and_then(|metadata| metadata.to_str().ok())
126    }
127
128    /// Collect all the keys from the `MetadataMap`.
129    fn keys(&self) -> Vec<&str> {
130        self.0
131            .keys()
132            .map(|key| match key {
133                tonic::metadata::KeyRef::Ascii(v) => v.as_str(),
134                tonic::metadata::KeyRef::Binary(v) => v.as_str(),
135            })
136            .collect::<Vec<_>>()
137    }
138}
139
140struct MetadataInjector<'a>(&'a mut tonic::metadata::MetadataMap);
141impl opentelemetry::propagation::Injector for MetadataInjector<'_> {
142    /// Set a key and value in the `MetadataMap`.  Does nothing if the key or value are not valid
143    /// inputs
144    fn set(&mut self, key: &str, value: String) {
145        if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes()) {
146            if let Ok(val) = tonic::metadata::MetadataValue::try_from(&value) {
147                self.0.insert(key, val);
148            }
149        }
150    }
151}