Skip to content

Commit a08b2e1

Browse files
committed
feat: enhance otel implementation by setting more span info on request receipt
1 parent 16a42a2 commit a08b2e1

File tree

10 files changed

+279
-80
lines changed

10 files changed

+279
-80
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "Simple, modern, ergonomic JSON-RPC 2.0 router built with tower an
55
keywords = ["json-rpc", "jsonrpc", "json"]
66
categories = ["web-programming::http-server", "web-programming::websocket"]
77

8-
version = "0.3.4"
8+
version = "0.4.0"
99
edition = "2021"
1010
rust-version = "1.81"
1111
authors = ["init4", "James Prestwich"]

src/axum.rs

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
use crate::{
22
types::{InboundData, Response},
3-
HandlerCtx, TaskSet,
3+
HandlerCtx, TaskSet, TracingInfo,
44
};
55
use axum::{
66
extract::FromRequest,
77
http::{header, HeaderValue},
88
response::IntoResponse,
99
};
1010
use bytes::Bytes;
11-
use std::{future::Future, pin::Pin};
11+
use std::{
12+
future::Future,
13+
pin::Pin,
14+
sync::{
15+
atomic::{AtomicU32, Ordering},
16+
Arc,
17+
},
18+
};
1219
use tokio::runtime::Handle;
20+
use tracing::{debug, debug_span};
1321

1422
/// A wrapper around an [`Router`] that implements the
1523
/// [`axum::handler::Handler`] trait. This struct is an implementation detail
@@ -21,14 +29,22 @@ use tokio::runtime::Handle;
2129
#[derive(Debug, Clone)]
2230
pub(crate) struct IntoAxum<S> {
2331
pub(crate) router: crate::Router<S>,
32+
2433
pub(crate) task_set: TaskSet,
34+
35+
/// Counter for OTEL messages received.
36+
pub(crate) rx_msg_id: Arc<AtomicU32>,
37+
/// Counter for OTEL messages sent.
38+
pub(crate) tx_msg_id: Arc<AtomicU32>,
2539
}
2640

2741
impl<S> From<crate::Router<S>> for IntoAxum<S> {
2842
fn from(router: crate::Router<S>) -> Self {
2943
Self {
3044
router,
3145
task_set: Default::default(),
46+
rx_msg_id: Arc::new(AtomicU32::new(0)),
47+
tx_msg_id: Arc::new(AtomicU32::new(0)),
3248
}
3349
}
3450
}
@@ -39,12 +55,35 @@ impl<S> IntoAxum<S> {
3955
Self {
4056
router,
4157
task_set: handle.into(),
58+
rx_msg_id: Arc::new(AtomicU32::new(0)),
59+
tx_msg_id: Arc::new(AtomicU32::new(0)),
4260
}
4361
}
62+
}
4463

45-
/// Get a new context, built from the task set.
64+
impl<S> IntoAxum<S>
65+
where
66+
S: Clone + Send + Sync + 'static,
67+
{
4668
fn ctx(&self) -> HandlerCtx {
47-
self.task_set.clone().into()
69+
let request_span = debug_span!(
70+
"ajj.IntoAxum::call",
71+
"otel.kind" = "server",
72+
"rpc.system" = "jsonrpc",
73+
"rpc.jsonrpc.version" = "2.0",
74+
"rpc.service" = self.router.service_name(),
75+
notifications_enabled = false,
76+
params = tracing::field::Empty
77+
);
78+
79+
HandlerCtx::new(
80+
None,
81+
self.task_set.clone(),
82+
TracingInfo {
83+
service: self.router.service_name(),
84+
request_span,
85+
},
86+
)
4887
}
4988
}
5089

@@ -56,25 +95,44 @@ where
5695

