Skip to content

Commit e0f2ff2

Browse files
Preslav LeConvex, Inc.
Preslav Le
authored and
Convex, Inc.
committed
Add request tracing middleware. (#24427)
* Add request_id extractor. * Set the request_id to sentry scope via middleware. * Enable tracing based on middleware. GitOrigin-RevId: 8d891352864a3fc599a3ebdcd59fad0d83b15d83
1 parent aa99e12 commit e0f2ff2

File tree

6 files changed

+97
-48
lines changed

6 files changed

+97
-48
lines changed

Diff for: crates/common/src/execution_context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ impl RequestId {
100100
hash.truncate(16);
101101
Self(hash)
102102
}
103+
104+
pub fn as_str(&self) -> &str {
105+
self.0.as_str()
106+
}
103107
}
104108

105109
impl FromStr for RequestId {

Diff for: crates/common/src/http/mod.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ use std::{
55
future::Future,
66
net::SocketAddr,
77
pin::Pin,
8-
str::{
9-
self,
10-
},
8+
str,
119
sync::LazyLock,
1210
time::{
1311
Duration,
@@ -70,6 +68,7 @@ use http::{
7068
};
7169
use hyper::server::conn::AddrIncoming;
7270
use itertools::Itertools;
71+
use maplit::btreemap;
7372
use prometheus::TextEncoder;
7473
use sentry::integrations::tower as sentry_tower;
7574
use serde::{
@@ -94,11 +93,13 @@ use crate::{
9493
errors::report_error,
9594
knobs::HTTP_SERVER_TCP_BACKLOG,
9695
metrics::log_client_version_unsupported,
96+
minitrace_helpers::get_sampled_span,
9797
version::{
9898
ClientVersion,
9999
ClientVersionState,
100100
COMPILED_REVISION,
101101
},
102+
RequestId,
102103
};
103104

104105
pub mod extract;
@@ -707,6 +708,7 @@ async fn client_version_state_middleware(
707708
pub async fn stats_middleware<RM: RouteMapper>(
708709
State(route_metric_mapper): State<RM>,
709710
matched_path: Option<axum::extract::MatchedPath>,
711+
ExtractRequestId(request_id): ExtractRequestId,
710712
ExtractClientVersion(client_version): ExtractClientVersion,
711713
req: http::request::Request<Body>,
712714
next: axum::middleware::Next<Body>,
@@ -725,6 +727,18 @@ pub async fn stats_middleware<RM: RouteMapper>(
725727

726728
let route = route_metric_mapper.map_route(route);
727729

730+
// Configure tracing.
731+
let mut rng = rand::thread_rng();
732+
let root = get_sampled_span(
733+
route.clone(),
734+
&mut rng,
735+
btreemap!["request_id".to_owned() => request_id.to_string()],
736+
);
737+
let _guard = root.set_local_parent();
738+
739+
// Add the request_id to sentry
740+
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
741+
728742
log_http_request(
729743
&client_version_s,
730744
&route,
@@ -804,6 +818,51 @@ where
804818
}
805819
}
806820

821+
#[allow(clippy::declare_interior_mutable_const)]
822+
pub const CONVEX_REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("convex-request-id");
823+
824+
pub struct ExtractRequestId(pub RequestId);
825+
826+
async fn request_id_from_req_parts(
827+
parts: &mut axum::http::request::Parts,
828+
) -> anyhow::Result<RequestId> {
829+
if let Some(request_id_header) = parts
830+
.headers
831+
.get(CONVEX_REQUEST_ID_HEADER)
832+
.and_then(|h| h.to_str().ok().map(|s| s.to_string()))
833+
{
834+
request_id_header.parse::<RequestId>()
835+
} else {
836+
// Generate a new request_id
837+
let request_id = RequestId::new();
838+
parts
839+
.headers
840+
.insert(CONVEX_REQUEST_ID_HEADER, request_id.as_str().parse()?);
841+
Ok(request_id)
842+
}
843+
}
844+
845+
#[async_trait]
846+
impl<S> FromRequestParts<S> for ExtractRequestId
847+
where
848+
S: Send + Sync,
849+
{
850+
type Rejection = HttpResponseError;
851+
852+
async fn from_request_parts(
853+
parts: &mut axum::http::request::Parts,
854+
_state: &S,
855+
) -> Result<Self, Self::Rejection> {
856+
let request_id = request_id_from_req_parts(parts).await.map_err(|e| {
857+
anyhow::anyhow!(ErrorMetadata::bad_request(
858+
"InvalidRequestId",
859+
e.to_string(),
860+
))
861+
})?;
862+
Ok(Self(request_id))
863+
}
864+
}
865+
807866
async fn log_middleware<B: Send>(
808867
remote_addr: Option<axum::extract::ConnectInfo<SocketAddr>>,
809868
req: http::request::Request<B>,

Diff for: crates/common/src/minitrace_helpers.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
use std::collections::BTreeMap;
1+
use std::{
2+
borrow::Cow,
3+
collections::BTreeMap,
4+
};
25

36
use minitrace::{
47
collector::SpanContext,
58
Span,
69
};
710
use rand::Rng;
811

9-
use crate::{
10-
knobs::REQUEST_TRACE_SAMPLE_PERCENT,
11-
runtime::Runtime,
12-
};
12+
use crate::knobs::REQUEST_TRACE_SAMPLE_PERCENT;
1313

1414
#[derive(Clone)]
1515
pub struct EncodedSpan(pub Option<String>);
@@ -27,14 +27,12 @@ impl EncodedSpan {
2727

2828
/// Given an instance name returns a span with the sample percentage specified
2929
/// in `knobs.rs`
30-
pub fn get_sampled_span<RT: Runtime>(
31-
request_name: String,
32-
rt: RT,
30+
pub fn get_sampled_span<R: Rng>(
31+
request_name: impl Into<Cow<'static, str>>,
32+
rng: &mut R,
3333
properties: BTreeMap<String, String>,
3434
) -> Span {
35-
let should_sample = rt
36-
.clone()
37-
.with_rng(|rng| rng.gen_bool(*REQUEST_TRACE_SAMPLE_PERCENT));
35+
let should_sample = rng.gen_bool(*REQUEST_TRACE_SAMPLE_PERCENT);
3836
match should_sample {
3937
true => Span::root(request_name, SpanContext::random()).with_properties(|| properties),
4038
false => Span::noop(),

Diff for: crates/local_backend/src/http_actions.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ use axum::{
1717
RequestExt,
1818
};
1919
use common::{
20-
http::HttpResponseError,
20+
http::{
21+
ExtractRequestId,
22+
HttpResponseError,
23+
},
2124
types::FunctionCaller,
22-
RequestId,
2325
};
2426
use futures::TryStreamExt;
2527
use http::{
@@ -87,6 +89,7 @@ impl FromRequest<LocalAppState, axum::body::Body> for ExtractHttpRequestMetadata
8789
pub async fn http_any_method(
8890
State(st): State<LocalAppState>,
8991
TryExtractIdentity(identity_result): TryExtractIdentity,
92+
ExtractRequestId(request_id): ExtractRequestId,
9093
ExtractHttpRequestMetadata(http_request_metadata): ExtractHttpRequestMetadata,
9194
) -> Result<impl IntoResponse, HttpResponseError> {
9295
// All HTTP actions run the default export of the http.js path.
@@ -97,10 +100,6 @@ pub async fn http_any_method(
97100
// to go through if the header does not seem to specify Convex auth.
98101
let identity = identity_result.unwrap_or(Identity::Unknown);
99102

100-
// TODO: Move generating request_id and configuring sentry axum middleware.
101-
let request_id = RequestId::new();
102-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
103-
104103
let udf_return = st
105104
.application
106105
.http_action_udf(

Diff for: crates/local_backend/src/public_api.rs

+7-20
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use common::{
1717
Query,
1818
},
1919
ExtractClientVersion,
20+
ExtractRequestId,
2021
HttpResponseError,
2122
},
2223
pause::PauseClient,
@@ -25,7 +26,6 @@ use common::{
2526
FunctionCaller,
2627
},
2728
version::ClientVersion,
28-
RequestId,
2929
};
3030
use errors::ErrorMetadata;
3131
use isolate::UdfArgsJson;
@@ -137,6 +137,7 @@ impl UdfResponse {
137137
/// Executes an arbitrary query/mutation/action from its name.
138138
pub async fn public_function_post(
139139
State(st): State<LocalAppState>,
140+
ExtractRequestId(request_id): ExtractRequestId,
140141
ExtractIdentity(identity): ExtractIdentity,
141142
ExtractClientVersion(client_version): ExtractClientVersion,
142143
Json(req): Json<UdfPostRequest>,
@@ -147,10 +148,6 @@ pub async fn public_function_post(
147148
return Result::Err(anyhow!(bad_admin_key_error(Some(st.instance_name.clone()))).into());
148149
}
149150

150-
// TODO: Move generating request_id and configuring sentry axum middleware.
151-
let request_id = RequestId::new();
152-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
153-
154151
let udf_path = parse_udf_path(&req.path)?;
155152
let udf_result = st
156153
.application
@@ -205,16 +202,14 @@ pub fn export_value(
205202
pub async fn public_query_get(
206203
State(st): State<LocalAppState>,
207204
Query(req): Query<UdfArgsQuery>,
205+
ExtractRequestId(request_id): ExtractRequestId,
208206
ExtractIdentity(identity): ExtractIdentity,
209207
ExtractClientVersion(client_version): ExtractClientVersion,
210208
) -> Result<impl IntoResponse, HttpResponseError> {
211209
let udf_path = req.path.parse().context(ErrorMetadata::bad_request(
212210
"InvalidConvexFunction",
213211
format!("Failed to parse Convex function path: {}", req.path),
214212
))?;
215-
// TODO: Move generating request_id and configuring sentry axum middleware.
216-
let request_id = RequestId::new();
217-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
218213
let args = req.args.into_arg_vec();
219214
let udf_return = st
220215
.application
@@ -242,6 +237,7 @@ pub async fn public_query_get(
242237

243238
pub async fn public_query_post(
244239
State(st): State<LocalAppState>,
240+
ExtractRequestId(request_id): ExtractRequestId,
245241
ExtractIdentity(identity): ExtractIdentity,
246242
ExtractClientVersion(client_version): ExtractClientVersion,
247243
Json(req): Json<UdfPostRequest>,
@@ -250,9 +246,6 @@ pub async fn public_query_post(
250246
"InvalidConvexFunction",
251247
format!("Failed to parse Convex function path: {}", req.path),
252248
))?;
253-
// TODO: Move generating request_id and configuring sentry axum middleware.
254-
let request_id = RequestId::new();
255-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
256249
let udf_return = st
257250
.application
258251
.read_only_udf(
@@ -289,15 +282,13 @@ pub struct QueryBatchResponse {
289282

290283
pub async fn public_query_batch_post(
291284
State(st): State<LocalAppState>,
285+
ExtractRequestId(request_id): ExtractRequestId,
292286
ExtractIdentity(identity): ExtractIdentity,
293287
ExtractClientVersion(client_version): ExtractClientVersion,
294288
Json(req_batch): Json<QueryBatchArgs>,
295289
) -> Result<impl IntoResponse, HttpResponseError> {
296290
let mut results = vec![];
297291
let ts = *st.application.now_ts_for_reads();
298-
// TODO: Move generating request_id and configuring sentry axum middleware.
299-
let request_id = RequestId::new();
300-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
301292
for req in req_batch.queries {
302293
let value_format = req.format.as_ref().map(|f| f.parse()).transpose()?;
303294
let udf_path = parse_udf_path(&req.path)?;
@@ -333,14 +324,12 @@ pub async fn public_query_batch_post(
333324

334325
pub async fn public_mutation_post(
335326
State(st): State<LocalAppState>,
327+
ExtractRequestId(request_id): ExtractRequestId,
336328
ExtractIdentity(identity): ExtractIdentity,
337329
ExtractClientVersion(client_version): ExtractClientVersion,
338330
Json(req): Json<UdfPostRequest>,
339331
) -> Result<impl IntoResponse, HttpResponseError> {
340332
let udf_path = parse_udf_path(&req.path)?;
341-
// TODO: Move generating request_id and configuring sentry axum middleware.
342-
let request_id = RequestId::new();
343-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
344333
let udf_result = st
345334
.application
346335
.mutation_udf(
@@ -372,13 +361,11 @@ pub async fn public_mutation_post(
372361

373362
pub async fn public_action_post(
374363
State(st): State<LocalAppState>,
364+
ExtractRequestId(request_id): ExtractRequestId,
375365
ExtractIdentity(identity): ExtractIdentity,
376366
ExtractClientVersion(client_version): ExtractClientVersion,
377367
Json(req): Json<UdfPostRequest>,
378368
) -> Result<impl IntoResponse, HttpResponseError> {
379-
// TODO: Move generating request_id and configuring sentry axum middleware.
380-
let request_id = RequestId::new();
381-
sentry::configure_scope(|scope| scope.set_tag("request_id", request_id.clone()));
382369
let udf_path = parse_udf_path(&req.path)?;
383370
let action_result = st
384371
.application

Diff for: crates/sync/src/worker.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,16 @@ impl<RT: Runtime> SyncWorker<RT> {
430430
Some(id) => RequestId::new_for_ws_session(id, request_id),
431431
None => RequestId::new(),
432432
};
433-
let root = get_sampled_span(
434-
"sync-worker/mutation".into(),
435-
self.rt.clone(),
436-
btreemap! {
437-
"udf_type".into() => UdfType::Mutation.to_lowercase_string().into(),
438-
"udf_path".into() => udf_path.clone().into(),
439-
},
440-
);
433+
let root = self.rt.with_rng(|rng| {
434+
get_sampled_span(
435+
"sync-worker/mutation",
436+
rng,
437+
btreemap! {
438+
"udf_type".into() => UdfType::Mutation.to_lowercase_string().into(),
439+
"udf_path".into() => udf_path.clone().into(),
440+
},
441+
)
442+
});
441443
let rt = self.rt.clone();
442444
let client_version = self.config.client_version.clone();
443445
let timer = mutation_queue_timer();

0 commit comments

Comments
 (0)