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