5796
fn call(self, req: axum::extract::Request, state: S) -> Self::Future {
5897
Box::pin(async move {
98+
let ctx = self.ctx();
99+
59100
let Ok(bytes) = Bytes::from_request(req, &state).await else {
60101
return Box::<str>::from(Response::parse_error()).into_response();
61102
};
62103

63-
// If the inbound data is not currently parsable, we
64-
// send an empty one it to the router, as the router enforces
65-
// the specification.
66-
let req = InboundData::try_from(bytes).unwrap_or_default();
104+
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md#message-event
105+
let req = ctx.span().in_scope(|| {
106+
debug!(
107+
"rpc.message.id" = self.rx_msg_id.fetch_add(1, Ordering::Relaxed),
108+
"rpc.message.type" = "received",
109+
"rpc.message.uncompressed_size" = bytes.len(),
110+
"Received request"
111+
);
112+
113+
// If the inbound data is not currently parsable, we
114+
// send an empty one it to the router, as the router enforces
115+
// the specification.
116+
InboundData::try_from(bytes).unwrap_or_default()
117+
});
67118

68-
if let Some(response) = self
69-
.router
70-
.call_batch_with_state(self.ctx(), req, state)
71-
.await
72-
{
119+
let span = ctx.span().clone();
120+
if let Some(response) = self.router.call_batch_with_state(ctx, req, state).await {
73121
let headers = [(
74122
header::CONTENT_TYPE,
75123
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
76124
)];
125+
77126
let body = Box::<str>::from(response);
127+
128+
span.in_scope(|| {
129+
debug!(
130+
"rpc.message.id" = self.tx_msg_id.fetch_add(1, Ordering::Relaxed),
131+
"rpc.message.type" = "received",
132+
"Received request"
133+
);
134+
});
135+
78136
(headers, body).into_response()
79137
} else {
80138
().into_response()

src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ pub use pubsub::ReadJsonStream;
171171
mod routes;
172172
pub use routes::{
173173
BatchFuture, Handler, HandlerArgs, HandlerCtx, NotifyError, Params, RouteFuture, State,
174+
TracingInfo,
174175
};
175176
pub(crate) use routes::{BoxedIntoRoute, ErasedIntoRoute, Method, Route};
176177

@@ -206,7 +207,8 @@ pub(crate) mod test_utils {
206207
mod test {
207208

208209
use crate::{
209-
router::RouterInner, routes::HandlerArgs, test_utils::assert_rv_eq, ResponsePayload,
210+
router::RouterInner, routes::HandlerArgs, test_utils::assert_rv_eq, HandlerCtx,
211+
ResponsePayload,
210212
};
211213
use bytes::Bytes;
212214
use serde_json::value::RawValue;
@@ -232,7 +234,7 @@ mod test {
232234
let res = router
233235
.call_with_state(
234236
HandlerArgs {
235-
ctx: Default::default(),
237+
ctx: HandlerCtx::mock(),
236238
req: req.try_into().unwrap(),
237239
},
238240
(),
@@ -251,7 +253,7 @@ mod test {
251253
let res2 = router
252254
.call_with_state(
253255
HandlerArgs {
254-
ctx: Default::default(),
256+
ctx: HandlerCtx::mock(),
255257
req: req2.try_into().unwrap(),
256258
},
257259
(),

src/pubsub/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ mod ipc;
9595
pub use ipc::ReadJsonStream;
9696

9797
mod shared;
98+
pub(crate) use shared::WriteItem;
9899
pub use shared::{ConnectionId, DEFAULT_NOTIFICATION_BUFFER_PER_CLIENT};
99100

100101
mod shutdown;

src/pubsub/shared.rs

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use crate::{
22
pubsub::{In, JsonSink, Listener, Out},
33
types::InboundData,
4-
HandlerCtx, TaskSet,
4+
HandlerCtx, TaskSet, TracingInfo,
55
};
66
use core::fmt;
77
use serde_json::value::RawValue;
8-
use std::sync::{atomic::AtomicU64, Arc};
8+
use std::sync::{
9+
atomic::{AtomicU32, AtomicU64, Ordering},
10+
Arc,
11+
};
912
use tokio::{pin, runtime::Handle, select, sync::mpsc, task::JoinHandle};
1013
use tokio_stream::StreamExt;
1114
use tokio_util::sync::WaitForCancellationFutureOwned;
@@ -105,8 +108,7 @@ impl ConnectionManager {
105108

106109
/// Increment the connection ID counter and return an unused ID.
107110
fn next_id(&self) -> ConnectionId {
108-
self.next_id
109-
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
111+
self.next_id.fetch_add(1, Ordering::Relaxed)
110112
}
111113

112114
/// Get a clone of the router.
@@ -131,13 +133,15 @@ impl ConnectionManager {
131133
write_task: tx,
132134
requests,
133135
tasks: tasks.clone(),
136+
rx_msg_id: Arc::new(AtomicU32::new(0)),
134137
};
135138

136139
let wt = WriteTask {
137140
tasks,
138141
conn_id,
139-
json: rx,
142+
items: rx,
140143
connection,
144+
tx_msg_id: Arc::new(AtomicU32::new(0)),
141145
};
142146

143147
(rt, wt)
@@ -168,11 +172,14 @@ struct RouteTask<T: crate::pubsub::Listener> {
168172
/// Connection ID for the connection serviced by this task.
169173
pub(crate) conn_id: ConnectionId,
170174
/// Sender to the write task.
171-
pub(crate) write_task: mpsc::Sender<Box<RawValue>>,
175+
pub(crate) write_task: mpsc::Sender<WriteItem>,
172176
/// Stream of requests.
173177
pub(crate) requests: In<T>,
174178
/// The task set for this connection
175179
pub(crate) tasks: TaskSet,
180+
181+
/// Counter for OTEL messages received.
182+
pub(crate) rx_msg_id: Arc<AtomicU32>,
176183
}
177184

178185
impl<T: crate::pubsub::Listener> fmt::Debug for RouteTask<T> {
@@ -199,6 +206,7 @@ where
199206
mut requests,
200207
write_task,
201208
tasks,
209+
rx_msg_id,
202210
..
203211
} = self;
204212

@@ -224,6 +232,8 @@ where
224232
break;
225233
};
226234

235+
let item_bytes = item.len();
236+
227237
// If the inbound data is not currently parsable, we
228238
// send an empty one it to the router, as the router
229239
// enforces the specification.
@@ -234,16 +244,38 @@ where
234244
// if the client stops accepting responses, we do not keep
235245
// handling inbound requests.
236246
let Ok(permit) = write_task.clone().reserve_owned().await else {
237-
tracing::error!("write task dropped while waiting for permit");
247+
error!("write task dropped while waiting for permit");
238248
break;
239249
};
240250

251+
let tracing = TracingInfo { service: router.service_name(), request_span: debug_span!(
252+
parent: None,
253+
"ajj.pubsub.RouteTask::call",
254+
"otel.kind" = "server",
255+
"rpc.system" = "jsonrpc",
256+
"rpc.jsonrpc.version" = "2.0",
257+
"rpc.service" = router.service_name(),
258+
notifications_enabled = true,
259+
params = tracing::field::Empty
260+
) };
261+
241262
let ctx =
242263
HandlerCtx::new(
243264
Some(write_task.clone()),
244265
children.clone(),
266+
tracing,
245267
);
246268

269+
let span = ctx.span().clone();
270+
span.in_scope(|| {
271+
debug!(
272+
"rpc.message.id" = rx_msg_id.fetch_add(1, Ordering::Relaxed),
273+
"rpc.message.type" = "received",
274+
"rpc.message.uncompressed_size" = item_bytes,
275+
"Received request"
276+
);
277+
});
278+
247279
// Run the future in a new task.
248280
let fut = router.handle_request_batch(ctx, reqs);
249281

@@ -252,9 +284,9 @@ where
252284
// Send the response to the write task.
253285
// we don't care if the receiver has gone away,
254286
// as the task is done regardless.
255-
if let Some(rv) = fut.await {
287+
if let Some(json) = fut.await {
256288
let _ = permit.send(
257-
rv
289+
WriteItem { span, json }
258290
);
259291
}
260292
}
@@ -275,6 +307,13 @@ where
275307
}
276308
}
277309

310+
/// An item to be written to an outbound JSON pubsub stream.
311+
#[derive(Debug, Clone)]
312+
pub(crate) struct WriteItem {
313+
pub(crate) span: tracing::Span,
314+
pub(crate) json: Box<RawValue>,
315+
}
316+
278317
/// The Write Task is responsible for writing JSON to the outbound connection.
279318
struct WriteTask<T: Listener> {
280319
/// Task set
@@ -287,10 +326,13 @@ struct WriteTask<T: Listener> {
287326
///
288327
/// Dropping this channel will cause the associated [`RouteTask`] to
289328
/// shutdown.
290-
pub(crate) json: mpsc::Receiver<Box<RawValue>>,
329+
pub(crate) items: mpsc::Receiver<WriteItem>,
291330

292331
/// Outbound connections.
293332
pub(crate) connection: Out<T>,
333+
334+
/// Counter for OTEL messages sent.
335+
pub(crate) tx_msg_id: Arc<AtomicU32>,
294336
}
295337

296338
impl<T: Listener> WriteTask<T> {
@@ -305,8 +347,9 @@ impl<T: Listener> WriteTask<T> {
305347
pub(crate) async fn task_future(self) {
306348
let WriteTask {
307349
tasks,
308-
mut json,
350+
mut items,
309351
mut connection,
352+
tx_msg_id,
310353
..
311354
} = self;
312355

@@ -318,12 +361,20 @@ impl<T: Listener> WriteTask<T> {
318361
debug!("Shutdown signal received");
319362
break;
320363
}
321-
json = json.recv() => {
322-
let Some(json) = json else {
364+
item = items.recv() => {
365+
let Some(WriteItem { span, json }) = item else {
323366
tracing::error!("Json stream has closed");
324367
break;
325368
};
326-
let span = debug_span!("WriteTask", conn_id = self.conn_id);
369+
span.record("conn_id", self.conn_id);
370+
span.in_scope(|| {
371+
debug!(
372+
"rpc.message.id" = tx_msg_id.fetch_add(1, Ordering::Relaxed),
373+
"rpc.message.type" = "sent",
374+
"Sending response"
375+
);
376+
});
377+
327378
if let Err(err) = connection.send_json(json).instrument(span).await {
328379
debug!(%err, conn_id = self.conn_id, "Failed to send json");
329380
break;

0 commit comments

Comments
 (0)