diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 8a8a77d8..0e58a087 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -64,7 +64,7 @@ use ruma::{ }; use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use tokio::sync::RwLock; -use tracing::{debug, error, field, warn}; +use tracing::{debug, error, field, trace, trace_span, warn}; use super::appservice_server; use crate::{ @@ -253,6 +253,14 @@ where signature, ))); + // can be enabled selectively using `filter = + // grapevine[outgoing_request_curl]=trace` in config + trace_span!("outgoing_request_curl").in_scope(|| { + trace!( + cmd = utils::curlify(&http_request), + "curl command line for outgoing request" + ); + }); let reqwest_request = reqwest::Request::try_from(http_request)?; let url = reqwest_request.url().clone(); diff --git a/src/main.rs b/src/main.rs index da1010c7..8af1825c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,26 +147,42 @@ async fn run_server() -> Result<(), error::Serve> { let middlewares = ServiceBuilder::new() .sensitive_headers([header::AUTHORIZATION]) .layer(axum::middleware::from_fn(spawn_task)) - .layer(TraceLayer::new_for_http().make_span_with( - |request: &http::Request<_>| { - let endpoint = if let Some(endpoint) = - request.extensions().get::() - { - endpoint.as_str() - } else { - request.uri().path() - }; + .layer( + TraceLayer::new_for_http() + .make_span_with(|request: &http::Request<_>| { + let endpoint = if let Some(endpoint) = + request.extensions().get::() + { + endpoint.as_str() + } else { + request.uri().path() + }; - let method = request.method(); + let method = request.method(); - tracing::info_span!( - "http_request", - otel.name = format!("{method} {endpoint}"), - %method, - %endpoint, - ) - }, - )) + tracing::info_span!( + "http_request", + otel.name = format!("{method} {endpoint}"), + %method, + %endpoint, + ) + }) + .on_request( + |request: &http::Request<_>, _span: &tracing::Span| { + // can be enabled selectively using `filter = + // grapevine[incoming_request_curl]=trace` in config + tracing::trace_span!("incoming_request_curl").in_scope( + || { + tracing::trace!( + cmd = utils::curlify(request), + "curl command line for incoming request \ + (guessed hostname)" + ); + }, + ); + }, + ), + ) .layer(axum::middleware::from_fn(unrecognized_method)) .layer( CorsLayer::new() diff --git a/src/utils.rs b/src/utils.rs index 30be1bad..504d21b1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -306,6 +306,66 @@ impl<'a> TryFrom<&'a MxcUri> for MxcData<'a> { } } +fn curlify_args(req: &http::Request) -> Option> { + let mut args = + vec!["curl".to_owned(), "-X".to_owned(), req.method().to_string()]; + + for (name, val) in req.headers() { + args.extend([ + "-H".to_owned(), + format!("{name}: {}", val.to_str().ok()?), + ]); + } + + let fix_uri = || { + if req.uri().scheme().is_some() { + return None; + } + if req.uri().authority().is_some() { + return None; + } + let mut parts = req.uri().clone().into_parts(); + + parts.scheme = Some(http::uri::Scheme::HTTPS); + + let host = + req.headers().get(http::header::HOST)?.to_str().ok()?.to_owned(); + parts.authority = + Some(http::uri::Authority::from_maybe_shared(host).ok()?); + + http::uri::Uri::from_parts(parts).ok() + }; + + let uri = if let Some(new_uri) = fix_uri() { + Cow::Owned(new_uri) + } else { + Cow::Borrowed(req.uri()) + }; + + args.push(uri.to_string()); + + Some(args) +} + +pub(crate) fn curlify(req: &http::Request) -> Option { + let args = curlify_args(req)?; + + Some( + args.into_iter() + .map(|arg| { + if arg.chars().all(|c| { + c.is_alphanumeric() || ['-', '_', ':', '/'].contains(&c) + }) { + arg + } else { + format!("'{}'", arg.replace('\'', "\\'")) + } + }) + .collect::>() + .join(" "), + ) +} + #[cfg(test)] mod tests { use crate::utils::dbg_truncate_str;