diff --git a/Cargo.lock b/Cargo.lock index df9a4c62f..709a5ed99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4100,9 +4100,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -4114,9 +4114,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ "async-trait", "bytes", @@ -4127,9 +4127,9 @@ dependencies = [ [[package]] name = "opentelemetry-instrumentation-process" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a81f1738bbdcf58eae20bd85ab0973f9c1f700ba37789e3253dc4e7ba61855c" +checksum = "065d9891eac9d4089f2c61872a43236029496c3642f62706ddbe71e96f6e00be" dependencies = [ "opentelemetry", "opentelemetry-semantic-conventions", @@ -4138,9 +4138,9 @@ dependencies = [ [[package]] name = "opentelemetry-instrumentation-tokio" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbe5cbcb32e33e68fd7a3a60d47c3b0a00cd40a4d012cec2f862f463dc0c296" +checksum = "aac2efa77dc2389d53fc00e53074a8117f53b1bd5ab92b0cd8bf7625156dc49f" dependencies = [ "opentelemetry", "tokio", @@ -4148,18 +4148,18 @@ dependencies = [ [[package]] name = "opentelemetry-jaeger-propagator" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3bbd907f151104a112f749f3b8387ef669b7264e0bb80546ea0700a3b307b7" +checksum = "92c2cebef9a57a493394ba0901b692a6915cc4a1314e93f905db1344392b4aa4" dependencies = [ "opentelemetry", ] [[package]] name = "opentelemetry-otlp" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http", "opentelemetry", @@ -4173,9 +4173,9 @@ dependencies = [ [[package]] name = "opentelemetry-prometheus-text-exporter" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897906366b17a89bec845f6051e0c3474049402a09a0711eea180941293bd013" +checksum = "b4fe57ea90b12f643a366aa7c4fa5cfb54bd1cf252f55368337e40534b278a68" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -4184,22 +4184,20 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ "opentelemetry", "opentelemetry_sdk", "prost", - "tonic", - "tonic-prost", ] [[package]] name = "opentelemetry-resource-detectors" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82845106cf72d47c141cee7f0d95e0650d8f28c6222a1f1ae727a8883899c19" +checksum = "96c8b28af3391a756b248f470f59b35041dd3ac9b9633d2df40757df709314f1" dependencies = [ "opentelemetry", "opentelemetry-semantic-conventions", @@ -4208,15 +4206,15 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" +checksum = "6ca2f98a0437b427b4b08f19f1caa3c44db885a202bc12cfea13d6c702243d68" [[package]] name = "opentelemetry-stdout" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8887887e169414f637b18751487cce4e095be787d23fad13c454e2fb1b3811" +checksum = "a1b1c6a247d79091f0062a5f4bd058589525cf987a8d4c169440d9c1be72f0ad" dependencies = [ "chrono", "opentelemetry", @@ -4225,15 +4223,16 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", + "portable-atomic", "rand 0.9.4", "thiserror 2.0.17", "tokio", @@ -4942,9 +4941,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -4966,6 +4965,7 @@ dependencies = [ "pin-project-lite", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "serde_urlencoded", @@ -5132,6 +5132,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -5392,13 +5393,14 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8" +checksum = "931a20b0da02350676e3d6d3c9028d58eaa448cf42a866712eec5845a505421e" dependencies = [ "cfg_aliases", "httpdate", "reqwest", + "rustls", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -5406,13 +5408,14 @@ dependencies = [ "sentry-tower", "sentry-tracing", "tokio", + "ureq", ] [[package]] name = "sentry-backtrace" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8784d0a27b5cd4b5f75769ffc84f0b7580e3c35e1af9cd83cb90b612d769cc" +checksum = "911ee36abf5b7fa335fccd5f54361ba9c16baea5f0c3bb361a687b6c195c21cf" dependencies = [ "backtrace", "regex", @@ -5421,9 +5424,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e5eb42f4cd4f9fdfec9e3b07b25a4c9769df83d218a7e846658984d5948ad3e" +checksum = "9b9d7d469e9e22741c17ca23fb8b42d79861590eb7cf330f3da34fc1e4bc1bc6" dependencies = [ "hostname", "libc", @@ -5435,9 +5438,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b1e7ca40f965db239da279bf278d87b7407469b98835f27f0c8e59ed189b06" +checksum = "545dc562b6758d646ac19e1407f4ebc26d452111386743e03323464bc48bb2e0" dependencies = [ "rand 0.9.4", "sentry-types", @@ -5448,9 +5451,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8906f8be87aea5ac7ef937323fb655d66607427f61007b99b7cb3504dc5a156c" +checksum = "772d9de150c8ca910c835353c85f434457348fdd21208f9b3da3574202b1dc5d" dependencies = [ "sentry-backtrace", "sentry-core", @@ -5458,9 +5461,9 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56aebe376310840b49dad4cca55c7b32d9abdc14946cd071d4158ecb149b63a4" +checksum = "2abea154597936d5df2d39fbe8aac16d584de6b3572c70c39558764d9d2efe15" dependencies = [ "axum", "http", @@ -5473,9 +5476,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b07eefe04486316c57aba08ab53dd44753c25102d1d3fe05775cc93a13262d9" +checksum = "c51ec9620a4d398dcdf7ee90effbf8d8691cfa24e91978bfa8565cac039d4980" dependencies = [ "bitflags", "sentry-backtrace", @@ -5486,9 +5489,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.46.2" +version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567711f01f86a842057e1fc17779eba33a336004227e1a1e7e6cc2599e22e259" +checksum = "041359745a44dd2e14fe21b7510fe7ca8b5beffce6636a0b52e5bc7d5f736887" dependencies = [ "debugid", "hex", @@ -6453,7 +6456,6 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ - "async-trait", "base64", "bytes", "http", @@ -6470,17 +6472,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tonic-prost" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" -dependencies = [ - "bytes", - "prost", - "tonic", -] - [[package]] name = "tower" version = "0.5.2" @@ -6608,9 +6599,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -6765,6 +6756,34 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots 1.0.2", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.7" @@ -6789,6 +6808,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index a6077739c..f7605a70b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -404,40 +404,40 @@ version = "0.2.0" # OpenTelemetry [workspace.dependencies.opentelemetry] -version = "0.31.0" +version = "0.32.0" features = ["trace", "metrics"] [workspace.dependencies.opentelemetry-http] -version = "0.31.0" +version = "0.32.0" features = ["reqwest"] [workspace.dependencies.opentelemetry-instrumentation-process] -version = "0.1.2" +version = "0.2.0" [workspace.dependencies.opentelemetry-instrumentation-tokio] -version = "0.1.2" +version = "0.2.0" [workspace.dependencies.opentelemetry-jaeger-propagator] -version = "0.31.0" +version = "0.32.0" [workspace.dependencies.opentelemetry-otlp] -version = "0.31.1" +version = "0.32.0" default-features = false features = ["trace", "metrics", "http-proto", "tls-provider-agnostic"] [workspace.dependencies.opentelemetry-prometheus-text-exporter] -version = "0.2.1" +version = "0.3.0" [workspace.dependencies.opentelemetry-resource-detectors] -version = "0.10.0" +version = "0.11.0" [workspace.dependencies.opentelemetry-semantic-conventions] -version = "0.31.0" +version = "0.32.0" features = ["semconv_experimental"] [workspace.dependencies.opentelemetry-stdout] -version = "0.31.0" +version = "0.32.0" features = ["trace", "metrics"] [workspace.dependencies.opentelemetry_sdk] -version = "0.31.0" +version = "0.32.1" features = [ "experimental_trace_batch_span_processor_with_async_runtime", "experimental_metrics_periodicreader_with_async_runtime", "rt-tokio", ] [workspace.dependencies.tracing-opentelemetry] -version = "0.32.1" +version = "0.33.0" default-features = false # P256 elliptic curve @@ -508,13 +508,14 @@ version = "1.12.2" # High-level HTTP client [workspace.dependencies.reqwest] -version = "0.12.24" +version = "0.13.4" default-features = false features = [ "http2", - "rustls-tls-manual-roots-no-provider", + "rustls-no-provider", "charset", "json", + "form", "socks", "system-proxy", ] @@ -576,18 +577,18 @@ features = [ # Sentry error tracking [workspace.dependencies.sentry] -version = "0.46.2" +version = "0.48.2" default-features = false -features = ["backtrace", "contexts", "panic", "tower", "reqwest"] +features = ["backtrace", "contexts", "panic", "tower", "reqwest", "rustls-no-provider"] # Sentry tower layer [workspace.dependencies.sentry-tower] -version = "0.46.0" +version = "0.48.2" features = ["http", "axum-matched-path"] # Sentry tracing integration [workspace.dependencies.sentry-tracing] -version = "0.46.0" +version = "0.48.2" # Serialization and deserialization [workspace.dependencies.serde] diff --git a/crates/cli/src/server.rs b/crates/cli/src/server.rs index 3fbafc4cc..34a50c831 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/server.rs @@ -21,7 +21,7 @@ use hyper::{Method, Request, Response, StatusCode, Version, header::USER_AGENT}; use listenfd::ListenFd; use mas_config::{HttpBindConfig, HttpResource, HttpTlsConfig, UnixOrTcp}; use mas_context::LogContext; -use mas_handlers::ClientIp; +use mas_handlers::{ClientIp, GraphQLOperation}; use mas_listener::{ConnectionInfo, unix_or_tcp::UnixOrTcpListener}; use mas_router::Route; use mas_templates::Templates; @@ -207,6 +207,16 @@ async fn log_response_middleware( let response = next.run(request).await; + // If the request went through the GraphQL handler, it will have recorded the + // operation type and name in the response extensions. + let graphql = response.extensions().get::(); + let graphql_operation_type = graphql + .and_then(|operation| operation.operation_type) + .map(tracing::field::display); + let graphql_operation_name = graphql + .and_then(|operation| operation.operation_name.as_deref()) + .map(tracing::field::display); + let Some((stats, requester)) = LogContext::maybe_with(|ctx| { ( ctx.stats(), @@ -221,17 +231,32 @@ async fn log_response_middleware( match status_code.as_u16() { 100..=399 => tracing::info!( name: "http.server.response", - { requester = requester, client.address = client_ip }, + { + requester = requester, + client.address = client_ip, + graphql.operation.type = graphql_operation_type, + graphql.operation.name = graphql_operation_name, + }, "\"{method} {path} HTTP/{version}\" {status_code} {user_agent:?} [{stats}]", ), 400..=499 => tracing::warn!( name: "http.server.response", - { requester = requester, client.address = client_ip }, + { + requester = requester, + client.address = client_ip, + graphql.operation.type = graphql_operation_type, + graphql.operation.name = graphql_operation_name, + }, "\"{method} {path} HTTP/{version}\" {status_code} {user_agent:?} [{stats}]", ), 500..=599 => tracing::error!( name: "http.server.response", - { requester = requester, client.address = client_ip }, + { + requester = requester, + client.address = client_ip, + graphql.operation.type = graphql_operation_type, + graphql.operation.name = graphql_operation_name, + }, "\"{method} {path} HTTP/{version}\" {status_code} {user_agent:?} [{stats}]", ), _ => { /* This shouldn't happen */ } diff --git a/crates/cli/src/telemetry.rs b/crates/cli/src/telemetry.rs index 283b9018d..12add6ec4 100644 --- a/crates/cli/src/telemetry.rs +++ b/crates/cli/src/telemetry.rs @@ -83,6 +83,12 @@ fn match_propagator(propagator: Propagator) -> Box Box::new(TraceContextPropagator::new()), P::Baggage => Box::new(BaggagePropagator::new()), + // opentelemetry-jaeger-propagator is deprecated upstream but still + // published; we keep supporting the Jaeger format until it's removed. + #[expect( + deprecated, + reason = "the Jaeger propagator is deprecated upstream but we still expose it as an option" + )] P::Jaeger => Box::new(opentelemetry_jaeger_propagator::Propagator::new()), } } diff --git a/crates/context/src/fmt.rs b/crates/context/src/fmt.rs index 5d52625fc..0b6edb130 100644 --- a/crates/context/src/fmt.rs +++ b/crates/context/src/fmt.rs @@ -129,7 +129,7 @@ where // If we have a OTEL span, we can add the trace ID to the end of the log line if let Some(span) = ctx.lookup_current() && let Some(trace_id) = tracing::dispatcher::get_default(|dispatch| { - let otel_cx = get_otel_context(&mut span.extensions_mut(), dispatch)?; + let otel_cx = get_otel_context(&span.id(), dispatch)?; let trace_id = otel_cx.span().span_context().trace_id(); Some(trace_id) }) diff --git a/crates/handlers/src/graphql/mod.rs b/crates/handlers/src/graphql/mod.rs index ef211019b..44503aa9b 100644 --- a/crates/handlers/src/graphql/mod.rs +++ b/crates/handlers/src/graphql/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025, 2026 Element Creations Ltd. +// Copyright 2026 Element Creations Ltd. // Copyright 2024, 2025 New Vector Ltd. // Copyright 2022-2024 The Matrix.org Foundation C.I.C. // @@ -13,6 +13,7 @@ use async_graphql::{ EmptySubscription, InputObject, extensions::Tracing, http::{GraphQLPlaygroundConfig, MultipartOptions, playground_source}, + parser::types::{DocumentOperations, OperationType}, }; use axum::{ Extension, Json, @@ -37,7 +38,9 @@ use mas_matrix::HomeserverConnection; use mas_policy::{InstantiateError, Policy, PolicyFactory}; use mas_router::UrlBuilder; use mas_storage::{BoxRepository, BoxRepositoryFactory, RepositoryError}; -use opentelemetry_semantic_conventions::trace::{GRAPHQL_DOCUMENT, GRAPHQL_OPERATION_NAME}; +use opentelemetry_semantic_conventions::trace::{ + GRAPHQL_DOCUMENT, GRAPHQL_OPERATION_NAME, GRAPHQL_OPERATION_TYPE, +}; use rand::{SeedableRng, thread_rng}; use rand_chacha::ChaChaRng; use state::has_session_ended; @@ -148,21 +151,90 @@ pub fn schema( schema_builder().extension(Tracing).data(state).finish() } -fn span_for_graphql_request(request: &async_graphql::Request) -> tracing::Span { +fn span_and_operation_for_graphql_request( + request: &mut async_graphql::Request, +) -> (tracing::Span, GraphQLOperation) { let span = info_span!( "GraphQL operation", "otel.name" = tracing::field::Empty, "otel.kind" = "server", { GRAPHQL_DOCUMENT } = request.query, { GRAPHQL_OPERATION_NAME } = tracing::field::Empty, + { GRAPHQL_OPERATION_TYPE } = tracing::field::Empty, ); - if let Some(name) = &request.operation_name { - span.record("otel.name", name); - span.record(GRAPHQL_OPERATION_NAME, name); + let mut graphql_operation = GraphQLOperation { + operation_type: None, + operation_name: None, + }; + + // We need to clone the operation_name before parsing the query, else we're + // going to have a borrow conflict between request.parsed_query() and + // request.operation_name + let operation_name = request.operation_name.clone(); + if let Ok(document) = request.parsed_query() { + match (&document.operations, operation_name) { + // A single anonymous operation, with no name requested: the + // document defines no name for it, so we only record the type. + (DocumentOperations::Single(operation), None) => { + span.record("otel.name", format!("GraphQL {}", operation.node.ty)); + span.record( + GRAPHQL_OPERATION_TYPE, + tracing::field::display(operation.node.ty), + ); + graphql_operation.operation_type = Some(operation.node.ty); + } + + (DocumentOperations::Multiple(operations), Some(name)) => { + if let Some((name, operation)) = operations.get_key_value(name.as_str()) { + span.record( + "otel.name", + format!("GraphQL {} {}", operation.node.ty, name), + ); + span.record( + GRAPHQL_OPERATION_TYPE, + tracing::field::display(operation.node.ty), + ); + span.record(GRAPHQL_OPERATION_NAME, tracing::field::display(name)); + graphql_operation.operation_type = Some(operation.node.ty); + graphql_operation.operation_name = Some(name.to_string()); + } + } + + (DocumentOperations::Multiple(operations), None) if operations.len() == 1 => { + let mut iter = operations.iter(); + let (name, operation) = iter.next().unwrap(); + span.record( + "otel.name", + format!("GraphQL {} {}", operation.node.ty, name), + ); + span.record( + GRAPHQL_OPERATION_TYPE, + tracing::field::display(operation.node.ty), + ); + span.record(GRAPHQL_OPERATION_NAME, name.as_ref()); + graphql_operation.operation_type = Some(operation.node.ty); + graphql_operation.operation_name = Some(name.to_string()); + } + + // Cases the executor rejects, so we don't record a misleading + // operation: a single anonymous operation with a name requested, or + // several named operations with no requested name (ambiguous). + (DocumentOperations::Single(_), Some(_)) | (DocumentOperations::Multiple(_), None) => {} + } } - span + (span, graphql_operation) +} + +/// The GraphQL operation being executed, attached to the response extensions so +/// the HTTP logging middleware can record it on the request log line. +#[derive(Clone, Debug)] +pub struct GraphQLOperation { + /// The type of the operation: query, mutation or subscription. + pub operation_type: Option, + /// The name of the operation, as defined in the query document. + pub operation_name: Option, } #[derive(thiserror::Error, Debug)] @@ -345,7 +417,7 @@ pub async fn post( let content_type = content_type.map(|TypedHeader(h)| h.to_string()); - let request = async_graphql::http::receive_body( + let mut request = async_graphql::http::receive_body( content_type, body.map_err(std::io::Error::other).into_async_read(), MultipartOptions::default(), @@ -353,7 +425,7 @@ pub async fn post( .await? .data(requester); // XXX: this should probably return another error response? - let span = span_for_graphql_request(&request); + let (span, operation) = span_and_operation_for_graphql_request(&mut request); let mut response = schema.execute(request).instrument(span).await; if has_session_ended(&mut response) { @@ -369,7 +441,13 @@ pub async fn post( let headers = response.http_headers.clone(); - Ok((headers, cache_control, cookie_jar, Json(response))) + Ok(( + headers, + cache_control, + cookie_jar, + Extension(operation), + Json(response), + )) } pub async fn get( @@ -401,10 +479,10 @@ pub async fn get( ) .await?; - let request = + let mut request = async_graphql::http::parse_query_string(&query.unwrap_or_default())?.data(requester); - let span = span_for_graphql_request(&request); + let (span, operation) = span_and_operation_for_graphql_request(&mut request); let mut response = schema.execute(request).instrument(span).await; if has_session_ended(&mut response) { @@ -420,7 +498,13 @@ pub async fn get( let headers = response.http_headers.clone(); - Ok((headers, cache_control, cookie_jar, Json(response))) + Ok(( + headers, + cache_control, + cookie_jar, + Extension(operation), + Json(response), + )) } pub async fn playground() -> impl IntoResponse { diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 2f075f4c0..20d17d982 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -109,7 +109,8 @@ pub use self::{ admin::router as admin_api_router, client_ip::ClientIp, graphql::{ - Schema as GraphQLSchema, schema as graphql_schema, schema_builder as graphql_schema_builder, + GraphQLOperation, Schema as GraphQLSchema, schema as graphql_schema, + schema_builder as graphql_schema_builder, }, preferred_language::PreferredLanguage, rate_limit::{Limiter, RequesterFingerprint},