From 58ac57988f3237cb67741670ec9d8e82c02b61ba Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Wed, 25 Sep 2024 14:40:45 -0600 Subject: [PATCH 01/18] Implement HTTP metrics for Connectors --- ...nfiguration__tests__schema_generation.snap | 460 +++++++++++++++++ .../telemetry/config_new/attributes.rs | 135 +++++ .../telemetry/config_new/instruments.rs | 328 ++++++++++++ .../plugins/telemetry/config_new/selectors.rs | 481 ++++++++++++++++++ .../plugins/telemetry/metrics/apollo/mod.rs | 5 +- apollo-router/src/plugins/telemetry/mod.rs | 66 ++- apollo-router/src/plugins/test.rs | 4 +- .../src/services/connector_service.rs | 30 ++ 8 files changed, 1501 insertions(+), 8 deletions(-) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 1261e674ef..5e2d2afc46 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -624,6 +624,143 @@ expression: "&schema" } ] }, + "Condition_for_ConnectorSelector": { + "oneOf": [ + { + "additionalProperties": false, + "description": "A condition to check a selection against a value.", + "properties": { + "eq": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "eq" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be greater than the second selection.", + "properties": { + "gt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "gt" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The first selection must be less than the second selection.", + "properties": { + "lt": { + "items": { + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "lt" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "A condition to check a selection against a selector.", + "properties": { + "exists": { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + } + }, + "required": [ + "exists" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "All sub-conditions must be true.", + "properties": { + "all": { + "items": { + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" + }, + "type": "array" + } + }, + "required": [ + "all" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "At least one sub-conditions must be true.", + "properties": { + "any": { + "items": { + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" + }, + "type": "array" + } + }, + "required": [ + "any" + ], + "type": "object" + }, + { + "additionalProperties": false, + "description": "The sub-condition must not be true", + "properties": { + "not": { + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" + } + }, + "required": [ + "not" + ], + "type": "object" + }, + { + "description": "Static true condition", + "enum": [ + "true" + ], + "type": "string" + }, + { + "description": "Static false condition", + "enum": [ + "false" + ], + "type": "string" + } + ] + }, "Condition_for_GraphQLSelector": { "oneOf": [ { @@ -1780,6 +1917,204 @@ expression: "&schema" }, "type": "object" }, + "ConnectorAttributes": { + "additionalProperties": false, + "properties": { + "connector.http.method": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.source.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.url.template": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "subgraph.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + } + }, + "type": "object" + }, + "ConnectorInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + } + }, + "type": "object" + }, + "ConnectorSelector": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "subgraph_name": { + "description": "The subgraph name", + "type": "boolean" + } + }, + "required": [ + "subgraph_name" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_source": { + "$ref": "#/definitions/ConnectorSource", + "description": "#/definitions/ConnectorSource" + } + }, + "required": [ + "connector_source" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_request_header": { + "description": "The name of a connector request header.", + "type": "string" + }, + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "connector_request_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_response_header": { + "description": "The name of a connector response header.", + "type": "string" + }, + "default": { + "description": "Optional default value.", + "nullable": true, + "type": "string" + } + }, + "required": [ + "connector_response_header" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_response_status": { + "$ref": "#/definitions/ResponseStatus", + "description": "#/definitions/ResponseStatus" + } + }, + "required": [ + "connector_response_status" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_http_method": { + "description": "The connector HTTP method.", + "type": "boolean" + } + }, + "required": [ + "connector_http_method" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "connector_url_template": { + "description": "The connector URL template.", + "type": "boolean" + } + }, + "required": [ + "connector_url_template" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "static": { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + } + }, + "required": [ + "static" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/ErrorRepr", + "description": "#/definitions/ErrorRepr" + } + }, + "required": [ + "error" + ], + "type": "object" + } + ] + }, + "ConnectorSource": { + "oneOf": [ + { + "description": "The name of the connector source.", + "enum": [ + "name" + ], + "type": "string" + } + ] + }, + "ConnectorValue": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + } + ] + }, "ConnectorsConfig": { "additionalProperties": false, "properties": { @@ -2011,6 +2346,29 @@ expression: "&schema" } ] }, + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + } + }, + "required": [ + "attributes" + ], + "type": "object" + } + ] + }, "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { "anyOf": [ { @@ -3397,6 +3755,42 @@ expression: "&schema" ], "type": "object" }, + "Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" + }, + "description": { + "description": "The description of the instrument.", + "type": "string" + }, + "type": { + "$ref": "#/definitions/InstrumentType", + "description": "#/definitions/InstrumentType" + }, + "unit": { + "description": "The units of the instrument, e.g. \"ms\", \"bytes\", \"requests\".", + "type": "string" + }, + "value": { + "$ref": "#/definitions/ConnectorValue", + "description": "#/definitions/ConnectorValue" + } + }, + "required": [ + "description", + "type", + "unit", + "value" + ], + "type": "object" + }, "Instrument_for_GraphQLAttributes_and_GraphQLSelector_and_GraphQLValue": { "additionalProperties": false, "properties": { @@ -3567,6 +3961,10 @@ expression: "&schema" "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, + "connector": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + }, "default_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", "description": "#/definitions/DefaultAttributeRequirementLevel" @@ -5330,6 +5728,18 @@ expression: "&schema" }, "type": "object" }, + "SelectorOrValue_for_ConnectorSelector": { + "anyOf": [ + { + "$ref": "#/definitions/AttributeValue", + "description": "#/definitions/AttributeValue" + }, + { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + } + ] + }, "SelectorOrValue_for_GraphQLSelector": { "anyOf": [ { @@ -7500,6 +7910,35 @@ expression: "&schema" } ] }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector": { + "additionalProperties": { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + }, + "properties": { + "connector.http.method": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.source.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.url.template": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "subgraph.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + } + }, + "type": "object" + }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { "additionalProperties": { "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", @@ -8041,6 +8480,27 @@ expression: "&schema" }, "type": "object" }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "additionalProperties": { + "$ref": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue", + "description": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue" + }, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + } + }, + "type": "object" + }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::RouterInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { "$ref": "#/definitions/Instrument_for_RouterAttributes_and_RouterSelector_and_RouterValue", diff --git a/apollo-router/src/plugins/telemetry/config_new/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/attributes.rs index 53640cd87b..fb876da4f7 100644 --- a/apollo-router/src/plugins/telemetry/config_new/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/attributes.rs @@ -44,6 +44,10 @@ use crate::plugins::telemetry::config_new::DefaultForLevel; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otel::OpenTelemetrySpanExt; use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::services::connector_service::ConnectorInfo; +use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; use crate::services::router; use crate::services::router::Request; use crate::services::subgraph; @@ -65,6 +69,10 @@ const NETWORK_LOCAL_PORT: Key = Key::from_static_str("network.local.port"); const NETWORK_PEER_ADDRESS: Key = Key::from_static_str("network.peer.address"); const NETWORK_PEER_PORT: Key = Key::from_static_str("network.peer.port"); +const CONNECTOR_SOURCE_NAME: Key = Key::from_static_str("connector.source.name"); +const CONNECTOR_HTTP_METHOD: Key = Key::from_static_str("connector.http.method"); +const CONNECTOR_URL_TEMPLATE: Key = Key::from_static_str("connector.url.template"); + #[derive(Deserialize, JsonSchema, Clone, Debug, Default, Copy)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub(crate) enum DefaultAttributeRequirementLevel { @@ -264,6 +272,78 @@ impl DefaultForLevel for SubgraphAttributes { } } +#[derive(Deserialize, JsonSchema, Clone, Default, Debug)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorAttributes { + /// The name of the subgraph containing the connector + /// Examples: + /// + /// * posts + /// + /// Requirement level: Required + #[serde(rename = "subgraph.name")] + subgraph_name: Option, + + /// The name of the source for this connector, if defined + /// Examples: + /// + /// * posts_api + /// + /// Requirement level: Conditionally Required: If the connector has a source defined + #[serde(rename = "connector.source.name")] + connector_source_name: Option, + + /// The HTTP method for the connector + /// Examples: + /// + /// * GET + /// * POST + /// + /// Requirement level: Required + #[serde(rename = "connector.http.method")] + connector_http_method: Option, + + /// The connector URL template, relative to the source base URL if one is defined + /// Examples: + /// + /// * /users/{$this.id!}/post + /// + /// Requirement level: Required + #[serde(rename = "connector.url.template")] + connector_url_template: Option, +} + +impl DefaultForLevel for ConnectorAttributes { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + _kind: TelemetryDataKind, + ) { + match requirement_level { + DefaultAttributeRequirementLevel::Required => { + if self.subgraph_name.is_none() { + self.subgraph_name = Some(StandardAttribute::Bool(true)); + } + } + DefaultAttributeRequirementLevel::Recommended => { + if self.subgraph_name.is_none() { + self.subgraph_name = Some(StandardAttribute::Bool(true)); + } + if self.connector_source_name.is_none() { + self.connector_source_name = Some(StandardAttribute::Bool(true)); + } + if self.connector_http_method.is_none() { + self.connector_http_method = Some(StandardAttribute::Bool(true)); + } + if self.connector_url_template.is_none() { + self.connector_url_template = Some(StandardAttribute::Bool(true)); + } + } + DefaultAttributeRequirementLevel::None => {} + } + } +} + /// Common attributes for http server and client. /// See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes #[derive(Deserialize, JsonSchema, Clone, Default, Debug)] @@ -1131,6 +1211,61 @@ impl Selectors for SubgraphAttributes { } } +impl Selectors for ConnectorAttributes { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) -> Vec { + let mut attrs = Vec::new(); + + if let Ok(Some(connector_info)) = request + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY) + { + if let Some(key) = self + .subgraph_name + .as_ref() + .and_then(|a| a.key(SUBGRAPH_NAME)) + { + attrs.push(KeyValue::new(key, connector_info.subgraph_name.to_string())); + } + if let Some(key) = self + .connector_source_name + .as_ref() + .and_then(|a| a.key(CONNECTOR_SOURCE_NAME)) + { + if let Some(source_name) = connector_info.source_name { + attrs.push(KeyValue::new(key, source_name.to_string())); + } + } + if let Some(key) = self + .connector_http_method + .as_ref() + .and_then(|a| a.key(CONNECTOR_HTTP_METHOD)) + { + attrs.push(KeyValue::new(key, connector_info.http_method)); + } + if let Some(key) = self + .connector_url_template + .as_ref() + .and_then(|a| a.key(CONNECTOR_URL_TEMPLATE)) + { + attrs.push(KeyValue::new(key, connector_info.url_template.to_string())); + } + } + attrs + } + + fn on_response(&self, _response: &Self::Response) -> Vec { + Vec::default() + } + + fn on_error(&self, _error: &BoxError, _ctx: &Context) -> Vec { + Vec::default() + } +} + #[cfg(test)] mod test { use std::net::SocketAddr; diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index 341f84ad35..7f0f65823f 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -20,6 +20,7 @@ use serde_json_bytes::Value; use tokio::time::Instant; use tower::BoxError; +use super::attributes::ConnectorAttributes; use super::attributes::HttpServerAttributes; use super::cache::attributes::CacheAttributes; use super::cache::CacheInstruments; @@ -30,6 +31,8 @@ use super::graphql::GraphQLInstruments; use super::graphql::FIELD_EXECUTION; use super::graphql::FIELD_LENGTH; use super::selectors::CacheKind; +use super::selectors::ConnectorSelector; +use super::selectors::ConnectorValue; use super::DefaultForLevel; use super::Selector; use crate::metrics; @@ -53,6 +56,8 @@ use crate::plugins::telemetry::config_new::selectors::SupergraphSelector; use crate::plugins::telemetry::config_new::selectors::SupergraphValue; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; @@ -81,6 +86,11 @@ pub(crate) struct InstrumentsConfig { SubgraphInstrumentsConfig, Instrument, >, + /// Connector service instruments. For more information see documentation on Router lifecycle. + pub(crate) connector: Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, /// GraphQL response field instruments. pub(crate) graphql: Extendable< GraphQLInstrumentsConfig, @@ -685,6 +695,227 @@ impl InstrumentsConfig { } } + pub(crate) fn new_connector_instruments( + &self, + static_instruments: Arc>, + ) -> ConnectorInstruments { + let http_client_request_duration = + self.connector + .attributes + .http_client_request_duration + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &self.connector.attributes.http_client_request_duration { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Duration(Instant::now()), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: None, + selectors, + updated: false, + }), + } + }); + let http_client_request_body_size = + self.connector + .attributes + .http_client_request_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &self.connector.attributes.http_client_request_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorSelector::ConnectorRequestHeader { + connector_request_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + let http_client_response_body_size = + self.connector + .attributes + .http_client_response_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &self.connector.attributes.http_client_response_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorSelector::ConnectorResponseHeader { + connector_response_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + ConnectorInstruments { + http_client_request_duration, + http_client_request_body_size, + http_client_response_body_size, + custom: CustomInstruments::new(&self.connector.custom, static_instruments), + } + } + + pub(crate) fn new_builtin_connector_instruments(&self) -> HashMap { + let meter = metrics::meter_provider().meter(METER_NAME); + let mut static_instruments = HashMap::with_capacity(3); + + if self + .connector + .attributes + .http_client_request_duration + .is_enabled() + { + static_instruments.insert( + HTTP_CLIENT_REQUEST_DURATION_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .with_unit(Unit::new("s")) + .with_description("Duration of HTTP client requests.") + .init(), + ), + ); + } + + if self + .connector + .attributes + .http_client_request_body_size + .is_enabled() + { + static_instruments.insert( + HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client request bodies.") + .init(), + ), + ); + } + + if self + .connector + .attributes + .http_client_response_body_size + .is_enabled() + { + static_instruments.insert( + HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client response bodies.") + .init(), + ), + ); + } + + for (instrument_name, instrument) in &self.connector.custom { + match instrument.ty { + InstrumentType::Counter => { + static_instruments.insert( + instrument_name.clone(), + StaticInstrument::CounterF64( + meter + .f64_counter(instrument_name.clone()) + .with_description(instrument.description.clone()) + .with_unit(Unit::new(instrument.unit.clone())) + .init(), + ), + ); + } + InstrumentType::Histogram => { + static_instruments.insert( + instrument_name.clone(), + StaticInstrument::Histogram( + meter + .f64_histogram(instrument_name.clone()) + .with_description(instrument.description.clone()) + .with_unit(Unit::new(instrument.unit.clone())) + .init(), + ), + ); + } + } + } + + static_instruments + } + pub(crate) fn new_builtin_graphql_instruments(&self) -> HashMap { let meter = metrics::meter_provider().meter(METER_NAME); let mut static_instruments = HashMap::with_capacity(self.graphql.custom.len()); @@ -1144,6 +1375,40 @@ impl DefaultForLevel for SubgraphInstrumentsConfig { } } +#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorInstrumentsConfig { + /// Histogram of client request duration + #[serde(rename = "http.client.request.duration")] + http_client_request_duration: + DefaultedStandardInstrument>, + + /// Histogram of client request body size + #[serde(rename = "http.client.request.body.size")] + http_client_request_body_size: + DefaultedStandardInstrument>, + + /// Histogram of client response body size + #[serde(rename = "http.client.response.body.size")] + http_client_response_body_size: + DefaultedStandardInstrument>, +} + +impl DefaultForLevel for ConnectorInstrumentsConfig { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + kind: TelemetryDataKind, + ) { + self.http_client_request_duration + .defaults_for_level(requirement_level, kind); + self.http_client_request_body_size + .defaults_for_level(requirement_level, kind); + self.http_client_response_body_size + .defaults_for_level(requirement_level, kind); + } +} + #[derive(Clone, Deserialize, JsonSchema, Debug)] #[serde(deny_unknown_fields)] pub(crate) struct Instrument @@ -1751,6 +2016,61 @@ impl Instrumented for SubgraphInstruments { } } +pub(crate) struct ConnectorInstruments { + http_client_request_duration: + Option>, + http_client_request_body_size: + Option>, + http_client_response_body_size: + Option>, + custom: ConnectorCustomInstruments, +} + +impl Instrumented for ConnectorInstruments { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_request(request); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_request(request); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_request(request); + } + self.custom.on_request(request); + } + + fn on_response(&self, response: &Self::Response) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_response(response); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_response(response); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_response(response); + } + self.custom.on_response(response); + } + + fn on_error(&self, error: &BoxError, ctx: &Context) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_error(error, ctx); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_error(error, ctx); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_error(error, ctx); + } + self.custom.on_error(error, ctx); + } +} + pub(crate) type RouterCustomInstruments = CustomInstruments< router::Request, router::Response, @@ -1775,6 +2095,14 @@ pub(crate) type SubgraphCustomInstruments = CustomInstruments< SubgraphValue, >; +pub(crate) type ConnectorCustomInstruments = CustomInstruments< + HttpRequest, + HttpResponse, + ConnectorAttributes, + ConnectorSelector, + ConnectorValue, +>; + // ---------------- Counter ----------------------- #[derive(Debug, Clone)] pub(crate) enum Increment { diff --git a/apollo-router/src/plugins/telemetry/config_new/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/selectors.rs index 8c9c8dde7a..ed48324105 100644 --- a/apollo-router/src/plugins/telemetry/config_new/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/selectors.rs @@ -6,6 +6,7 @@ use serde::Deserialize; use serde_json_bytes::path::JsonPathInst; use serde_json_bytes::ByteString; use sha2::Digest; +use tower::BoxError; use crate::context::CONTAINS_GRAPHQL_ERROR; use crate::context::OPERATION_KIND; @@ -23,8 +24,13 @@ use crate::plugins::telemetry::config_new::instruments::InstrumentValue; use crate::plugins::telemetry::config_new::instruments::Standard; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::Selector; +use crate::plugins::telemetry::config_new::Stage; use crate::plugins::telemetry::config_new::ToOtelValue; use crate::query_planner::APOLLO_OPERATION_ID; +use crate::services::connector_service::ConnectorInfo; +use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; @@ -89,6 +95,13 @@ pub(crate) enum OperationKind { String, } +#[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)] +#[serde(deny_unknown_fields, rename_all = "snake_case")] +pub(crate) enum ConnectorSource { + /// The name of the connector source. + Name, +} + #[derive(Deserialize, JsonSchema, Clone, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] pub(crate) enum RouterValue { @@ -623,6 +636,76 @@ pub(crate) enum SubgraphSelector { }, } +#[derive(Deserialize, JsonSchema, Clone, Debug)] +#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] +pub(crate) enum ConnectorValue { + Standard(Standard), + Custom(ConnectorSelector), +} + +impl From<&ConnectorValue> for InstrumentValue { + fn from(value: &ConnectorValue) -> Self { + match value { + ConnectorValue::Standard(s) => InstrumentValue::Standard(s.clone()), + ConnectorValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), + } + } +} + +#[derive(Deserialize, JsonSchema, Clone, Derivative)] +#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] +#[derivative(Debug, PartialEq)] +pub(crate) enum ConnectorSelector { + SubgraphName { + /// The subgraph name + subgraph_name: bool, + }, + ConnectorSource { + /// The connector source. + connector_source: ConnectorSource, + }, + ConnectorRequestHeader { + /// The name of a connector request header. + connector_request_header: String, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, + ConnectorResponseHeader { + /// The name of a connector response header. + connector_response_header: String, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, + ConnectorResponseStatus { + /// The connector http response status code. + connector_response_status: ResponseStatus, + }, + ConnectorHttpMethod { + /// The connector HTTP method. + connector_http_method: bool, + }, + ConnectorUrlTemplate { + /// The connector URL template. + connector_url_template: bool, + }, + StaticField { + /// A static value + r#static: AttributeValue, + }, + Error { + /// Critical error if it happens + error: ErrorRepr, + }, +} + #[derive(Deserialize, JsonSchema, Clone, PartialEq, Debug)] #[serde(rename_all = "snake_case", untagged)] pub(crate) enum EntityType { @@ -1729,11 +1812,196 @@ impl Selector for SubgraphSelector { } } +impl Selector for ConnectorSelector { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) -> Option { + let connector_info = request + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorRequestHeader { + connector_request_header, + default, + .. + } => request + .http_request + .headers() + .get(connector_request_header) + .and_then(|h| Some(h.to_str().ok()?.to_string())) + .or_else(|| default.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_response(&self, response: &Self::Response) -> Option { + let connector_info = response + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorResponseHeader { + connector_response_header, + default, + .. + } => response + .http_response + .headers() + .get(connector_response_header) + .and_then(|h| Some(h.to_str().ok()?.to_string())) + .or_else(|| default.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorResponseStatus { + connector_response_status: response_status, + } => match response_status { + ResponseStatus::Code => { + Some(Value::I64(response.http_response.status().as_u16() as i64)) + } + ResponseStatus::Reason => response + .http_response + .status() + .canonical_reason() + .map(|reason| reason.into()), + }, + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_error(&self, error: &BoxError, ctx: &Context) -> Option { + let connector_info = ctx.get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::Error { .. } => Some(error.to_string().into()), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_drop(&self) -> Option { + match self { + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn is_active(&self, stage: Stage) -> bool { + match stage { + Stage::Request => matches!( + self, + ConnectorSelector::ConnectorRequestHeader { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } + ), + Stage::Response => matches!( + self, + ConnectorSelector::ConnectorResponseHeader { .. } + | ConnectorSelector::ConnectorResponseStatus { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } + ), + Stage::ResponseEvent => false, + Stage::ResponseField => false, + Stage::Error => matches!( + self, + ConnectorSelector::Error { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } + ), + Stage::Drop => matches!(self, ConnectorSelector::StaticField { .. }), + } + } +} + #[cfg(test)] mod test { use std::str::FromStr; use std::sync::Arc; + use apollo_federation::sources::connect::HTTPMethod; use http::StatusCode; use opentelemetry::baggage::BaggageExt; use opentelemetry::trace::SpanContext; @@ -1745,6 +2013,8 @@ mod test { use opentelemetry::Context; use opentelemetry::KeyValue; use opentelemetry_api::StringValue; + use rstest::fixture; + use rstest::rstest; use serde_json::json; use serde_json_bytes::path::JsonPathInst; use tower::BoxError; @@ -1752,6 +2022,7 @@ mod test { use tracing::subscriber; use tracing_subscriber::layer::SubscriberExt; + use crate::context::Context as RouterContext; use crate::context::OPERATION_KIND; use crate::context::OPERATION_NAME; use crate::graphql; @@ -1761,6 +2032,8 @@ mod test { use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config_new::selectors::All; use crate::plugins::telemetry::config_new::selectors::CacheKind; + use crate::plugins::telemetry::config_new::selectors::ConnectorSelector; + use crate::plugins::telemetry::config_new::selectors::ConnectorSource; use crate::plugins::telemetry::config_new::selectors::EntityType; use crate::plugins::telemetry::config_new::selectors::OperationKind; use crate::plugins::telemetry::config_new::selectors::OperationName; @@ -1774,6 +2047,10 @@ mod test { use crate::plugins::telemetry::config_new::Selector; use crate::plugins::telemetry::otel; use crate::query_planner::APOLLO_OPERATION_ID; + use crate::services::connector_service::ConnectorInfo; + use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; + use crate::services::http::HttpRequest; + use crate::services::http::HttpResponse; use crate::services::FIRST_EVENT_CONTEXT_KEY; use crate::spec::operation_limits::OperationLimits; @@ -3569,4 +3846,208 @@ mod test { Some("default".into()) ); } + + const TEST_SUBGRAPH_NAME: &str = "test_subgraph_name"; + const TEST_SOURCE_NAME: &str = "test_source_name"; + const TEST_URL_TEMPLATE: &str = "/test"; + const TEST_HEADER_NAME: &str = "test_header_name"; + const TEST_HEADER_VALUE: &str = "test_header_value"; + const TEST_STATIC: &str = "test_static"; + + #[fixture] + fn connector_info() -> ConnectorInfo { + ConnectorInfo { + subgraph_name: TEST_SUBGRAPH_NAME.to_string(), + source_name: Some(TEST_SOURCE_NAME.to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: TEST_URL_TEMPLATE.to_string(), + } + } + + #[fixture] + fn context(connector_info: ConnectorInfo) -> RouterContext { + let context = RouterContext::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + context + } + + #[fixture] + fn http_request(context: RouterContext) -> HttpRequest { + HttpRequest { + http_request: http::Request::builder().body("".into()).unwrap(), + context, + } + } + + #[fixture] + fn http_request_with_header(context: RouterContext) -> HttpRequest { + HttpRequest { + http_request: http::Request::builder() + .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) + .body("".into()) + .unwrap(), + context, + } + } + + #[fixture] + fn http_response( + context: RouterContext, + #[default(StatusCode::OK)] status_code: StatusCode, + ) -> HttpResponse { + HttpResponse { + http_response: http::Response::builder() + .status(status_code) + .body("".into()) + .unwrap(), + context, + } + } + + #[fixture] + fn http_response_with_header(context: RouterContext) -> HttpResponse { + HttpResponse { + http_response: http::Response::builder() + .status(StatusCode::OK) + .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) + .body("".into()) + .unwrap(), + context, + } + } + + #[rstest] + fn connector_static_field(http_response: HttpResponse) { + let selector = ConnectorSelector::StaticField { + r#static: TEST_STATIC.to_string().into(), + }; + assert_eq!( + selector.on_response(&http_response).unwrap(), + TEST_STATIC.into() + ); + assert_eq!(selector.on_drop().unwrap(), TEST_STATIC.into()); + } + + #[rstest] + fn connector_subgraph_name(http_request: HttpRequest, http_response: HttpResponse) { + let selector = ConnectorSelector::SubgraphName { + subgraph_name: true, + }; + assert_eq!( + selector.on_request(&http_request), + Some(TEST_SUBGRAPH_NAME.into()) + ); + assert_eq!( + selector.on_response(&http_response), + Some(TEST_SUBGRAPH_NAME.into()) + ); + } + + #[rstest] + fn connector_request_header(http_request_with_header: HttpRequest) { + let selector = ConnectorSelector::ConnectorRequestHeader { + connector_request_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }; + assert_eq!( + selector.on_request(&http_request_with_header).unwrap(), + TEST_HEADER_VALUE.into() + ); + } + + #[rstest] + fn connector_request_header_defaulted(http_request: HttpRequest) { + let selector = ConnectorSelector::ConnectorRequestHeader { + connector_request_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }; + assert_eq!( + selector.on_request(&http_request).unwrap(), + "defaulted".into() + ); + } + + #[rstest] + fn connector_response_header(http_response_with_header: HttpResponse) { + let selector = ConnectorSelector::ConnectorResponseHeader { + connector_response_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }; + assert_eq!( + selector.on_response(&http_response_with_header).unwrap(), + TEST_HEADER_VALUE.into() + ); + } + + #[rstest] + fn connector_response_header_defaulted(http_response: HttpResponse) { + let selector = ConnectorSelector::ConnectorResponseHeader { + connector_response_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }; + assert_eq!( + selector.on_response(&http_response).unwrap(), + "defaulted".into() + ); + } + + #[rstest] + fn connector_response_status( + #[with(context(connector_info()), StatusCode::NOT_FOUND)] http_response: HttpResponse, + ) { + let selector = ConnectorSelector::ConnectorResponseStatus { + connector_response_status: ResponseStatus::Code, + }; + assert_eq!( + selector.on_response(&http_response).unwrap(), + opentelemetry::Value::I64(404) + ); + } + + #[rstest] + fn connector_http_method(http_request: HttpRequest) { + let selector = ConnectorSelector::ConnectorHttpMethod { + connector_http_method: true, + }; + assert_eq!( + selector.on_request(&http_request), + Some(HTTPMethod::Get.as_str().into()) + ); + } + + #[rstest] + fn connector_source_name(http_request: HttpRequest, http_response: HttpResponse) { + let selector = ConnectorSelector::ConnectorSource { + connector_source: ConnectorSource::Name, + }; + assert_eq!( + selector.on_request(&http_request), + Some(TEST_SOURCE_NAME.into()) + ); + assert_eq!( + selector.on_response(&http_response), + Some(TEST_SOURCE_NAME.into()) + ); + } + + #[rstest] + fn connector_url_template(http_request: HttpRequest, http_response: HttpResponse) { + let selector = ConnectorSelector::ConnectorUrlTemplate { + connector_url_template: true, + }; + assert_eq!( + selector.on_request(&http_request), + Some(TEST_URL_TEMPLATE.into()) + ); + assert_eq!( + selector.on_response(&http_response), + Some(TEST_URL_TEMPLATE.into()) + ); + } } diff --git a/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs b/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs index 47a13762d0..39fcf1f2de 100644 --- a/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs +++ b/apollo-router/src/plugins/telemetry/metrics/apollo/mod.rs @@ -190,7 +190,6 @@ mod test { use super::studio::SingleStatsReport; use super::*; use crate::context::OPERATION_KIND; - use crate::plugin::Plugin; use crate::plugin::PluginInit; use crate::plugins::subscription; use crate::plugins::telemetry::apollo; @@ -364,7 +363,7 @@ mod test { request_builder.header("accept", "multipart/mixed;subscriptionSpec=1.0"); } TestHarness::builder() - .extra_plugin(plugin) + .extra_private_plugin(plugin) .extra_plugin(create_subscription_plugin().await?) .build_router() .await? @@ -410,6 +409,7 @@ mod test { async fn create_plugin_with_apollo_config( apollo_config: apollo::Config, ) -> Result { + use crate::plugin::PluginPrivate; Telemetry::new(PluginInit::fake_new( config::Conf { apollo: apollo_config, @@ -421,6 +421,7 @@ mod test { } async fn create_subscription_plugin() -> Result { + use crate::plugin::PluginPrivate; subscription::Subscription::new(PluginInit::fake_new( subscription::SubscriptionConfig::default(), Default::default(), diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 1478261be5..c4cd09f54a 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -93,8 +93,8 @@ use crate::layers::ServiceBuilderExt; use crate::metrics::aggregation::MeterProviderType; use crate::metrics::filter::FilterMeterProvider; use crate::metrics::meter_provider; -use crate::plugin::Plugin; use crate::plugin::PluginInit; +use crate::plugin::PluginPrivate; use crate::plugins::telemetry::apollo::ForwardHeaders; use crate::plugins::telemetry::apollo_exporter::proto::reports::trace::node::Id::ResponseName; use crate::plugins::telemetry::apollo_exporter::proto::reports::StatsContext; @@ -103,6 +103,7 @@ use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; use crate::plugins::telemetry::config_new::cost::add_cost_attributes; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; +use crate::plugins::telemetry::config_new::instruments::ConnectorInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::DatadogId; @@ -133,8 +134,8 @@ use crate::plugins::telemetry::tracing::apollo_telemetry::APOLLO_PRIVATE_OPERATI use crate::plugins::telemetry::tracing::TracingConfigurator; use crate::plugins::telemetry::utils::TracingUtils; use crate::query_planner::OperationKind; -use crate::register_plugin; use crate::router_factory::Endpoint; +use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; use crate::services::execution; use crate::services::router; use crate::services::subgraph; @@ -205,6 +206,7 @@ pub(crate) struct Telemetry { router_custom_instruments: RwLock>>, supergraph_custom_instruments: RwLock>>, subgraph_custom_instruments: RwLock>>, + connector_custom_instruments: RwLock>>, cache_custom_instruments: RwLock>>, activation: Mutex, } @@ -264,6 +266,7 @@ struct BuiltinInstruments { router_custom_instruments: Arc>, supergraph_custom_instruments: Arc>, subgraph_custom_instruments: Arc>, + connector_custom_instruments: Arc>, cache_custom_instruments: Arc>, } @@ -273,12 +276,13 @@ fn create_builtin_instruments(config: &InstrumentsConfig) -> BuiltinInstruments router_custom_instruments: Arc::new(config.new_builtin_router_instruments()), supergraph_custom_instruments: Arc::new(config.new_builtin_supergraph_instruments()), subgraph_custom_instruments: Arc::new(config.new_builtin_subgraph_instruments()), + connector_custom_instruments: Arc::new(config.new_builtin_connector_instruments()), cache_custom_instruments: Arc::new(config.new_builtin_cache_instruments()), } } #[async_trait::async_trait] -impl Plugin for Telemetry { +impl PluginPrivate for Telemetry { type Config = config::Conf; async fn new(init: PluginInit) -> Result { @@ -308,7 +312,9 @@ impl Plugin for Telemetry { router_custom_instruments, supergraph_custom_instruments, subgraph_custom_instruments, + connector_custom_instruments, cache_custom_instruments, + .. } = create_builtin_instruments(&config.instrumentation.instruments); Ok(Telemetry { @@ -332,6 +338,7 @@ impl Plugin for Telemetry { router_custom_instruments: RwLock::new(router_custom_instruments), supergraph_custom_instruments: RwLock::new(supergraph_custom_instruments), subgraph_custom_instruments: RwLock::new(subgraph_custom_instruments), + connector_custom_instruments: RwLock::new(connector_custom_instruments), cache_custom_instruments: RwLock::new(cache_custom_instruments), sampling_filter_ratio, config: Arc::new(config), @@ -851,6 +858,55 @@ impl Plugin for Telemetry { .boxed() } + fn http_client_service( + &self, + _subgraph_name: &str, + service: crate::services::http::BoxService, + ) -> crate::services::http::BoxService { + let req_fn_config = self.config.clone(); + let static_connector_instruments = self.connector_custom_instruments.read().clone(); + ServiceBuilder::new() + .map_future_with_request_data( + move |http_request: &crate::services::http::HttpRequest| { + if http_request + .context + .contains_key(CONNECTOR_INFO_CONTEXT_KEY) + { + let custom_instruments = req_fn_config + .instrumentation + .instruments + .new_connector_instruments(static_connector_instruments.clone()); + custom_instruments.on_request(http_request); + (http_request.context.clone(), Some(custom_instruments)) + } else { + (http_request.context.clone(), None) + } + }, + move |(context, custom_instruments): (Context, Option), + f: BoxFuture< + 'static, + Result, + >| { + async move { + let result = f.await; + if let Some(custom_instruments) = custom_instruments { + match &result { + Ok(resp) => { + custom_instruments.on_response(resp); + } + Err(err) => { + custom_instruments.on_error(err, &context); + } + } + } + result + } + }, + ) + .service(service) + .boxed() + } + fn web_endpoints(&self) -> MultiMap { self.custom_endpoints.clone() } @@ -899,6 +955,7 @@ impl Telemetry { router_custom_instruments, supergraph_custom_instruments, subgraph_custom_instruments, + connector_custom_instruments, cache_custom_instruments, } = create_builtin_instruments(&self.config.instrumentation.instruments); @@ -906,6 +963,7 @@ impl Telemetry { *self.router_custom_instruments.write() = router_custom_instruments; *self.supergraph_custom_instruments.write() = supergraph_custom_instruments; *self.subgraph_custom_instruments.write() = subgraph_custom_instruments; + *self.connector_custom_instruments.write() = connector_custom_instruments; *self.cache_custom_instruments.write() = cache_custom_instruments; reload_fmt(create_fmt_layer(&self.config)); @@ -1980,7 +2038,7 @@ fn handle_error_internal>( } } -register_plugin!("apollo", "telemetry", Telemetry); +register_private_plugin!("apollo", "telemetry", Telemetry); fn request_ftv1(mut req: SubgraphRequest) -> SubgraphRequest { if req diff --git a/apollo-router/src/plugins/test.rs b/apollo-router/src/plugins/test.rs index 1593523015..76f7410412 100644 --- a/apollo-router/src/plugins/test.rs +++ b/apollo-router/src/plugins/test.rs @@ -65,12 +65,12 @@ use crate::Notify; /// You can pass in a configuration and a schema to the test harness. If you pass in a schema, the test harness will create a query planner and use the schema to extract subgraph schemas. /// /// -pub(crate) struct PluginTestHarness { +pub(crate) struct PluginTestHarness { plugin: Box, phantom: std::marker::PhantomData, } #[buildstructor::buildstructor] -impl PluginTestHarness { +impl PluginTestHarness { #[builder] pub(crate) async fn new<'a, 'b>(config: Option<&'a str>, schema: Option<&'b str>) -> Self { let factory = crate::plugin::plugins() diff --git a/apollo-router/src/services/connector_service.rs b/apollo-router/src/services/connector_service.rs index 706e7a2fda..3345fb58c8 100644 --- a/apollo-router/src/services/connector_service.rs +++ b/apollo-router/src/services/connector_service.rs @@ -10,8 +10,11 @@ use futures::future::BoxFuture; use indexmap::IndexMap; use opentelemetry::Key; use parking_lot::Mutex; +use serde::Deserialize; +use serde::Serialize; use tower::BoxError; use tower::ServiceExt; +use tracing::warn; use tracing::Instrument; use super::connect::BoxService; @@ -48,6 +51,7 @@ pub(crate) const APOLLO_CONNECTOR_SOURCE_NAME: Key = Key::from_static_str("apollo.connector.source.name"); pub(crate) const APOLLO_CONNECTOR_SOURCE_DETAIL: Key = Key::from_static_str("apollo.connector.source.detail"); +pub(crate) const CONNECTOR_INFO_CONTEXT_KEY: &str = "apollo_router::connector::info"; /// A service for executing connector requests. #[derive(Clone)] @@ -59,6 +63,26 @@ pub(crate) struct ConnectorService { pub(crate) connectors_by_service_name: Arc, Connector>>, } +/// Serializable information about a connector. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct ConnectorInfo { + pub(crate) subgraph_name: String, + pub(crate) source_name: Option, + pub(crate) http_method: String, + pub(crate) url_template: String, +} + +impl From<&Connector> for ConnectorInfo { + fn from(connector: &Connector) -> Self { + Self { + subgraph_name: connector.id.subgraph_name.to_string(), + source_name: connector.id.source_name.clone(), + http_method: connector.transport.method.as_str().to_string(), + url_template: connector.transport.connect_template.to_string(), + } + } +} + impl tower::Service for ConnectorService { type Response = ConnectResponse; type Error = BoxError; @@ -155,6 +179,12 @@ async fn execute( // inner result fails just that one task, but an `Err` on the outer result cancels all the // tasks and fails the whole operation. let context = context.clone(); + if context + .insert(CONNECTOR_INFO_CONTEXT_KEY, ConnectorInfo::from(connector)) + .is_err() + { + warn!("Failed to store connector info in context - instruments may be inaccurate"); + } let original_subgraph_name = original_subgraph_name.clone(); let request_limit = request_limit.clone(); async move { From fa18074fa73a2f8a7d3a0736302976d631ad5f80 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Thu, 26 Sep 2024 14:05:54 -0600 Subject: [PATCH 02/18] Include Http in type names to prevent future conflicts --- ...nfiguration__tests__schema_generation.snap | 164 ++++++++-------- .../telemetry/config_new/attributes.rs | 6 +- .../telemetry/config_new/instruments.rs | 55 +++--- .../plugins/telemetry/config_new/selectors.rs | 176 +++++++++--------- apollo-router/src/plugins/telemetry/mod.rs | 7 +- 5 files changed, 210 insertions(+), 198 deletions(-) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 5e2d2afc46..f465e3efd2 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -624,7 +624,7 @@ expression: "&schema" } ] }, - "Condition_for_ConnectorSelector": { + "Condition_for_ConnectorHttpSelector": { "oneOf": [ { "additionalProperties": false, @@ -632,8 +632,8 @@ expression: "&schema" "properties": { "eq": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" }, "maxItems": 2, "minItems": 2, @@ -651,8 +651,8 @@ expression: "&schema" "properties": { "gt": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" }, "maxItems": 2, "minItems": 2, @@ -670,8 +670,8 @@ expression: "&schema" "properties": { "lt": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" }, "maxItems": 2, "minItems": 2, @@ -688,8 +688,8 @@ expression: "&schema" "description": "A condition to check a selection against a selector.", "properties": { "exists": { - "$ref": "#/definitions/ConnectorSelector", - "description": "#/definitions/ConnectorSelector" + "$ref": "#/definitions/ConnectorHttpSelector", + "description": "#/definitions/ConnectorHttpSelector" } }, "required": [ @@ -703,8 +703,8 @@ expression: "&schema" "properties": { "all": { "items": { - "$ref": "#/definitions/Condition_for_ConnectorSelector", - "description": "#/definitions/Condition_for_ConnectorSelector" + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" }, "type": "array" } @@ -720,8 +720,8 @@ expression: "&schema" "properties": { "any": { "items": { - "$ref": "#/definitions/Condition_for_ConnectorSelector", - "description": "#/definitions/Condition_for_ConnectorSelector" + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" }, "type": "array" } @@ -736,8 +736,8 @@ expression: "&schema" "description": "The sub-condition must not be true", "properties": { "not": { - "$ref": "#/definitions/Condition_for_ConnectorSelector", - "description": "#/definitions/Condition_for_ConnectorSelector" + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" } }, "required": [ @@ -1917,7 +1917,7 @@ expression: "&schema" }, "type": "object" }, - "ConnectorAttributes": { + "ConnectorHttpAttributes": { "additionalProperties": false, "properties": { "connector.http.method": { @@ -1943,25 +1943,7 @@ expression: "&schema" }, "type": "object" }, - "ConnectorInstrumentsConfig": { - "additionalProperties": false, - "properties": { - "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" - }, - "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" - }, - "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" - } - }, - "type": "object" - }, - "ConnectorSelector": { + "ConnectorHttpSelector": { "anyOf": [ { "additionalProperties": false, @@ -1992,8 +1974,8 @@ expression: "&schema" { "additionalProperties": false, "properties": { - "connector_request_header": { - "description": "The name of a connector request header.", + "connector_http_request_header": { + "description": "The name of a connector HTTP request header.", "type": "string" }, "default": { @@ -2003,15 +1985,15 @@ expression: "&schema" } }, "required": [ - "connector_request_header" + "connector_http_request_header" ], "type": "object" }, { "additionalProperties": false, "properties": { - "connector_response_header": { - "description": "The name of a connector response header.", + "connector_http_response_header": { + "description": "The name of a connector HTTP response header.", "type": "string" }, "default": { @@ -2021,20 +2003,20 @@ expression: "&schema" } }, "required": [ - "connector_response_header" + "connector_http_response_header" ], "type": "object" }, { "additionalProperties": false, "properties": { - "connector_response_status": { + "connector_http_response_status": { "$ref": "#/definitions/ResponseStatus", "description": "#/definitions/ResponseStatus" } }, "required": [ - "connector_response_status" + "connector_http_response_status" ], "type": "object" }, @@ -2092,6 +2074,36 @@ expression: "&schema" } ] }, + "ConnectorHttpValue": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/ConnectorHttpSelector", + "description": "#/definitions/ConnectorHttpSelector" + } + ] + }, + "ConnectorInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + } + }, + "type": "object" + }, "ConnectorSource": { "oneOf": [ { @@ -2103,18 +2115,6 @@ expression: "&schema" } ] }, - "ConnectorValue": { - "anyOf": [ - { - "$ref": "#/definitions/Standard", - "description": "#/definitions/Standard" - }, - { - "$ref": "#/definitions/ConnectorSelector", - "description": "#/definitions/ConnectorSelector" - } - ] - }, "ConnectorsConfig": { "additionalProperties": false, "properties": { @@ -2346,7 +2346,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector": { "anyOf": [ { "type": "null" @@ -2358,8 +2358,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" } }, "required": [ @@ -3755,16 +3755,16 @@ expression: "&schema" ], "type": "object" }, - "Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue": { + "Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue": { "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" }, "condition": { - "$ref": "#/definitions/Condition_for_ConnectorSelector", - "description": "#/definitions/Condition_for_ConnectorSelector" + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" }, "description": { "description": "The description of the instrument.", @@ -3779,8 +3779,8 @@ expression: "&schema" "type": "string" }, "value": { - "$ref": "#/definitions/ConnectorValue", - "description": "#/definitions/ConnectorValue" + "$ref": "#/definitions/ConnectorHttpValue", + "description": "#/definitions/ConnectorHttpValue" } }, "required": [ @@ -3962,8 +3962,8 @@ expression: "&schema" "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "connector": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "default_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", @@ -5728,15 +5728,15 @@ expression: "&schema" }, "type": "object" }, - "SelectorOrValue_for_ConnectorSelector": { + "SelectorOrValue_for_ConnectorHttpSelector": { "anyOf": [ { "$ref": "#/definitions/AttributeValue", "description": "#/definitions/AttributeValue" }, { - "$ref": "#/definitions/ConnectorSelector", - "description": "#/definitions/ConnectorSelector" + "$ref": "#/definitions/ConnectorHttpSelector", + "description": "#/definitions/ConnectorHttpSelector" } ] }, @@ -7910,10 +7910,10 @@ expression: "&schema" } ] }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector": { "additionalProperties": { - "$ref": "#/definitions/ConnectorSelector", - "description": "#/definitions/ConnectorSelector" + "$ref": "#/definitions/ConnectorHttpSelector", + "description": "#/definitions/ConnectorHttpSelector" }, "properties": { "connector.http.method": { @@ -8480,23 +8480,23 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { - "$ref": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue", - "description": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue" + "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", + "description": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue" }, "properties": { "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" }, "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" }, "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" } }, "type": "object" diff --git a/apollo-router/src/plugins/telemetry/config_new/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/attributes.rs index fb876da4f7..e2bcd8fcbf 100644 --- a/apollo-router/src/plugins/telemetry/config_new/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/attributes.rs @@ -274,7 +274,7 @@ impl DefaultForLevel for SubgraphAttributes { #[derive(Deserialize, JsonSchema, Clone, Default, Debug)] #[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorAttributes { +pub(crate) struct ConnectorHttpAttributes { /// The name of the subgraph containing the connector /// Examples: /// @@ -313,7 +313,7 @@ pub(crate) struct ConnectorAttributes { connector_url_template: Option, } -impl DefaultForLevel for ConnectorAttributes { +impl DefaultForLevel for ConnectorHttpAttributes { fn defaults_for_level( &mut self, requirement_level: DefaultAttributeRequirementLevel, @@ -1211,7 +1211,7 @@ impl Selectors for SubgraphAttributes { } } -impl Selectors for ConnectorAttributes { +impl Selectors for ConnectorHttpAttributes { type Request = HttpRequest; type Response = HttpResponse; type EventResponse = (); diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index 7f0f65823f..e348bdc79a 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -20,7 +20,7 @@ use serde_json_bytes::Value; use tokio::time::Instant; use tower::BoxError; -use super::attributes::ConnectorAttributes; +use super::attributes::ConnectorHttpAttributes; use super::attributes::HttpServerAttributes; use super::cache::attributes::CacheAttributes; use super::cache::CacheInstruments; @@ -31,8 +31,8 @@ use super::graphql::GraphQLInstruments; use super::graphql::FIELD_EXECUTION; use super::graphql::FIELD_LENGTH; use super::selectors::CacheKind; -use super::selectors::ConnectorSelector; -use super::selectors::ConnectorValue; +use super::selectors::ConnectorHttpSelector; +use super::selectors::ConnectorHttpValue; use super::DefaultForLevel; use super::Selector; use crate::metrics; @@ -89,7 +89,7 @@ pub(crate) struct InstrumentsConfig { /// Connector service instruments. For more information see documentation on Router lifecycle. pub(crate) connector: Extendable< ConnectorInstrumentsConfig, - Instrument, + Instrument, >, /// GraphQL response field instruments. pub(crate) graphql: Extendable< @@ -698,7 +698,7 @@ impl InstrumentsConfig { pub(crate) fn new_connector_instruments( &self, static_instruments: Arc>, - ) -> ConnectorInstruments { + ) -> ConnectorHttpInstruments { let http_client_request_duration = self.connector .attributes @@ -767,8 +767,8 @@ impl InstrumentsConfig { ) ), attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorSelector::ConnectorRequestHeader { - connector_request_header: "content-length".to_string(), + selector: Some(Arc::new(ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: "content-length".to_string(), redact: None, default: None, })), @@ -808,8 +808,8 @@ impl InstrumentsConfig { ) ), attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorSelector::ConnectorResponseHeader { - connector_response_header: "content-length".to_string(), + selector: Some(Arc::new(ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: "content-length".to_string(), redact: None, default: None, })), @@ -818,7 +818,7 @@ impl InstrumentsConfig { }), } }); - ConnectorInstruments { + ConnectorHttpInstruments { http_client_request_duration, http_client_request_body_size, http_client_response_body_size, @@ -1381,17 +1381,17 @@ pub(crate) struct ConnectorInstrumentsConfig { /// Histogram of client request duration #[serde(rename = "http.client.request.duration")] http_client_request_duration: - DefaultedStandardInstrument>, + DefaultedStandardInstrument>, /// Histogram of client request body size #[serde(rename = "http.client.request.body.size")] http_client_request_body_size: - DefaultedStandardInstrument>, + DefaultedStandardInstrument>, /// Histogram of client response body size #[serde(rename = "http.client.response.body.size")] http_client_response_body_size: - DefaultedStandardInstrument>, + DefaultedStandardInstrument>, } impl DefaultForLevel for ConnectorInstrumentsConfig { @@ -2016,17 +2016,20 @@ impl Instrumented for SubgraphInstruments { } } -pub(crate) struct ConnectorInstruments { - http_client_request_duration: - Option>, - http_client_request_body_size: - Option>, - http_client_response_body_size: - Option>, - custom: ConnectorCustomInstruments, +pub(crate) struct ConnectorHttpInstruments { + http_client_request_duration: Option< + CustomHistogram, + >, + http_client_request_body_size: Option< + CustomHistogram, + >, + http_client_response_body_size: Option< + CustomHistogram, + >, + custom: ConnectorHttpCustomInstruments, } -impl Instrumented for ConnectorInstruments { +impl Instrumented for ConnectorHttpInstruments { type Request = HttpRequest; type Response = HttpResponse; type EventResponse = (); @@ -2095,12 +2098,12 @@ pub(crate) type SubgraphCustomInstruments = CustomInstruments< SubgraphValue, >; -pub(crate) type ConnectorCustomInstruments = CustomInstruments< +pub(crate) type ConnectorHttpCustomInstruments = CustomInstruments< HttpRequest, HttpResponse, - ConnectorAttributes, - ConnectorSelector, - ConnectorValue, + ConnectorHttpAttributes, + ConnectorHttpSelector, + ConnectorHttpValue, >; // ---------------- Counter ----------------------- diff --git a/apollo-router/src/plugins/telemetry/config_new/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/selectors.rs index ed48324105..feee845126 100644 --- a/apollo-router/src/plugins/telemetry/config_new/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/selectors.rs @@ -638,16 +638,16 @@ pub(crate) enum SubgraphSelector { #[derive(Deserialize, JsonSchema, Clone, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] -pub(crate) enum ConnectorValue { +pub(crate) enum ConnectorHttpValue { Standard(Standard), - Custom(ConnectorSelector), + Custom(ConnectorHttpSelector), } -impl From<&ConnectorValue> for InstrumentValue { - fn from(value: &ConnectorValue) -> Self { +impl From<&ConnectorHttpValue> for InstrumentValue { + fn from(value: &ConnectorHttpValue) -> Self { match value { - ConnectorValue::Standard(s) => InstrumentValue::Standard(s.clone()), - ConnectorValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), + ConnectorHttpValue::Standard(s) => InstrumentValue::Standard(s.clone()), + ConnectorHttpValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), } } } @@ -655,7 +655,7 @@ impl From<&ConnectorValue> for InstrumentValue { #[derive(Deserialize, JsonSchema, Clone, Derivative)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] #[derivative(Debug, PartialEq)] -pub(crate) enum ConnectorSelector { +pub(crate) enum ConnectorHttpSelector { SubgraphName { /// The subgraph name subgraph_name: bool, @@ -665,8 +665,8 @@ pub(crate) enum ConnectorSelector { connector_source: ConnectorSource, }, ConnectorRequestHeader { - /// The name of a connector request header. - connector_request_header: String, + /// The name of a connector HTTP request header. + connector_http_request_header: String, #[serde(skip)] #[allow(dead_code)] /// Optional redaction pattern. @@ -675,8 +675,8 @@ pub(crate) enum ConnectorSelector { default: Option, }, ConnectorResponseHeader { - /// The name of a connector response header. - connector_response_header: String, + /// The name of a connector HTTP response header. + connector_http_response_header: String, #[serde(skip)] #[allow(dead_code)] /// Optional redaction pattern. @@ -685,8 +685,8 @@ pub(crate) enum ConnectorSelector { default: Option, }, ConnectorResponseStatus { - /// The connector http response status code. - connector_response_status: ResponseStatus, + /// The connector HTTP response status code. + connector_http_response_status: ResponseStatus, }, ConnectorHttpMethod { /// The connector HTTP method. @@ -1812,7 +1812,7 @@ impl Selector for SubgraphSelector { } } -impl Selector for ConnectorSelector { +impl Selector for ConnectorHttpSelector { type Request = HttpRequest; type Response = HttpResponse; type EventResponse = (); @@ -1822,32 +1822,34 @@ impl Selector for ConnectorSelector { .context .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorSource { .. } => connector_info + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorHttpMethod { + ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorUrlTemplate { + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorRequestHeader { - connector_request_header, + ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: connector_request_header, default, .. } => request @@ -1857,7 +1859,7 @@ impl Selector for ConnectorSelector { .and_then(|h| Some(h.to_str().ok()?.to_string())) .or_else(|| default.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -1867,32 +1869,34 @@ impl Selector for ConnectorSelector { .context .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorSource { .. } => connector_info + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorHttpMethod { + ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorUrlTemplate { + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorResponseHeader { - connector_response_header, + ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: connector_response_header, default, .. } => response @@ -1902,8 +1906,8 @@ impl Selector for ConnectorSelector { .and_then(|h| Some(h.to_str().ok()?.to_string())) .or_else(|| default.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorResponseStatus { - connector_response_status: response_status, + ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: response_status, } => match response_status { ResponseStatus::Code => { Some(Value::I64(response.http_response.status().as_u16() as i64)) @@ -1914,7 +1918,7 @@ impl Selector for ConnectorSelector { .canonical_reason() .map(|reason| reason.into()), }, - ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -1922,39 +1926,41 @@ impl Selector for ConnectorSelector { fn on_error(&self, error: &BoxError, ctx: &Context) -> Option { let connector_info = ctx.get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorSource { .. } => connector_info + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorHttpMethod { + ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::ConnectorUrlTemplate { + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorSelector::Error { .. } => Some(error.to_string().into()), - ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorHttpSelector::Error { .. } => Some(error.to_string().into()), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } fn on_drop(&self) -> Option { match self { - ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -1963,35 +1969,35 @@ impl Selector for ConnectorSelector { match stage { Stage::Request => matches!( self, - ConnectorSelector::ConnectorRequestHeader { .. } - | ConnectorSelector::SubgraphName { .. } - | ConnectorSelector::ConnectorSource { .. } - | ConnectorSelector::ConnectorHttpMethod { .. } - | ConnectorSelector::ConnectorUrlTemplate { .. } - | ConnectorSelector::StaticField { .. } + ConnectorHttpSelector::ConnectorRequestHeader { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } ), Stage::Response => matches!( self, - ConnectorSelector::ConnectorResponseHeader { .. } - | ConnectorSelector::ConnectorResponseStatus { .. } - | ConnectorSelector::SubgraphName { .. } - | ConnectorSelector::ConnectorSource { .. } - | ConnectorSelector::ConnectorHttpMethod { .. } - | ConnectorSelector::ConnectorUrlTemplate { .. } - | ConnectorSelector::StaticField { .. } + ConnectorHttpSelector::ConnectorResponseHeader { .. } + | ConnectorHttpSelector::ConnectorResponseStatus { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } ), Stage::ResponseEvent => false, Stage::ResponseField => false, Stage::Error => matches!( self, - ConnectorSelector::Error { .. } - | ConnectorSelector::SubgraphName { .. } - | ConnectorSelector::ConnectorSource { .. } - | ConnectorSelector::ConnectorHttpMethod { .. } - | ConnectorSelector::ConnectorUrlTemplate { .. } - | ConnectorSelector::StaticField { .. } + ConnectorHttpSelector::Error { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } ), - Stage::Drop => matches!(self, ConnectorSelector::StaticField { .. }), + Stage::Drop => matches!(self, ConnectorHttpSelector::StaticField { .. }), } } } @@ -2032,7 +2038,7 @@ mod test { use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config_new::selectors::All; use crate::plugins::telemetry::config_new::selectors::CacheKind; - use crate::plugins::telemetry::config_new::selectors::ConnectorSelector; + use crate::plugins::telemetry::config_new::selectors::ConnectorHttpSelector; use crate::plugins::telemetry::config_new::selectors::ConnectorSource; use crate::plugins::telemetry::config_new::selectors::EntityType; use crate::plugins::telemetry::config_new::selectors::OperationKind; @@ -3920,7 +3926,7 @@ mod test { #[rstest] fn connector_static_field(http_response: HttpResponse) { - let selector = ConnectorSelector::StaticField { + let selector = ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.to_string().into(), }; assert_eq!( @@ -3932,7 +3938,7 @@ mod test { #[rstest] fn connector_subgraph_name(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorSelector::SubgraphName { + let selector = ConnectorHttpSelector::SubgraphName { subgraph_name: true, }; assert_eq!( @@ -3947,8 +3953,8 @@ mod test { #[rstest] fn connector_request_header(http_request_with_header: HttpRequest) { - let selector = ConnectorSelector::ConnectorRequestHeader { - connector_request_header: TEST_HEADER_NAME.to_string(), + let selector = ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), }; @@ -3960,8 +3966,8 @@ mod test { #[rstest] fn connector_request_header_defaulted(http_request: HttpRequest) { - let selector = ConnectorSelector::ConnectorRequestHeader { - connector_request_header: TEST_HEADER_NAME.to_string(), + let selector = ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), }; @@ -3973,8 +3979,8 @@ mod test { #[rstest] fn connector_response_header(http_response_with_header: HttpResponse) { - let selector = ConnectorSelector::ConnectorResponseHeader { - connector_response_header: TEST_HEADER_NAME.to_string(), + let selector = ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), }; @@ -3986,8 +3992,8 @@ mod test { #[rstest] fn connector_response_header_defaulted(http_response: HttpResponse) { - let selector = ConnectorSelector::ConnectorResponseHeader { - connector_response_header: TEST_HEADER_NAME.to_string(), + let selector = ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), }; @@ -4001,8 +4007,8 @@ mod test { fn connector_response_status( #[with(context(connector_info()), StatusCode::NOT_FOUND)] http_response: HttpResponse, ) { - let selector = ConnectorSelector::ConnectorResponseStatus { - connector_response_status: ResponseStatus::Code, + let selector = ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: ResponseStatus::Code, }; assert_eq!( selector.on_response(&http_response).unwrap(), @@ -4012,7 +4018,7 @@ mod test { #[rstest] fn connector_http_method(http_request: HttpRequest) { - let selector = ConnectorSelector::ConnectorHttpMethod { + let selector = ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method: true, }; assert_eq!( @@ -4023,7 +4029,7 @@ mod test { #[rstest] fn connector_source_name(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorSelector::ConnectorSource { + let selector = ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name, }; assert_eq!( @@ -4038,7 +4044,7 @@ mod test { #[rstest] fn connector_url_template(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorSelector::ConnectorUrlTemplate { + let selector = ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true, }; assert_eq!( diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index c4cd09f54a..2df7139a85 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -103,7 +103,7 @@ use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; use crate::plugins::telemetry::config_new::cost::add_cost_attributes; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; -use crate::plugins::telemetry::config_new::instruments::ConnectorInstruments; +use crate::plugins::telemetry::config_new::instruments::ConnectorHttpInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::DatadogId; @@ -882,7 +882,10 @@ impl PluginPrivate for Telemetry { (http_request.context.clone(), None) } }, - move |(context, custom_instruments): (Context, Option), + move |(context, custom_instruments): ( + Context, + Option, + ), f: BoxFuture< 'static, Result, From 4ab0f40798df738e8e743b57c0442c0aae7510c7 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Thu, 26 Sep 2024 15:49:15 -0600 Subject: [PATCH 03/18] More unit testing --- .../plugins/telemetry/config_new/selectors.rs | 225 +++++++++--------- 1 file changed, 110 insertions(+), 115 deletions(-) diff --git a/apollo-router/src/plugins/telemetry/config_new/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/selectors.rs index feee845126..e1f8b6399e 100644 --- a/apollo-router/src/plugins/telemetry/config_new/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/selectors.rs @@ -3913,10 +3913,13 @@ mod test { } #[fixture] - fn http_response_with_header(context: RouterContext) -> HttpResponse { + fn http_response_with_header( + context: RouterContext, + #[default(StatusCode::OK)] status_code: StatusCode, + ) -> HttpResponse { HttpResponse { http_response: http::Response::builder() - .status(StatusCode::OK) + .status(status_code) .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) .body("".into()) .unwrap(), @@ -3925,135 +3928,127 @@ mod test { } #[rstest] - fn connector_static_field(http_response: HttpResponse) { - let selector = ConnectorHttpSelector::StaticField { - r#static: TEST_STATIC.to_string().into(), - }; - assert_eq!( - selector.on_response(&http_response).unwrap(), - TEST_STATIC.into() - ); - assert_eq!(selector.on_drop().unwrap(), TEST_STATIC.into()); - } - - #[rstest] - fn connector_subgraph_name(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorHttpSelector::SubgraphName { - subgraph_name: true, - }; - assert_eq!( - selector.on_request(&http_request), - Some(TEST_SUBGRAPH_NAME.into()) - ); - assert_eq!( - selector.on_response(&http_response), - Some(TEST_SUBGRAPH_NAME.into()) - ); - } - - #[rstest] - fn connector_request_header(http_request_with_header: HttpRequest) { - let selector = ConnectorHttpSelector::ConnectorRequestHeader { + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::SubgraphName { subgraph_name: true }, + Some(TEST_SUBGRAPH_NAME.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, + Some(TEST_SOURCE_NAME.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, + Some(TEST_URL_TEMPLATE.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), - }; - assert_eq!( - selector.on_request(&http_request_with_header).unwrap(), - TEST_HEADER_VALUE.into() - ); - } - - #[rstest] - fn connector_request_header_defaulted(http_request: HttpRequest) { - let selector = ConnectorHttpSelector::ConnectorRequestHeader { + }, + Some("defaulted".into()), + )] + #[case( + http_request_with_header(context(connector_info())), + ConnectorHttpSelector::ConnectorRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, - default: Some("defaulted".into()), - }; - assert_eq!( - selector.on_request(&http_request).unwrap(), - "defaulted".into() - ); + default: None, + }, + Some(TEST_HEADER_VALUE.into()), + )] + fn connector_on_request( + #[case] http_request: HttpRequest, + #[case] selector: ConnectorHttpSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_request(&http_request)); } #[rstest] - fn connector_response_header(http_response_with_header: HttpResponse) { - let selector = ConnectorHttpSelector::ConnectorResponseHeader { + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::SubgraphName { subgraph_name: true }, + Some(TEST_SUBGRAPH_NAME.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, + Some(TEST_SOURCE_NAME.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, + Some(TEST_URL_TEMPLATE.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), - }; - assert_eq!( - selector.on_response(&http_response_with_header).unwrap(), - TEST_HEADER_VALUE.into() - ); - } - - #[rstest] - fn connector_response_header_defaulted(http_response: HttpResponse) { - let selector = ConnectorHttpSelector::ConnectorResponseHeader { + }, + Some("defaulted".into()), + )] + #[case( + http_response_with_header(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, - default: Some("defaulted".into()), - }; - assert_eq!( - selector.on_response(&http_response).unwrap(), - "defaulted".into() - ); - } - - #[rstest] - fn connector_response_status( - #[with(context(connector_info()), StatusCode::NOT_FOUND)] http_response: HttpResponse, - ) { - let selector = ConnectorHttpSelector::ConnectorResponseStatus { + default: None, + }, + Some(TEST_HEADER_VALUE.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::NOT_FOUND), + ConnectorHttpSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Code, - }; - assert_eq!( - selector.on_response(&http_response).unwrap(), - opentelemetry::Value::I64(404) - ); - } - - #[rstest] - fn connector_http_method(http_request: HttpRequest) { - let selector = ConnectorHttpSelector::ConnectorHttpMethod { - connector_http_method: true, - }; - assert_eq!( - selector.on_request(&http_request), - Some(HTTPMethod::Get.as_str().into()) - ); - } - - #[rstest] - fn connector_source_name(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorHttpSelector::ConnectorSource { - connector_source: ConnectorSource::Name, - }; - assert_eq!( - selector.on_request(&http_request), - Some(TEST_SOURCE_NAME.into()) - ); - assert_eq!( - selector.on_response(&http_response), - Some(TEST_SOURCE_NAME.into()) - ); + }, + Some(opentelemetry::Value::I64(404)), + )] + #[case( + http_response(context(connector_info()), StatusCode::NOT_FOUND), + ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: ResponseStatus::Reason, + }, + Some("Not Found".into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method: true }, + Some(HTTPMethod::Get.as_str().into()), + )] + fn connector_on_response( + #[case] http_response: HttpResponse, + #[case] selector: ConnectorHttpSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_response(&http_response)); } #[rstest] - fn connector_url_template(http_request: HttpRequest, http_response: HttpResponse) { - let selector = ConnectorHttpSelector::ConnectorUrlTemplate { - connector_url_template: true, - }; - assert_eq!( - selector.on_request(&http_request), - Some(TEST_URL_TEMPLATE.into()) - ); - assert_eq!( - selector.on_response(&http_response), - Some(TEST_URL_TEMPLATE.into()) - ); + #[case( + RouterSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + fn connector_on_drop( + #[case] selector: RouterSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_drop()); } } From cd048b16c36db966594a052d4d3aa6b2d2c2e371 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Mon, 30 Sep 2024 11:21:21 -0600 Subject: [PATCH 04/18] Move connectors telemetry code into its own modules --- ...nfiguration__tests__schema_generation.snap | 150 +++--- .../telemetry/config_new/attributes.rs | 135 ----- .../telemetry/config_new/connectors.rs | 3 + .../telemetry/config_new/connectors/http.rs | 5 + .../config_new/connectors/http/attributes.rs | 150 ++++++ .../config_new/connectors/http/instruments.rs | 328 ++++++++++++ .../config_new/connectors/http/selectors.rs | 505 ++++++++++++++++++ .../telemetry/config_new/instruments.rs | 298 +---------- .../src/plugins/telemetry/config_new/mod.rs | 1 + .../plugins/telemetry/config_new/selectors.rs | 482 ----------------- apollo-router/src/plugins/telemetry/mod.rs | 3 +- 11 files changed, 1078 insertions(+), 982 deletions(-) create mode 100644 apollo-router/src/plugins/telemetry/config_new/connectors.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/connectors/http.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index f465e3efd2..2ec937d533 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -2090,16 +2090,16 @@ expression: "&schema" "additionalProperties": false, "properties": { "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" }, "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" }, "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" } }, "type": "object" @@ -2346,7 +2346,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { "anyOf": [ { "type": "null" @@ -2358,8 +2358,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" } }, "required": [ @@ -2369,7 +2369,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { "anyOf": [ { "type": "null" @@ -2381,8 +2381,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" } }, "required": [ @@ -2392,7 +2392,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector": { "anyOf": [ { "type": "null" @@ -2404,8 +2404,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SubgraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" } }, "required": [ @@ -2415,7 +2415,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { "anyOf": [ { "type": "null" @@ -2427,8 +2427,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::SupergraphAttributes_apollo_router::plugins::telemetry::config_new::selectors::SupergraphSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" } }, "required": [ @@ -2438,7 +2438,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector": { "anyOf": [ { "type": "null" @@ -2450,8 +2450,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::attributes::CacheAttributes_apollo_router::plugins::telemetry::config_new::selectors::SubgraphSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" } }, "required": [ @@ -3759,8 +3759,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" }, "condition": { "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", @@ -3962,8 +3962,8 @@ expression: "&schema" "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "connector": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "default_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", @@ -7910,35 +7910,6 @@ expression: "&schema" } ] }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector": { - "additionalProperties": { - "$ref": "#/definitions/ConnectorHttpSelector", - "description": "#/definitions/ConnectorHttpSelector" - }, - "properties": { - "connector.http.method": { - "$ref": "#/definitions/StandardAttribute", - "description": "#/definitions/StandardAttribute", - "nullable": true - }, - "connector.source.name": { - "$ref": "#/definitions/StandardAttribute", - "description": "#/definitions/StandardAttribute", - "nullable": true - }, - "connector.url.template": { - "$ref": "#/definitions/StandardAttribute", - "description": "#/definitions/StandardAttribute", - "nullable": true - }, - "subgraph.name": { - "$ref": "#/definitions/StandardAttribute", - "description": "#/definitions/StandardAttribute", - "nullable": true - } - }, - "type": "object" - }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::RouterAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { "additionalProperties": { "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector", @@ -8366,6 +8337,56 @@ expression: "&schema" }, "type": "object" }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector": { + "additionalProperties": { + "$ref": "#/definitions/ConnectorHttpSelector", + "description": "#/definitions/ConnectorHttpSelector" + }, + "properties": { + "connector.http.method": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.source.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.url.template": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "subgraph.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + } + }, + "type": "object" + }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "additionalProperties": { + "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", + "description": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue" + }, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + } + }, + "type": "object" + }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { "additionalProperties": { "$ref": "#/definitions/Event_for_RouterAttributes_and_RouterSelector", @@ -8480,27 +8501,6 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { - "additionalProperties": { - "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", - "description": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue" - }, - "properties": { - "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" - }, - "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" - }, - "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::selectors::ConnectorHttpSelector" - } - }, - "type": "object" - }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::instruments::RouterInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { "$ref": "#/definitions/Instrument_for_RouterAttributes_and_RouterSelector_and_RouterValue", diff --git a/apollo-router/src/plugins/telemetry/config_new/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/attributes.rs index e2bcd8fcbf..53640cd87b 100644 --- a/apollo-router/src/plugins/telemetry/config_new/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/attributes.rs @@ -44,10 +44,6 @@ use crate::plugins::telemetry::config_new::DefaultForLevel; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otel::OpenTelemetrySpanExt; use crate::plugins::telemetry::otlp::TelemetryDataKind; -use crate::services::connector_service::ConnectorInfo; -use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; use crate::services::router; use crate::services::router::Request; use crate::services::subgraph; @@ -69,10 +65,6 @@ const NETWORK_LOCAL_PORT: Key = Key::from_static_str("network.local.port"); const NETWORK_PEER_ADDRESS: Key = Key::from_static_str("network.peer.address"); const NETWORK_PEER_PORT: Key = Key::from_static_str("network.peer.port"); -const CONNECTOR_SOURCE_NAME: Key = Key::from_static_str("connector.source.name"); -const CONNECTOR_HTTP_METHOD: Key = Key::from_static_str("connector.http.method"); -const CONNECTOR_URL_TEMPLATE: Key = Key::from_static_str("connector.url.template"); - #[derive(Deserialize, JsonSchema, Clone, Debug, Default, Copy)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub(crate) enum DefaultAttributeRequirementLevel { @@ -272,78 +264,6 @@ impl DefaultForLevel for SubgraphAttributes { } } -#[derive(Deserialize, JsonSchema, Clone, Default, Debug)] -#[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorHttpAttributes { - /// The name of the subgraph containing the connector - /// Examples: - /// - /// * posts - /// - /// Requirement level: Required - #[serde(rename = "subgraph.name")] - subgraph_name: Option, - - /// The name of the source for this connector, if defined - /// Examples: - /// - /// * posts_api - /// - /// Requirement level: Conditionally Required: If the connector has a source defined - #[serde(rename = "connector.source.name")] - connector_source_name: Option, - - /// The HTTP method for the connector - /// Examples: - /// - /// * GET - /// * POST - /// - /// Requirement level: Required - #[serde(rename = "connector.http.method")] - connector_http_method: Option, - - /// The connector URL template, relative to the source base URL if one is defined - /// Examples: - /// - /// * /users/{$this.id!}/post - /// - /// Requirement level: Required - #[serde(rename = "connector.url.template")] - connector_url_template: Option, -} - -impl DefaultForLevel for ConnectorHttpAttributes { - fn defaults_for_level( - &mut self, - requirement_level: DefaultAttributeRequirementLevel, - _kind: TelemetryDataKind, - ) { - match requirement_level { - DefaultAttributeRequirementLevel::Required => { - if self.subgraph_name.is_none() { - self.subgraph_name = Some(StandardAttribute::Bool(true)); - } - } - DefaultAttributeRequirementLevel::Recommended => { - if self.subgraph_name.is_none() { - self.subgraph_name = Some(StandardAttribute::Bool(true)); - } - if self.connector_source_name.is_none() { - self.connector_source_name = Some(StandardAttribute::Bool(true)); - } - if self.connector_http_method.is_none() { - self.connector_http_method = Some(StandardAttribute::Bool(true)); - } - if self.connector_url_template.is_none() { - self.connector_url_template = Some(StandardAttribute::Bool(true)); - } - } - DefaultAttributeRequirementLevel::None => {} - } - } -} - /// Common attributes for http server and client. /// See https://opentelemetry.io/docs/specs/semconv/http/http-spans/#common-attributes #[derive(Deserialize, JsonSchema, Clone, Default, Debug)] @@ -1211,61 +1131,6 @@ impl Selectors for SubgraphAttributes { } } -impl Selectors for ConnectorHttpAttributes { - type Request = HttpRequest; - type Response = HttpResponse; - type EventResponse = (); - - fn on_request(&self, request: &Self::Request) -> Vec { - let mut attrs = Vec::new(); - - if let Ok(Some(connector_info)) = request - .context - .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY) - { - if let Some(key) = self - .subgraph_name - .as_ref() - .and_then(|a| a.key(SUBGRAPH_NAME)) - { - attrs.push(KeyValue::new(key, connector_info.subgraph_name.to_string())); - } - if let Some(key) = self - .connector_source_name - .as_ref() - .and_then(|a| a.key(CONNECTOR_SOURCE_NAME)) - { - if let Some(source_name) = connector_info.source_name { - attrs.push(KeyValue::new(key, source_name.to_string())); - } - } - if let Some(key) = self - .connector_http_method - .as_ref() - .and_then(|a| a.key(CONNECTOR_HTTP_METHOD)) - { - attrs.push(KeyValue::new(key, connector_info.http_method)); - } - if let Some(key) = self - .connector_url_template - .as_ref() - .and_then(|a| a.key(CONNECTOR_URL_TEMPLATE)) - { - attrs.push(KeyValue::new(key, connector_info.url_template.to_string())); - } - } - attrs - } - - fn on_response(&self, _response: &Self::Response) -> Vec { - Vec::default() - } - - fn on_error(&self, _error: &BoxError, _ctx: &Context) -> Vec { - Vec::default() - } -} - #[cfg(test)] mod test { use std::net::SocketAddr; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors.rs b/apollo-router/src/plugins/telemetry/config_new/connectors.rs new file mode 100644 index 0000000000..4b4cecc413 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connectors.rs @@ -0,0 +1,3 @@ +//! Connectors telemetry. + +pub(crate) mod http; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http.rs b/apollo-router/src/plugins/telemetry/config_new/connectors/http.rs new file mode 100644 index 0000000000..c6b1e9ad11 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connectors/http.rs @@ -0,0 +1,5 @@ +//! Telemetry related to Connectors HTTP requests and responses. + +pub(crate) mod attributes; +pub(crate) mod instruments; +pub(crate) mod selectors; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs new file mode 100644 index 0000000000..e4adc16138 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs @@ -0,0 +1,150 @@ +//! Attributes related to Connectors. + +use opentelemetry_api::Key; +use opentelemetry_api::KeyValue; +use schemars::JsonSchema; +use serde::Deserialize; +use tower::BoxError; + +use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; +use crate::plugins::telemetry::config_new::attributes::StandardAttribute; +use crate::plugins::telemetry::config_new::attributes::SUBGRAPH_NAME; +use crate::plugins::telemetry::config_new::DefaultForLevel; +use crate::plugins::telemetry::config_new::Selectors; +use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::services::connector_service::ConnectorInfo; +use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; +use crate::Context; + +const CONNECTOR_HTTP_METHOD: Key = Key::from_static_str("connector.http.method"); +const CONNECTOR_SOURCE_NAME: Key = Key::from_static_str("connector.source.name"); +const CONNECTOR_URL_TEMPLATE: Key = Key::from_static_str("connector.url.template"); + +#[derive(Deserialize, JsonSchema, Clone, Default, Debug)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorHttpAttributes { + /// The name of the subgraph containing the connector + /// Examples: + /// + /// * posts + /// + /// Requirement level: Required + #[serde(rename = "subgraph.name")] + subgraph_name: Option, + + /// The name of the source for this connector, if defined + /// Examples: + /// + /// * posts_api + /// + /// Requirement level: Conditionally Required: If the connector has a source defined + #[serde(rename = "connector.source.name")] + connector_source_name: Option, + + /// The HTTP method for the connector + /// Examples: + /// + /// * GET + /// * POST + /// + /// Requirement level: Required + #[serde(rename = "connector.http.method")] + connector_http_method: Option, + + /// The connector URL template, relative to the source base URL if one is defined + /// Examples: + /// + /// * /users/{$this.id!}/post + /// + /// Requirement level: Required + #[serde(rename = "connector.url.template")] + connector_url_template: Option, +} + +impl DefaultForLevel for ConnectorHttpAttributes { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + _kind: TelemetryDataKind, + ) { + match requirement_level { + DefaultAttributeRequirementLevel::Required => { + if self.subgraph_name.is_none() { + self.subgraph_name = Some(StandardAttribute::Bool(true)); + } + } + DefaultAttributeRequirementLevel::Recommended => { + if self.subgraph_name.is_none() { + self.subgraph_name = Some(StandardAttribute::Bool(true)); + } + if self.connector_source_name.is_none() { + self.connector_source_name = Some(StandardAttribute::Bool(true)); + } + if self.connector_http_method.is_none() { + self.connector_http_method = Some(StandardAttribute::Bool(true)); + } + if self.connector_url_template.is_none() { + self.connector_url_template = Some(StandardAttribute::Bool(true)); + } + } + DefaultAttributeRequirementLevel::None => {} + } + } +} + +impl Selectors for ConnectorHttpAttributes { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) -> Vec { + let mut attrs = Vec::new(); + + if let Ok(Some(connector_info)) = request + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY) + { + if let Some(key) = self + .subgraph_name + .as_ref() + .and_then(|a| a.key(SUBGRAPH_NAME)) + { + attrs.push(KeyValue::new(key, connector_info.subgraph_name.to_string())); + } + if let Some(key) = self + .connector_source_name + .as_ref() + .and_then(|a| a.key(CONNECTOR_SOURCE_NAME)) + { + if let Some(source_name) = connector_info.source_name { + attrs.push(KeyValue::new(key, source_name.to_string())); + } + } + if let Some(key) = self + .connector_http_method + .as_ref() + .and_then(|a| a.key(CONNECTOR_HTTP_METHOD)) + { + attrs.push(KeyValue::new(key, connector_info.http_method)); + } + if let Some(key) = self + .connector_url_template + .as_ref() + .and_then(|a| a.key(CONNECTOR_URL_TEMPLATE)) + { + attrs.push(KeyValue::new(key, connector_info.url_template.to_string())); + } + } + attrs + } + + fn on_response(&self, _response: &Self::Response) -> Vec { + Vec::default() + } + + fn on_error(&self, _error: &BoxError, _ctx: &Context) -> Vec { + Vec::default() + } +} diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs new file mode 100644 index 0000000000..c5fc3fe1ff --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs @@ -0,0 +1,328 @@ +//! Instruments related to Connectors. + +use std::collections::HashMap; +use std::sync::Arc; + +use opentelemetry::metrics::MeterProvider; +use opentelemetry_api::metrics::Unit; +use parking_lot::Mutex; +use schemars::JsonSchema; +use serde::Deserialize; +use tokio::time::Instant; +use tower::BoxError; + +use crate::metrics; +use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; +use crate::plugins::telemetry::config_new::conditions::Condition; +use crate::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpValue; +use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::instruments::CustomHistogram; +use crate::plugins::telemetry::config_new::instruments::CustomHistogramInner; +use crate::plugins::telemetry::config_new::instruments::CustomInstruments; +use crate::plugins::telemetry::config_new::instruments::DefaultedStandardInstrument; +use crate::plugins::telemetry::config_new::instruments::Increment; +use crate::plugins::telemetry::config_new::instruments::Instrument; +use crate::plugins::telemetry::config_new::instruments::Instrumented; +use crate::plugins::telemetry::config_new::instruments::StaticInstrument; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_DURATION_METRIC; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC; +use crate::plugins::telemetry::config_new::instruments::METER_NAME; +use crate::plugins::telemetry::config_new::DefaultForLevel; +use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; +use crate::Context; + +#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorInstrumentsConfig { + /// Histogram of client request duration + #[serde(rename = "http.client.request.duration")] + http_client_request_duration: + DefaultedStandardInstrument>, + + /// Histogram of client request body size + #[serde(rename = "http.client.request.body.size")] + http_client_request_body_size: + DefaultedStandardInstrument>, + + /// Histogram of client response body size + #[serde(rename = "http.client.response.body.size")] + http_client_response_body_size: + DefaultedStandardInstrument>, +} + +impl DefaultForLevel for ConnectorInstrumentsConfig { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + kind: TelemetryDataKind, + ) { + self.http_client_request_duration + .defaults_for_level(requirement_level, kind); + self.http_client_request_body_size + .defaults_for_level(requirement_level, kind); + self.http_client_response_body_size + .defaults_for_level(requirement_level, kind); + } +} + +pub(crate) struct ConnectorHttpInstruments { + http_client_request_duration: Option< + CustomHistogram, + >, + http_client_request_body_size: Option< + CustomHistogram, + >, + http_client_response_body_size: Option< + CustomHistogram, + >, + custom: ConnectorHttpCustomInstruments, +} + +impl ConnectorHttpInstruments { + pub(crate) fn new( + config: &Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, + static_instruments: Arc>, + ) -> Self { + let http_client_request_duration = + config + .attributes + .http_client_request_duration + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_request_duration { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Duration(Instant::now()), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: None, + selectors, + updated: false, + }), + } + }); + let http_client_request_body_size = + config + .attributes + .http_client_request_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_request_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + let http_client_response_body_size = + config + .attributes + .http_client_response_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_response_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + ConnectorHttpInstruments { + http_client_request_duration, + http_client_request_body_size, + http_client_response_body_size, + custom: CustomInstruments::new(&config.custom, static_instruments), + } + } + + pub(crate) fn new_builtin( + config: &Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, + ) -> HashMap { + let meter = metrics::meter_provider().meter(METER_NAME); + let mut static_instruments = HashMap::with_capacity(3); + + if config.attributes.http_client_request_duration.is_enabled() { + static_instruments.insert( + HTTP_CLIENT_REQUEST_DURATION_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .with_unit(Unit::new("s")) + .with_description("Duration of HTTP client requests.") + .init(), + ), + ); + } + + if config.attributes.http_client_request_body_size.is_enabled() { + static_instruments.insert( + HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client request bodies.") + .init(), + ), + ); + } + + if config + .attributes + .http_client_response_body_size + .is_enabled() + { + static_instruments.insert( + HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client response bodies.") + .init(), + ), + ); + } + + static_instruments + } +} + +impl Instrumented for ConnectorHttpInstruments { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_request(request); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_request(request); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_request(request); + } + self.custom.on_request(request); + } + + fn on_response(&self, response: &Self::Response) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_response(response); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_response(response); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_response(response); + } + self.custom.on_response(response); + } + + fn on_error(&self, error: &BoxError, ctx: &Context) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_error(error, ctx); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_error(error, ctx); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_error(error, ctx); + } + self.custom.on_error(error, ctx); + } +} + +pub(crate) type ConnectorHttpCustomInstruments = CustomInstruments< + HttpRequest, + HttpResponse, + ConnectorHttpAttributes, + ConnectorHttpSelector, + ConnectorHttpValue, +>; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs new file mode 100644 index 0000000000..3cce6c8a38 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs @@ -0,0 +1,505 @@ +//! Selectors related to Connectors. + +use derivative::Derivative; +use opentelemetry_api::Value; +use schemars::JsonSchema; +use serde::Deserialize; +use tower::BoxError; + +use crate::plugins::telemetry::config::AttributeValue; +use crate::plugins::telemetry::config_new::instruments::InstrumentValue; +use crate::plugins::telemetry::config_new::instruments::Standard; +use crate::plugins::telemetry::config_new::selectors::ErrorRepr; +use crate::plugins::telemetry::config_new::selectors::ResponseStatus; +use crate::plugins::telemetry::config_new::Selector; +use crate::plugins::telemetry::config_new::Stage; +use crate::services::connector_service::ConnectorInfo; +use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; +use crate::Context; + +#[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)] +#[serde(deny_unknown_fields, rename_all = "snake_case")] +pub(crate) enum ConnectorSource { + /// The name of the connector source. + Name, +} + +#[derive(Deserialize, JsonSchema, Clone, Debug)] +#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] +pub(crate) enum ConnectorHttpValue { + Standard(Standard), + Custom(ConnectorHttpSelector), +} + +impl From<&ConnectorHttpValue> for InstrumentValue { + fn from(value: &ConnectorHttpValue) -> Self { + match value { + ConnectorHttpValue::Standard(s) => InstrumentValue::Standard(s.clone()), + ConnectorHttpValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), + } + } +} + +#[derive(Deserialize, JsonSchema, Clone, Derivative)] +#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] +#[derivative(Debug, PartialEq)] +pub(crate) enum ConnectorHttpSelector { + SubgraphName { + /// The subgraph name + subgraph_name: bool, + }, + ConnectorSource { + /// The connector source. + connector_source: ConnectorSource, + }, + ConnectorRequestHeader { + /// The name of a connector HTTP request header. + connector_http_request_header: String, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, + ConnectorResponseHeader { + /// The name of a connector HTTP response header. + connector_http_response_header: String, + #[serde(skip)] + #[allow(dead_code)] + /// Optional redaction pattern. + redact: Option, + /// Optional default value. + default: Option, + }, + ConnectorResponseStatus { + /// The connector HTTP response status code. + connector_http_response_status: ResponseStatus, + }, + ConnectorHttpMethod { + /// The connector HTTP method. + connector_http_method: bool, + }, + ConnectorUrlTemplate { + /// The connector URL template. + connector_url_template: bool, + }, + StaticField { + /// A static value + r#static: AttributeValue, + }, + Error { + /// Critical error if it happens + error: ErrorRepr, + }, +} + +impl Selector for ConnectorHttpSelector { + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) -> Option { + let connector_info = request + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: connector_request_header, + default, + .. + } => request + .http_request + .headers() + .get(connector_request_header) + .and_then(|h| Some(h.to_str().ok()?.to_string())) + .or_else(|| default.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_response(&self, response: &Self::Response) -> Option { + let connector_info = response + .context + .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: connector_response_header, + default, + .. + } => response + .http_response + .headers() + .get(connector_response_header) + .and_then(|h| Some(h.to_str().ok()?.to_string())) + .or_else(|| default.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: response_status, + } => match response_status { + ResponseStatus::Code => { + Some(Value::I64(response.http_response.status().as_u16() as i64)) + } + ResponseStatus::Reason => response + .http_response + .status() + .canonical_reason() + .map(|reason| reason.into()), + }, + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_error(&self, error: &BoxError, ctx: &Context) -> Option { + let connector_info = ctx.get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); + match self { + ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { + connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from) + } + ConnectorHttpSelector::ConnectorSource { .. } => connector_info + .ok() + .flatten() + .and_then(|info| info.source_name.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorHttpMethod { + connector_http_method, + } if *connector_http_method => connector_info + .ok() + .flatten() + .map(|info| info.http_method.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::ConnectorUrlTemplate { + connector_url_template, + } if *connector_url_template => connector_info + .ok() + .flatten() + .map(|info| info.url_template.clone()) + .map(opentelemetry::Value::from), + ConnectorHttpSelector::Error { .. } => Some(error.to_string().into()), + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn on_drop(&self) -> Option { + match self { + ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + _ => None, + } + } + + fn is_active(&self, stage: Stage) -> bool { + match stage { + Stage::Request => matches!( + self, + ConnectorHttpSelector::ConnectorRequestHeader { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } + ), + Stage::Response => matches!( + self, + ConnectorHttpSelector::ConnectorResponseHeader { .. } + | ConnectorHttpSelector::ConnectorResponseStatus { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } + ), + Stage::ResponseEvent => false, + Stage::ResponseField => false, + Stage::Error => matches!( + self, + ConnectorHttpSelector::Error { .. } + | ConnectorHttpSelector::SubgraphName { .. } + | ConnectorHttpSelector::ConnectorSource { .. } + | ConnectorHttpSelector::ConnectorHttpMethod { .. } + | ConnectorHttpSelector::ConnectorUrlTemplate { .. } + | ConnectorHttpSelector::StaticField { .. } + ), + Stage::Drop => matches!(self, ConnectorHttpSelector::StaticField { .. }), + } + } +} + +#[cfg(test)] +mod tests { + use apollo_federation::sources::connect::HTTPMethod; + use http::StatusCode; + use rstest::fixture; + use rstest::rstest; + + use super::ConnectorSource; + use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; + use crate::plugins::telemetry::config_new::selectors::ResponseStatus; + use crate::plugins::telemetry::config_new::selectors::RouterSelector; + use crate::plugins::telemetry::config_new::Selector; + use crate::services::connector_service::ConnectorInfo; + use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; + use crate::services::http::HttpRequest; + use crate::services::http::HttpResponse; + use crate::Context; + + const TEST_SUBGRAPH_NAME: &str = "test_subgraph_name"; + const TEST_SOURCE_NAME: &str = "test_source_name"; + const TEST_URL_TEMPLATE: &str = "/test"; + const TEST_HEADER_NAME: &str = "test_header_name"; + const TEST_HEADER_VALUE: &str = "test_header_value"; + const TEST_STATIC: &str = "test_static"; + + #[fixture] + fn connector_info() -> ConnectorInfo { + ConnectorInfo { + subgraph_name: TEST_SUBGRAPH_NAME.to_string(), + source_name: Some(TEST_SOURCE_NAME.to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: TEST_URL_TEMPLATE.to_string(), + } + } + + #[fixture] + fn context(connector_info: ConnectorInfo) -> Context { + let context = Context::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + context + } + + #[fixture] + fn http_request(context: Context) -> HttpRequest { + HttpRequest { + http_request: http::Request::builder().body("".into()).unwrap(), + context, + } + } + + #[fixture] + fn http_request_with_header(context: Context) -> HttpRequest { + HttpRequest { + http_request: http::Request::builder() + .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) + .body("".into()) + .unwrap(), + context, + } + } + + #[fixture] + fn http_response( + context: Context, + #[default(StatusCode::OK)] status_code: StatusCode, + ) -> HttpResponse { + HttpResponse { + http_response: http::Response::builder() + .status(status_code) + .body("".into()) + .unwrap(), + context, + } + } + + #[fixture] + fn http_response_with_header( + context: Context, + #[default(StatusCode::OK)] status_code: StatusCode, + ) -> HttpResponse { + HttpResponse { + http_response: http::Response::builder() + .status(status_code) + .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) + .body("".into()) + .unwrap(), + context, + } + } + + #[rstest] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::SubgraphName { subgraph_name: true }, + Some(TEST_SUBGRAPH_NAME.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, + Some(TEST_SOURCE_NAME.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, + Some(TEST_URL_TEMPLATE.into()), + )] + #[case( + http_request(context(connector_info())), + ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }, + Some("defaulted".into()), + )] + #[case( + http_request_with_header(context(connector_info())), + ConnectorHttpSelector::ConnectorRequestHeader { + connector_http_request_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: None, + }, + Some(TEST_HEADER_VALUE.into()), + )] + fn connector_on_request( + #[case] http_request: HttpRequest, + #[case] selector: ConnectorHttpSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_request(&http_request)); + } + + #[rstest] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::SubgraphName { subgraph_name: true }, + Some(TEST_SUBGRAPH_NAME.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, + Some(TEST_SOURCE_NAME.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, + Some(TEST_URL_TEMPLATE.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: Some("defaulted".into()), + }, + Some("defaulted".into()), + )] + #[case( + http_response_with_header(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorResponseHeader { + connector_http_response_header: TEST_HEADER_NAME.to_string(), + redact: None, + default: None, + }, + Some(TEST_HEADER_VALUE.into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::NOT_FOUND), + ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: ResponseStatus::Code, + }, + Some(opentelemetry::Value::I64(404)), + )] + #[case( + http_response(context(connector_info()), StatusCode::NOT_FOUND), + ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: ResponseStatus::Reason, + }, + Some("Not Found".into()), + )] + #[case( + http_response(context(connector_info()), StatusCode::OK), + ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method: true }, + Some(HTTPMethod::Get.as_str().into()), + )] + fn connector_on_response( + #[case] http_response: HttpResponse, + #[case] selector: ConnectorHttpSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_response(&http_response)); + } + + #[rstest] + #[case( + RouterSelector::StaticField { r#static: TEST_STATIC.into() }, + Some(TEST_STATIC.into()), + )] + fn connector_on_drop( + #[case] selector: RouterSelector, + #[case] expected: Option, + ) { + assert_eq!(expected, selector.on_drop()); + } +} diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index e348bdc79a..df0f225e8d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -20,7 +20,6 @@ use serde_json_bytes::Value; use tokio::time::Instant; use tower::BoxError; -use super::attributes::ConnectorHttpAttributes; use super::attributes::HttpServerAttributes; use super::cache::attributes::CacheAttributes; use super::cache::CacheInstruments; @@ -31,8 +30,6 @@ use super::graphql::GraphQLInstruments; use super::graphql::FIELD_EXECUTION; use super::graphql::FIELD_LENGTH; use super::selectors::CacheKind; -use super::selectors::ConnectorHttpSelector; -use super::selectors::ConnectorHttpValue; use super::DefaultForLevel; use super::Selector; use crate::metrics; @@ -41,6 +38,11 @@ use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; use crate::plugins::telemetry::config_new::conditions::Condition; +use crate::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connectors::http::instruments::ConnectorHttpInstruments; +use crate::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig; +use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpValue; use crate::plugins::telemetry::config_new::cost::CostInstruments; use crate::plugins::telemetry::config_new::cost::CostInstrumentsConfig; use crate::plugins::telemetry::config_new::extendable::Extendable; @@ -56,8 +58,6 @@ use crate::plugins::telemetry::config_new::selectors::SupergraphSelector; use crate::plugins::telemetry::config_new::selectors::SupergraphValue; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otlp::TelemetryDataKind; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; @@ -108,9 +108,9 @@ const HTTP_SERVER_REQUEST_BODY_SIZE_METRIC: &str = "http.server.request.body.siz const HTTP_SERVER_RESPONSE_BODY_SIZE_METRIC: &str = "http.server.response.body.size"; const HTTP_SERVER_ACTIVE_REQUESTS: &str = "http.server.active_requests"; -const HTTP_CLIENT_REQUEST_DURATION_METRIC: &str = "http.client.request.duration"; -const HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC: &str = "http.client.request.body.size"; -const HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC: &str = "http.client.response.body.size"; +pub(super) const HTTP_CLIENT_REQUEST_DURATION_METRIC: &str = "http.client.request.duration"; +pub(super) const HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC: &str = "http.client.request.body.size"; +pub(super) const HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC: &str = "http.client.response.body.size"; impl InstrumentsConfig { pub(crate) fn validate(&self) -> Result<(), String> { @@ -699,190 +699,12 @@ impl InstrumentsConfig { &self, static_instruments: Arc>, ) -> ConnectorHttpInstruments { - let http_client_request_duration = - self.connector - .attributes - .http_client_request_duration - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &self.connector.attributes.http_client_request_duration { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Duration(Instant::now()), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_REQUEST_DURATION_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: None, - selectors, - updated: false, - }), - } - }); - let http_client_request_body_size = - self.connector - .attributes - .http_client_request_body_size - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &self.connector.attributes.http_client_request_body_size { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Custom(None), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorHttpSelector::ConnectorRequestHeader { - connector_http_request_header: "content-length".to_string(), - redact: None, - default: None, - })), - selectors, - updated: false, - }), - } - }); - let http_client_response_body_size = - self.connector - .attributes - .http_client_response_body_size - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &self.connector.attributes.http_client_response_body_size { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Custom(None), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorHttpSelector::ConnectorResponseHeader { - connector_http_response_header: "content-length".to_string(), - redact: None, - default: None, - })), - selectors, - updated: false, - }), - } - }); - ConnectorHttpInstruments { - http_client_request_duration, - http_client_request_body_size, - http_client_response_body_size, - custom: CustomInstruments::new(&self.connector.custom, static_instruments), - } + ConnectorHttpInstruments::new(&self.connector, static_instruments) } pub(crate) fn new_builtin_connector_instruments(&self) -> HashMap { let meter = metrics::meter_provider().meter(METER_NAME); - let mut static_instruments = HashMap::with_capacity(3); - - if self - .connector - .attributes - .http_client_request_duration - .is_enabled() - { - static_instruments.insert( - HTTP_CLIENT_REQUEST_DURATION_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_REQUEST_DURATION_METRIC) - .with_unit(Unit::new("s")) - .with_description("Duration of HTTP client requests.") - .init(), - ), - ); - } - - if self - .connector - .attributes - .http_client_request_body_size - .is_enabled() - { - static_instruments.insert( - HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) - .with_unit(Unit::new("By")) - .with_description("Size of HTTP client request bodies.") - .init(), - ), - ); - } - - if self - .connector - .attributes - .http_client_response_body_size - .is_enabled() - { - static_instruments.insert( - HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) - .with_unit(Unit::new("By")) - .with_description("Size of HTTP client response bodies.") - .init(), - ), - ); - } + let mut static_instruments = ConnectorHttpInstruments::new_builtin(&self.connector); for (instrument_name, instrument) in &self.connector.custom { match instrument.ty { @@ -1375,40 +1197,6 @@ impl DefaultForLevel for SubgraphInstrumentsConfig { } } -#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] -#[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorInstrumentsConfig { - /// Histogram of client request duration - #[serde(rename = "http.client.request.duration")] - http_client_request_duration: - DefaultedStandardInstrument>, - - /// Histogram of client request body size - #[serde(rename = "http.client.request.body.size")] - http_client_request_body_size: - DefaultedStandardInstrument>, - - /// Histogram of client response body size - #[serde(rename = "http.client.response.body.size")] - http_client_response_body_size: - DefaultedStandardInstrument>, -} - -impl DefaultForLevel for ConnectorInstrumentsConfig { - fn defaults_for_level( - &mut self, - requirement_level: DefaultAttributeRequirementLevel, - kind: TelemetryDataKind, - ) { - self.http_client_request_duration - .defaults_for_level(requirement_level, kind); - self.http_client_request_body_size - .defaults_for_level(requirement_level, kind); - self.http_client_response_body_size - .defaults_for_level(requirement_level, kind); - } -} - #[derive(Clone, Deserialize, JsonSchema, Debug)] #[serde(deny_unknown_fields)] pub(crate) struct Instrument @@ -2016,64 +1804,6 @@ impl Instrumented for SubgraphInstruments { } } -pub(crate) struct ConnectorHttpInstruments { - http_client_request_duration: Option< - CustomHistogram, - >, - http_client_request_body_size: Option< - CustomHistogram, - >, - http_client_response_body_size: Option< - CustomHistogram, - >, - custom: ConnectorHttpCustomInstruments, -} - -impl Instrumented for ConnectorHttpInstruments { - type Request = HttpRequest; - type Response = HttpResponse; - type EventResponse = (); - - fn on_request(&self, request: &Self::Request) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_request(request); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_request(request); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_request(request); - } - self.custom.on_request(request); - } - - fn on_response(&self, response: &Self::Response) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_response(response); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_response(response); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_response(response); - } - self.custom.on_response(response); - } - - fn on_error(&self, error: &BoxError, ctx: &Context) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_error(error, ctx); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_error(error, ctx); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_error(error, ctx); - } - self.custom.on_error(error, ctx); - } -} - pub(crate) type RouterCustomInstruments = CustomInstruments< router::Request, router::Response, @@ -2098,14 +1828,6 @@ pub(crate) type SubgraphCustomInstruments = CustomInstruments< SubgraphValue, >; -pub(crate) type ConnectorHttpCustomInstruments = CustomInstruments< - HttpRequest, - HttpResponse, - ConnectorHttpAttributes, - ConnectorHttpSelector, - ConnectorHttpValue, ->; - // ---------------- Counter ----------------------- #[derive(Debug, Clone)] pub(crate) enum Increment { diff --git a/apollo-router/src/plugins/telemetry/config_new/mod.rs b/apollo-router/src/plugins/telemetry/config_new/mod.rs index 082d0a438e..ed30b2eb60 100644 --- a/apollo-router/src/plugins/telemetry/config_new/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod conditions; pub(crate) mod cache; mod conditional; +pub(crate) mod connectors; pub(crate) mod cost; pub(crate) mod events; mod experimental_when_header; diff --git a/apollo-router/src/plugins/telemetry/config_new/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/selectors.rs index e1f8b6399e..8c9c8dde7a 100644 --- a/apollo-router/src/plugins/telemetry/config_new/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/selectors.rs @@ -6,7 +6,6 @@ use serde::Deserialize; use serde_json_bytes::path::JsonPathInst; use serde_json_bytes::ByteString; use sha2::Digest; -use tower::BoxError; use crate::context::CONTAINS_GRAPHQL_ERROR; use crate::context::OPERATION_KIND; @@ -24,13 +23,8 @@ use crate::plugins::telemetry::config_new::instruments::InstrumentValue; use crate::plugins::telemetry::config_new::instruments::Standard; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::Selector; -use crate::plugins::telemetry::config_new::Stage; use crate::plugins::telemetry::config_new::ToOtelValue; use crate::query_planner::APOLLO_OPERATION_ID; -use crate::services::connector_service::ConnectorInfo; -use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; @@ -95,13 +89,6 @@ pub(crate) enum OperationKind { String, } -#[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)] -#[serde(deny_unknown_fields, rename_all = "snake_case")] -pub(crate) enum ConnectorSource { - /// The name of the connector source. - Name, -} - #[derive(Deserialize, JsonSchema, Clone, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] pub(crate) enum RouterValue { @@ -636,76 +623,6 @@ pub(crate) enum SubgraphSelector { }, } -#[derive(Deserialize, JsonSchema, Clone, Debug)] -#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] -pub(crate) enum ConnectorHttpValue { - Standard(Standard), - Custom(ConnectorHttpSelector), -} - -impl From<&ConnectorHttpValue> for InstrumentValue { - fn from(value: &ConnectorHttpValue) -> Self { - match value { - ConnectorHttpValue::Standard(s) => InstrumentValue::Standard(s.clone()), - ConnectorHttpValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), - } - } -} - -#[derive(Deserialize, JsonSchema, Clone, Derivative)] -#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] -#[derivative(Debug, PartialEq)] -pub(crate) enum ConnectorHttpSelector { - SubgraphName { - /// The subgraph name - subgraph_name: bool, - }, - ConnectorSource { - /// The connector source. - connector_source: ConnectorSource, - }, - ConnectorRequestHeader { - /// The name of a connector HTTP request header. - connector_http_request_header: String, - #[serde(skip)] - #[allow(dead_code)] - /// Optional redaction pattern. - redact: Option, - /// Optional default value. - default: Option, - }, - ConnectorResponseHeader { - /// The name of a connector HTTP response header. - connector_http_response_header: String, - #[serde(skip)] - #[allow(dead_code)] - /// Optional redaction pattern. - redact: Option, - /// Optional default value. - default: Option, - }, - ConnectorResponseStatus { - /// The connector HTTP response status code. - connector_http_response_status: ResponseStatus, - }, - ConnectorHttpMethod { - /// The connector HTTP method. - connector_http_method: bool, - }, - ConnectorUrlTemplate { - /// The connector URL template. - connector_url_template: bool, - }, - StaticField { - /// A static value - r#static: AttributeValue, - }, - Error { - /// Critical error if it happens - error: ErrorRepr, - }, -} - #[derive(Deserialize, JsonSchema, Clone, PartialEq, Debug)] #[serde(rename_all = "snake_case", untagged)] pub(crate) enum EntityType { @@ -1812,202 +1729,11 @@ impl Selector for SubgraphSelector { } } -impl Selector for ConnectorHttpSelector { - type Request = HttpRequest; - type Response = HttpResponse; - type EventResponse = (); - - fn on_request(&self, request: &Self::Request) -> Option { - let connector_info = request - .context - .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); - match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info - .ok() - .flatten() - .and_then(|info| info.source_name.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { - connector_http_method, - } if *connector_http_method => connector_info - .ok() - .flatten() - .map(|info| info.http_method.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { - connector_url_template, - } if *connector_url_template => connector_info - .ok() - .flatten() - .map(|info| info.url_template.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorRequestHeader { - connector_http_request_header: connector_request_header, - default, - .. - } => request - .http_request - .headers() - .get(connector_request_header) - .and_then(|h| Some(h.to_str().ok()?.to_string())) - .or_else(|| default.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), - _ => None, - } - } - - fn on_response(&self, response: &Self::Response) -> Option { - let connector_info = response - .context - .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); - match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info - .ok() - .flatten() - .and_then(|info| info.source_name.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { - connector_http_method, - } if *connector_http_method => connector_info - .ok() - .flatten() - .map(|info| info.http_method.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { - connector_url_template, - } if *connector_url_template => connector_info - .ok() - .flatten() - .map(|info| info.url_template.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorResponseHeader { - connector_http_response_header: connector_response_header, - default, - .. - } => response - .http_response - .headers() - .get(connector_response_header) - .and_then(|h| Some(h.to_str().ok()?.to_string())) - .or_else(|| default.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorResponseStatus { - connector_http_response_status: response_status, - } => match response_status { - ResponseStatus::Code => { - Some(Value::I64(response.http_response.status().as_u16() as i64)) - } - ResponseStatus::Reason => response - .http_response - .status() - .canonical_reason() - .map(|reason| reason.into()), - }, - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), - _ => None, - } - } - - fn on_error(&self, error: &BoxError, ctx: &Context) -> Option { - let connector_info = ctx.get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); - match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info - .ok() - .flatten() - .and_then(|info| info.source_name.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { - connector_http_method, - } if *connector_http_method => connector_info - .ok() - .flatten() - .map(|info| info.http_method.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { - connector_url_template, - } if *connector_url_template => connector_info - .ok() - .flatten() - .map(|info| info.url_template.clone()) - .map(opentelemetry::Value::from), - ConnectorHttpSelector::Error { .. } => Some(error.to_string().into()), - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), - _ => None, - } - } - - fn on_drop(&self) -> Option { - match self { - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), - _ => None, - } - } - - fn is_active(&self, stage: Stage) -> bool { - match stage { - Stage::Request => matches!( - self, - ConnectorHttpSelector::ConnectorRequestHeader { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } - ), - Stage::Response => matches!( - self, - ConnectorHttpSelector::ConnectorResponseHeader { .. } - | ConnectorHttpSelector::ConnectorResponseStatus { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } - ), - Stage::ResponseEvent => false, - Stage::ResponseField => false, - Stage::Error => matches!( - self, - ConnectorHttpSelector::Error { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } - ), - Stage::Drop => matches!(self, ConnectorHttpSelector::StaticField { .. }), - } - } -} - #[cfg(test)] mod test { use std::str::FromStr; use std::sync::Arc; - use apollo_federation::sources::connect::HTTPMethod; use http::StatusCode; use opentelemetry::baggage::BaggageExt; use opentelemetry::trace::SpanContext; @@ -2019,8 +1745,6 @@ mod test { use opentelemetry::Context; use opentelemetry::KeyValue; use opentelemetry_api::StringValue; - use rstest::fixture; - use rstest::rstest; use serde_json::json; use serde_json_bytes::path::JsonPathInst; use tower::BoxError; @@ -2028,7 +1752,6 @@ mod test { use tracing::subscriber; use tracing_subscriber::layer::SubscriberExt; - use crate::context::Context as RouterContext; use crate::context::OPERATION_KIND; use crate::context::OPERATION_NAME; use crate::graphql; @@ -2038,8 +1761,6 @@ mod test { use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config_new::selectors::All; use crate::plugins::telemetry::config_new::selectors::CacheKind; - use crate::plugins::telemetry::config_new::selectors::ConnectorHttpSelector; - use crate::plugins::telemetry::config_new::selectors::ConnectorSource; use crate::plugins::telemetry::config_new::selectors::EntityType; use crate::plugins::telemetry::config_new::selectors::OperationKind; use crate::plugins::telemetry::config_new::selectors::OperationName; @@ -2053,10 +1774,6 @@ mod test { use crate::plugins::telemetry::config_new::Selector; use crate::plugins::telemetry::otel; use crate::query_planner::APOLLO_OPERATION_ID; - use crate::services::connector_service::ConnectorInfo; - use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; - use crate::services::http::HttpRequest; - use crate::services::http::HttpResponse; use crate::services::FIRST_EVENT_CONTEXT_KEY; use crate::spec::operation_limits::OperationLimits; @@ -3852,203 +3569,4 @@ mod test { Some("default".into()) ); } - - const TEST_SUBGRAPH_NAME: &str = "test_subgraph_name"; - const TEST_SOURCE_NAME: &str = "test_source_name"; - const TEST_URL_TEMPLATE: &str = "/test"; - const TEST_HEADER_NAME: &str = "test_header_name"; - const TEST_HEADER_VALUE: &str = "test_header_value"; - const TEST_STATIC: &str = "test_static"; - - #[fixture] - fn connector_info() -> ConnectorInfo { - ConnectorInfo { - subgraph_name: TEST_SUBGRAPH_NAME.to_string(), - source_name: Some(TEST_SOURCE_NAME.to_string()), - http_method: HTTPMethod::Get.as_str().to_string(), - url_template: TEST_URL_TEMPLATE.to_string(), - } - } - - #[fixture] - fn context(connector_info: ConnectorInfo) -> RouterContext { - let context = RouterContext::default(); - context - .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) - .unwrap(); - context - } - - #[fixture] - fn http_request(context: RouterContext) -> HttpRequest { - HttpRequest { - http_request: http::Request::builder().body("".into()).unwrap(), - context, - } - } - - #[fixture] - fn http_request_with_header(context: RouterContext) -> HttpRequest { - HttpRequest { - http_request: http::Request::builder() - .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) - .body("".into()) - .unwrap(), - context, - } - } - - #[fixture] - fn http_response( - context: RouterContext, - #[default(StatusCode::OK)] status_code: StatusCode, - ) -> HttpResponse { - HttpResponse { - http_response: http::Response::builder() - .status(status_code) - .body("".into()) - .unwrap(), - context, - } - } - - #[fixture] - fn http_response_with_header( - context: RouterContext, - #[default(StatusCode::OK)] status_code: StatusCode, - ) -> HttpResponse { - HttpResponse { - http_response: http::Response::builder() - .status(status_code) - .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) - .body("".into()) - .unwrap(), - context, - } - } - - #[rstest] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::SubgraphName { subgraph_name: true }, - Some(TEST_SUBGRAPH_NAME.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, - Some(TEST_SOURCE_NAME.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, - Some(TEST_URL_TEMPLATE.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorRequestHeader { - connector_http_request_header: TEST_HEADER_NAME.to_string(), - redact: None, - default: Some("defaulted".into()), - }, - Some("defaulted".into()), - )] - #[case( - http_request_with_header(context(connector_info())), - ConnectorHttpSelector::ConnectorRequestHeader { - connector_http_request_header: TEST_HEADER_NAME.to_string(), - redact: None, - default: None, - }, - Some(TEST_HEADER_VALUE.into()), - )] - fn connector_on_request( - #[case] http_request: HttpRequest, - #[case] selector: ConnectorHttpSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_request(&http_request)); - } - - #[rstest] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::SubgraphName { subgraph_name: true }, - Some(TEST_SUBGRAPH_NAME.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, - Some(TEST_SOURCE_NAME.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, - Some(TEST_URL_TEMPLATE.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorResponseHeader { - connector_http_response_header: TEST_HEADER_NAME.to_string(), - redact: None, - default: Some("defaulted".into()), - }, - Some("defaulted".into()), - )] - #[case( - http_response_with_header(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorResponseHeader { - connector_http_response_header: TEST_HEADER_NAME.to_string(), - redact: None, - default: None, - }, - Some(TEST_HEADER_VALUE.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::NOT_FOUND), - ConnectorHttpSelector::ConnectorResponseStatus { - connector_http_response_status: ResponseStatus::Code, - }, - Some(opentelemetry::Value::I64(404)), - )] - #[case( - http_response(context(connector_info()), StatusCode::NOT_FOUND), - ConnectorHttpSelector::ConnectorResponseStatus { - connector_http_response_status: ResponseStatus::Reason, - }, - Some("Not Found".into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method: true }, - Some(HTTPMethod::Get.as_str().into()), - )] - fn connector_on_response( - #[case] http_response: HttpResponse, - #[case] selector: ConnectorHttpSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_response(&http_response)); - } - - #[rstest] - #[case( - RouterSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - fn connector_on_drop( - #[case] selector: RouterSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_drop()); - } } diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 2df7139a85..5cb8e5b856 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -10,6 +10,7 @@ use ::tracing::info_span; use ::tracing::Span; use axum::headers::HeaderName; use config_new::cache::CacheInstruments; +use config_new::connectors::http::instruments::ConnectorHttpInstruments; use config_new::instruments::InstrumentsConfig; use config_new::instruments::StaticInstrument; use config_new::Selectors; @@ -103,7 +104,6 @@ use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; use crate::plugins::telemetry::config_new::cost::add_cost_attributes; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; -use crate::plugins::telemetry::config_new::instruments::ConnectorHttpInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::DatadogId; @@ -314,7 +314,6 @@ impl PluginPrivate for Telemetry { subgraph_custom_instruments, connector_custom_instruments, cache_custom_instruments, - .. } = create_builtin_instruments(&config.instrumentation.instruments); Ok(Telemetry { From 1c5c1a299796c2148140cbe7d749e1ccad63179f Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Mon, 30 Sep 2024 20:33:54 -0600 Subject: [PATCH 05/18] Create http level under connector and add tests --- ...nfiguration__tests__schema_generation.snap | 64 ++++++++------ .../{connectors.rs => connector.rs} | 2 + .../{connectors => connector}/http.rs | 2 +- .../http/attributes.rs | 2 +- .../http/instruments.rs | 16 ++-- .../http/selectors.rs | 4 +- .../config_new/connector/instruments.rs | 16 ++++ .../metrics.snap | 36 ++++++++ .../router.yaml | 22 +++++ .../custom_counter_with_conditions/test.yaml | 23 +++++ .../connector/custom_histogram/metrics.snap | 33 +++++++ .../connector/custom_histogram/router.yaml | 20 +++++ .../connector/custom_histogram/test.yaml | 27 ++++++ .../http_client_request_duration/metrics.snap | 40 +++++++++ .../http_client_request_duration/router.yaml | 21 +++++ .../http_client_request_duration/test.yaml | 20 +++++ .../subgraph_and_connector/metrics.snap | 30 +++++++ .../subgraph_and_connector/router.yaml | 13 +++ .../subgraph_and_connector/test.yaml | 42 +++++++++ .../telemetry/config_new/fixtures/schema.json | 72 +++++++++++++++ .../telemetry/config_new/instruments.rs | 88 ++++++++++++++++--- .../src/plugins/telemetry/config_new/mod.rs | 2 +- apollo-router/src/plugins/telemetry/mod.rs | 2 +- 23 files changed, 543 insertions(+), 54 deletions(-) rename apollo-router/src/plugins/telemetry/config_new/{connectors.rs => connector.rs} (62%) rename apollo-router/src/plugins/telemetry/config_new/{connectors => connector}/http.rs (54%) rename apollo-router/src/plugins/telemetry/config_new/{connectors => connector}/http/attributes.rs (99%) rename apollo-router/src/plugins/telemetry/config_new/{connectors => connector}/http/instruments.rs (96%) rename apollo-router/src/plugins/telemetry/config_new/{connectors => connector}/http/selectors.rs (99%) create mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/test.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/test.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/metrics.snap create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/test.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/metrics.snap create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml create mode 100644 apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/test.yaml diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 2ec937d533..282cc552f6 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1943,6 +1943,24 @@ expression: "&schema" }, "type": "object" }, + "ConnectorHttpInstrumentsConfig": { + "additionalProperties": false, + "properties": { + "http.client.request.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + }, + "http.client.request.duration": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + }, + "http.client.response.body.size": { + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + } + }, + "type": "object" + }, "ConnectorHttpSelector": { "anyOf": [ { @@ -2086,20 +2104,12 @@ expression: "&schema" } ] }, - "ConnectorInstrumentsConfig": { + "ConnectorInstrumentsKind": { "additionalProperties": false, "properties": { - "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" - }, - "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" - }, - "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "http": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" } }, "type": "object" @@ -2438,7 +2448,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector": { "anyOf": [ { "type": "null" @@ -2450,8 +2460,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" } }, "required": [ @@ -3759,8 +3769,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" }, "condition": { "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", @@ -3962,8 +3972,8 @@ expression: "&schema" "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "connector": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" + "$ref": "#/definitions/ConnectorInstrumentsKind", + "description": "#/definitions/ConnectorInstrumentsKind" }, "default_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", @@ -8337,7 +8347,7 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector": { "additionalProperties": { "$ref": "#/definitions/ConnectorHttpSelector", "description": "#/definitions/ConnectorHttpSelector" @@ -8366,23 +8376,23 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", "description": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue" }, "properties": { "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" }, "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" }, "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" } }, "type": "object" diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors.rs b/apollo-router/src/plugins/telemetry/config_new/connector.rs similarity index 62% rename from apollo-router/src/plugins/telemetry/config_new/connectors.rs rename to apollo-router/src/plugins/telemetry/config_new/connector.rs index 4b4cecc413..7586fbb98b 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector.rs @@ -1,3 +1,5 @@ //! Connectors telemetry. pub(crate) mod http; +pub(crate) mod instruments; + diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http.rs similarity index 54% rename from apollo-router/src/plugins/telemetry/config_new/connectors/http.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/http.rs index c6b1e9ad11..9f07577b30 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connectors/http.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http.rs @@ -1,4 +1,4 @@ -//! Telemetry related to Connectors HTTP requests and responses. +//! Telemetry related to HTTP requests and responses for connectors. pub(crate) mod attributes; pub(crate) mod instruments; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs similarity index 99% rename from apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs index e4adc16138..cf95763d06 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connectors/http/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs @@ -1,4 +1,4 @@ -//! Attributes related to Connectors. +//! Attributes for HTTP connectors. use opentelemetry_api::Key; use opentelemetry_api::KeyValue; diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs similarity index 96% rename from apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs index c5fc3fe1ff..b04bd42c27 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connectors/http/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs @@ -1,4 +1,4 @@ -//! Instruments related to Connectors. +//! Instruments for HTTP connectors. use std::collections::HashMap; use std::sync::Arc; @@ -14,9 +14,9 @@ use tower::BoxError; use crate::metrics; use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; use crate::plugins::telemetry::config_new::conditions::Condition; -use crate::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; -use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpValue; +use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpValue; use crate::plugins::telemetry::config_new::extendable::Extendable; use crate::plugins::telemetry::config_new::instruments::CustomHistogram; use crate::plugins::telemetry::config_new::instruments::CustomHistogramInner; @@ -38,7 +38,7 @@ use crate::Context; #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorInstrumentsConfig { +pub(crate) struct ConnectorHttpInstrumentsConfig { /// Histogram of client request duration #[serde(rename = "http.client.request.duration")] http_client_request_duration: @@ -55,7 +55,7 @@ pub(crate) struct ConnectorInstrumentsConfig { DefaultedStandardInstrument>, } -impl DefaultForLevel for ConnectorInstrumentsConfig { +impl DefaultForLevel for ConnectorHttpInstrumentsConfig { fn defaults_for_level( &mut self, requirement_level: DefaultAttributeRequirementLevel, @@ -86,7 +86,7 @@ pub(crate) struct ConnectorHttpInstruments { impl ConnectorHttpInstruments { pub(crate) fn new( config: &Extendable< - ConnectorInstrumentsConfig, + ConnectorHttpInstrumentsConfig, Instrument, >, static_instruments: Arc>, @@ -220,7 +220,7 @@ impl ConnectorHttpInstruments { pub(crate) fn new_builtin( config: &Extendable< - ConnectorInstrumentsConfig, + ConnectorHttpInstrumentsConfig, Instrument, >, ) -> HashMap { diff --git a/apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs similarity index 99% rename from apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs index 3cce6c8a38..025138e026 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connectors/http/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs @@ -1,4 +1,4 @@ -//! Selectors related to Connectors. +//! Selectors for HTTP connectors. use derivative::Derivative; use opentelemetry_api::Value; @@ -294,7 +294,7 @@ mod tests { use rstest::rstest; use super::ConnectorSource; - use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; + use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; use crate::plugins::telemetry::config_new::selectors::ResponseStatus; use crate::plugins::telemetry::config_new::selectors::RouterSelector; use crate::plugins::telemetry::config_new::Selector; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs new file mode 100644 index 0000000000..a2e7d9e023 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs @@ -0,0 +1,16 @@ +use serde::Deserialize; +use schemars::JsonSchema; +use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig; +use crate::plugins::telemetry::config_new::connector::http::selectors::{ConnectorHttpSelector, ConnectorHttpValue}; +use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::instruments::Instrument; + +#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorInstrumentsKind { + pub(crate) http: Extendable< + ConnectorHttpInstrumentsConfig, + Instrument, + >, +} \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap new file mode 100644 index 0000000000..b22c408f07 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap @@ -0,0 +1,36 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/instruments.rs +description: Custom counter with conditions +expression: "&metrics.all()" +info: + telemetry: + instrumentation: + instruments: + default_requirement_level: none + connector: + http: + not.found.count: + description: Count of 404 responses from the user API + type: counter + unit: count + value: unit + attributes: + url_template: + connector_url_template: true + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - user_api + - connector_source: name +--- +- name: not.found.count + description: Count of 404 responses from the user API + unit: count + data: + datapoints: + - value: 1 + attributes: + url_template: "/user/{$this.userid}" diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml new file mode 100644 index 0000000000..32cf12e538 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml @@ -0,0 +1,22 @@ +telemetry: + instrumentation: + instruments: + default_requirement_level: none + connector: + http: + not.found.count: + description: "Count of 404 responses from the user API" + type: counter + unit: count + value: unit + attributes: + "url_template": + connector_url_template: true + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/test.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/test.yaml new file mode 100644 index 0000000000..37f88836a0 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/test.yaml @@ -0,0 +1,23 @@ +description: Custom counter with conditions +events: + - - context: + map: + "apollo_router::connector::info": + subgraph_name: users + source_name: user_api + http_method: GET + url_template: "/user/{$this.userid}" + - http_request: + uri: "/user/1" + method: GET + - http_response: + status: 200 + body: | + { "username": "foo" } + - http_request: + uri: "/user/1" + method: GET + - http_response: + status: 404 + body: | + { "error": "not found" } diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap new file mode 100644 index 0000000000..1520ee35a0 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap @@ -0,0 +1,33 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/instruments.rs +description: Both subgraph and connector HTTP client duration metrics +expression: "&metrics.all()" +info: + telemetry: + instrumentation: + instruments: + default_requirement_level: none + connector: + http: + rate.limit: + value: + connector_http_response_header: x-ratelimit-remaining + unit: count + type: histogram + description: Rate limit remaining + condition: + all: + - eq: + - 200 + - connector_http_response_status: code + - eq: + - user_api + - connector_source: name +--- +- name: rate.limit + description: Rate limit remaining + unit: count + data: + datapoints: + - sum: 1499 + attributes: {} diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml new file mode 100644 index 0000000000..8d487b6efa --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml @@ -0,0 +1,20 @@ +telemetry: + instrumentation: + instruments: + default_requirement_level: none + connector: + http: + rate.limit: + value: + connector_http_response_header: "x-ratelimit-remaining" + unit: count + type: histogram + description: "Rate limit remaining" + condition: + all: + - eq: + - 200 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/test.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/test.yaml new file mode 100644 index 0000000000..ec048a9937 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/test.yaml @@ -0,0 +1,27 @@ +description: Both subgraph and connector HTTP client duration metrics +events: + - - context: + map: + "apollo_router::connector::info": + subgraph_name: users + source_name: user_api + http_method: GET + url_template: "/users" + - http_request: + uri: "/users" + method: GET + - http_response: + status: 200 + headers: + x-ratelimit-remaining: 999 + body: | + { "username": "foo" } + - http_request: + uri: "/users" + method: GET + - http_response: + status: 200 + headers: + x-ratelimit-remaining: 500 + body: | + { "username": "foo" } \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/metrics.snap new file mode 100644 index 0000000000..84e8060330 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/metrics.snap @@ -0,0 +1,40 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/instruments.rs +description: Connector HTTP client duration metric +expression: "&metrics.all()" +info: + telemetry: + instrumentation: + instruments: + connector: + http.client.request.duration: + attributes: + subgraph.name: true + connector.source: + connector_source: name + connector.http.method: true + connector.url.template: true + custom.request.header.attribute: + connector_http_request_header: custom_request_header + custom.response.header.attribute: + connector_http_response_header: custom_response_header + custom.response.status.attribute: + connector_http_response_status: code + custom.static.attribute: + static: custom_value +--- +- name: http.client.request.duration + description: Duration of HTTP client requests. + unit: s + data: + datapoints: + - sum: 0.1 + attributes: + connector.http.method: GET + connector.source: posts_api + connector.url.template: /posts + custom.request.header.attribute: custom_request_header_value + custom.response.header.attribute: custom_response_header_value + custom.response.status.attribute: 200 + custom.static.attribute: custom_value + subgraph.name: posts diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml new file mode 100644 index 0000000000..5d79f1f9f5 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml @@ -0,0 +1,21 @@ +telemetry: + instrumentation: + instruments: + default_requirement_level: none + connector: + http: + http.client.request.duration: + attributes: + subgraph.name: true + connector.source: + connector_source: name + connector.http.method: true + connector.url.template: true + custom.request.header.attribute: + connector_http_request_header: "custom_request_header" + custom.response.header.attribute: + connector_http_response_header: "custom_response_header" + custom.response.status.attribute: + connector_http_response_status: code + custom.static.attribute: + static: "custom_value" \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/test.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/test.yaml new file mode 100644 index 0000000000..4673c57c64 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/test.yaml @@ -0,0 +1,20 @@ +description: Connector HTTP client duration metric +events: + - - context: + map: + "apollo_router::connector::info": + subgraph_name: posts + source_name: posts_api + http_method: GET + url_template: "/posts" + - http_request: + uri: "/posts" + method: GET + headers: + custom_request_header: custom_request_header_value + - http_response: + status: 200 + body: | + { "foo": "bar" } + headers: + custom_response_header: custom_response_header_value diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/metrics.snap new file mode 100644 index 0000000000..278a245fd5 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/metrics.snap @@ -0,0 +1,30 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/instruments.rs +description: Connector HTTP client duration metric +expression: "&metrics.all()" +info: + telemetry: + instrumentation: + instruments: + default_requirement_level: none + subgraph: + http.client.request.duration: + attributes: + subgraph.name: true + connector: + http: + http.client.request.duration: + attributes: + subgraph.name: true +--- +- name: http.client.request.duration + description: Duration of HTTP client requests. + unit: s + data: + datapoints: + - sum: 0.1 + attributes: + subgraph.name: products + - sum: 0.1 + attributes: + subgraph.name: reviews diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml new file mode 100644 index 0000000000..348e87700f --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml @@ -0,0 +1,13 @@ +telemetry: + instrumentation: + instruments: + default_requirement_level: none + subgraph: + http.client.request.duration: + attributes: + subgraph.name: true + connector: + http: + http.client.request.duration: + attributes: + subgraph.name: true \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/test.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/test.yaml new file mode 100644 index 0000000000..62c554512a --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/test.yaml @@ -0,0 +1,42 @@ +description: Both subgraph and connector HTTP client duration metrics +events: + - - router_request: + uri: "/hello" + method: GET + body: | + hello + - supergraph_request: + uri: "/hello" + method: GET + query: "query { hello }" + - subgraph_request: + query: "query { hello }" + operation_name: "Products" + operation_kind: query + subgraph_name: "products" + - subgraph_response: + status: 200 + data: + hello: "world" + - context: + map: + "apollo_router::connector::info": + subgraph_name: reviews + source_name: reviews_api + http_method: GET + url_template: "/reviews" + - http_request: + uri: "/reviews" + method: GET + - http_response: + status: 200 + body: | + { "foo": "bar" } + - supergraph_response: + status: 200 + data: + hello: "world" + - router_response: + body: | + hello + status: 200 diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/schema.json b/apollo-router/src/plugins/telemetry/config_new/fixtures/schema.json index efa2b5fa12..ecc1409f59 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/schema.json +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/schema.json @@ -443,6 +443,78 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "http_request" + ], + "properties": { + "http_request": { + "type": "object", + "required": [ + "method", + "uri" + ], + "properties": { + "body": { + "type": [ + "string", + "null" + ] + }, + "headers": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "method": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "http_response" + ], + "properties": { + "http_response": { + "type": "object", + "required": [ + "body", + "status" + ], + "properties": { + "body": { + "type": "string" + }, + "headers": { + "default": {}, + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "status": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index df0f225e8d..e551027f1d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -38,11 +38,8 @@ use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; use crate::plugins::telemetry::config_new::conditions::Condition; -use crate::plugins::telemetry::config_new::connectors::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connectors::http::instruments::ConnectorHttpInstruments; -use crate::plugins::telemetry::config_new::connectors::http::instruments::ConnectorInstrumentsConfig; -use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpSelector; -use crate::plugins::telemetry::config_new::connectors::http::selectors::ConnectorHttpValue; +use crate::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstruments; +use crate::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsKind; use crate::plugins::telemetry::config_new::cost::CostInstruments; use crate::plugins::telemetry::config_new::cost::CostInstrumentsConfig; use crate::plugins::telemetry::config_new::extendable::Extendable; @@ -86,11 +83,8 @@ pub(crate) struct InstrumentsConfig { SubgraphInstrumentsConfig, Instrument, >, - /// Connector service instruments. For more information see documentation on Router lifecycle. - pub(crate) connector: Extendable< - ConnectorInstrumentsConfig, - Instrument, - >, + /// Connector service instruments. + pub(crate) connector: ConnectorInstrumentsKind, /// GraphQL response field instruments. pub(crate) graphql: Extendable< GraphQLInstrumentsConfig, @@ -139,6 +133,11 @@ impl InstrumentsConfig { format!("error for custom cache instrument {name:?} in condition: {err}") })?; } + for (name, custom) in &self.connector.http.custom { + custom.condition.validate(None).map_err(|err| { + format!("error for custom connector instrument {name:?} in condition: {err}") + })?; + } Ok(()) } @@ -154,6 +153,9 @@ impl InstrumentsConfig { .defaults_for_levels(self.default_requirement_level, TelemetryDataKind::Metrics); self.graphql .defaults_for_levels(self.default_requirement_level, TelemetryDataKind::Metrics); + self.connector + .http + .defaults_for_levels(self.default_requirement_level, TelemetryDataKind::Metrics); } pub(crate) fn new_builtin_router_instruments(&self) -> HashMap { @@ -699,14 +701,14 @@ impl InstrumentsConfig { &self, static_instruments: Arc>, ) -> ConnectorHttpInstruments { - ConnectorHttpInstruments::new(&self.connector, static_instruments) + ConnectorHttpInstruments::new(&self.connector.http, static_instruments) } pub(crate) fn new_builtin_connector_instruments(&self) -> HashMap { let meter = metrics::meter_provider().meter(METER_NAME); - let mut static_instruments = ConnectorHttpInstruments::new_builtin(&self.connector); + let mut static_instruments = ConnectorHttpInstruments::new_builtin(&self.connector.http); - for (instrument_name, instrument) in &self.connector.custom { + for (instrument_name, instrument) in &self.connector.http.custom { match instrument.ty { InstrumentType::Counter => { static_instruments.insert( @@ -2632,6 +2634,8 @@ mod tests { use crate::plugins::telemetry::APOLLO_PRIVATE_QUERY_DEPTH; use crate::plugins::telemetry::APOLLO_PRIVATE_QUERY_HEIGHT; use crate::plugins::telemetry::APOLLO_PRIVATE_QUERY_ROOT_FIELDS; + use crate::services::http::HttpRequest; + use crate::services::http::HttpResponse; use crate::services::OperationKind; use crate::services::RouterRequest; use crate::services::RouterResponse; @@ -2741,6 +2745,19 @@ mod tests { ResponseField { typed_value: TypedValueMirror, }, + HttpRequest { + method: String, + uri: String, + #[serde(default)] + headers: HashMap, + body: Option, + }, + HttpResponse { + status: u16, + #[serde(default)] + headers: HashMap, + body: String, + }, } #[derive(Deserialize, JsonSchema)] @@ -2925,6 +2942,7 @@ mod tests { let mut router_instruments = None; let mut supergraph_instruments = None; let mut subgraph_instruments = None; + let mut connector_instruments = None; let mut cache_instruments: Option = None; let graphql_instruments: GraphQLInstruments = config .new_graphql_instruments(Arc::new( @@ -3165,6 +3183,50 @@ mod tests { } } } + Event::HttpRequest { + method, + uri, + headers, + body, + } => { + let mut http_request = http::Request::builder() + .method(Method::from_str(&method).expect("method")) + .uri(Uri::from_str(&uri).expect("uri")) + .body(body.unwrap_or(String::from("")).into()) + .unwrap(); + *http_request.headers_mut() = convert_http_headers(headers); + let request = HttpRequest { + http_request, + context: context.clone(), + }; + connector_instruments = Some({ + let connector_instruments = config + .new_connector_instruments(Arc::new( + config.new_builtin_connector_instruments(), + )); + connector_instruments.on_request(&request); + connector_instruments + }); + } + Event::HttpResponse { + status, + headers, + body, + } => { + let mut http_response = http::Response::builder() + .status(StatusCode::from_u16(status).expect("status")) + .body(body.into()) + .unwrap(); + *http_response.headers_mut() = convert_http_headers(headers); + let response = HttpResponse { + http_response, + context: context.clone(), + }; + connector_instruments + .take() + .expect("http request must have been made first") + .on_response(&response); + } } } } diff --git a/apollo-router/src/plugins/telemetry/config_new/mod.rs b/apollo-router/src/plugins/telemetry/config_new/mod.rs index ed30b2eb60..8c82d1fc0c 100644 --- a/apollo-router/src/plugins/telemetry/config_new/mod.rs +++ b/apollo-router/src/plugins/telemetry/config_new/mod.rs @@ -20,7 +20,7 @@ pub(crate) mod conditions; pub(crate) mod cache; mod conditional; -pub(crate) mod connectors; +pub(crate) mod connector; pub(crate) mod cost; pub(crate) mod events; mod experimental_when_header; diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 5cb8e5b856..3090885d49 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -10,7 +10,7 @@ use ::tracing::info_span; use ::tracing::Span; use axum::headers::HeaderName; use config_new::cache::CacheInstruments; -use config_new::connectors::http::instruments::ConnectorHttpInstruments; +use config_new::connector::http::instruments::ConnectorHttpInstruments; use config_new::instruments::InstrumentsConfig; use config_new::instruments::StaticInstrument; use config_new::Selectors; From cb1c9c55d96f835e519a8714a98d207e33d514dc Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Tue, 1 Oct 2024 12:42:01 -0600 Subject: [PATCH 06/18] Add connector events --- ...nfiguration__tests__schema_generation.snap | 109 +++++++++ .../plugins/telemetry/config_new/connector.rs | 2 +- .../telemetry/config_new/connector/events.rs | 17 ++ .../telemetry/config_new/connector/http.rs | 1 + .../config_new/connector/http/events.rs | 206 ++++++++++++++++++ .../config_new/connector/instruments.rs | 8 +- .../plugins/telemetry/config_new/events.rs | 148 +++++++++++-- ..._tests__connector_events_request@logs.snap | 12 + ...tests__connector_events_response@logs.snap | 12 + .../src/plugins/telemetry/fmt_layer.rs | 104 ++++++++- apollo-router/src/plugins/telemetry/mod.rs | 19 +- ..._with_custom_events_with_instrumented.snap | 4 + ..._with_custom_events_with_instrumented.snap | 4 + .../testdata/custom_events.router.yaml | 55 ++++- 14 files changed, 673 insertions(+), 28 deletions(-) create mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/events.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs create mode 100644 apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap create mode 100644 apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 282cc552f6..16427e60da 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -1917,6 +1917,16 @@ expression: "&schema" }, "type": "object" }, + "ConnectorEventsKind": { + "additionalProperties": false, + "properties": { + "http": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" + } + }, + "type": "object" + }, "ConnectorHttpAttributes": { "additionalProperties": false, "properties": { @@ -1943,6 +1953,24 @@ expression: "&schema" }, "type": "object" }, + "ConnectorHttpEventsConfig": { + "additionalProperties": false, + "properties": { + "error": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + }, + "request": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + }, + "response": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + } + }, + "type": "object" + }, "ConnectorHttpInstrumentsConfig": { "additionalProperties": false, "properties": { @@ -2714,6 +2742,37 @@ expression: "&schema" } ] }, + "Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector": { + "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + }, + "condition": { + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" + }, + "level": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + "message": { + "description": "The event message.", + "type": "string" + }, + "on": { + "$ref": "#/definitions/EventOn", + "description": "#/definitions/EventOn" + } + }, + "required": [ + "level", + "message", + "on" + ], + "type": "object" + }, "Event_for_RouterAttributes_and_RouterSelector": { "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", "properties": { @@ -2843,6 +2902,10 @@ expression: "&schema" "additionalProperties": false, "description": "Events are", "properties": { + "connector": { + "$ref": "#/definitions/ConnectorEventsKind", + "description": "#/definitions/ConnectorEventsKind" + }, "router": { "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" @@ -5943,6 +6006,31 @@ expression: "&schema" } ] }, + "StandardEventConfig_for_ConnectorHttpSelector": { + "anyOf": [ + { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + }, + { + "properties": { + "condition": { + "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", + "description": "#/definitions/Condition_for_ConnectorHttpSelector" + }, + "level": { + "$ref": "#/definitions/EventLevel", + "description": "#/definitions/EventLevel" + } + }, + "required": [ + "condition", + "level" + ], + "type": "object" + } + ] + }, "StandardEventConfig_for_RouterSelector": { "anyOf": [ { @@ -8376,6 +8464,27 @@ expression: "&schema" }, "type": "object" }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { + "additionalProperties": { + "$ref": "#/definitions/Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector", + "description": "#/definitions/Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector" + }, + "properties": { + "error": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + }, + "request": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + }, + "response": { + "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + } + }, + "type": "object" + }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", diff --git a/apollo-router/src/plugins/telemetry/config_new/connector.rs b/apollo-router/src/plugins/telemetry/config_new/connector.rs index 7586fbb98b..c4cd7a2525 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector.rs @@ -1,5 +1,5 @@ //! Connectors telemetry. +pub(crate) mod events; pub(crate) mod http; pub(crate) mod instruments; - diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs new file mode 100644 index 0000000000..a2ae3bba9f --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs @@ -0,0 +1,17 @@ +use schemars::JsonSchema; +use serde::Deserialize; + +use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::events::Event; +use crate::plugins::telemetry::config_new::extendable::Extendable; + +#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorEventsKind { + pub(crate) http: Extendable< + ConnectorHttpEventsConfig, + Event, + >, +} diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http.rs index 9f07577b30..1c7f1b490d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http.rs @@ -1,5 +1,6 @@ //! Telemetry related to HTTP requests and responses for connectors. pub(crate) mod attributes; +pub(crate) mod events; pub(crate) mod instruments; pub(crate) mod selectors; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs new file mode 100644 index 0000000000..242d705e11 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs @@ -0,0 +1,206 @@ +use opentelemetry_api::Key; +use opentelemetry_api::KeyValue; +use parking_lot::Mutex; +use schemars::JsonSchema; +use serde::Deserialize; +use tower::BoxError; + +use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::events::log_event; +use crate::plugins::telemetry::config_new::events::CustomEvent; +use crate::plugins::telemetry::config_new::events::CustomEventInner; +use crate::plugins::telemetry::config_new::events::CustomEvents; +use crate::plugins::telemetry::config_new::events::Event; +use crate::plugins::telemetry::config_new::events::EventLevel; +use crate::plugins::telemetry::config_new::events::StandardEvent; +use crate::plugins::telemetry::config_new::events::StandardEventConfig; +use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::instruments::Instrumented; +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; +use crate::Context; + +#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorHttpEventsConfig { + /// Log the connector HTTP request + pub(crate) request: StandardEventConfig, + /// Log the connector HTTP response + pub(crate) response: StandardEventConfig, + /// Log the connector HTTP error + pub(crate) error: StandardEventConfig, +} + +#[derive(Clone)] +pub(crate) struct ConnectorHttpEventRequest(pub(crate) StandardEvent); + +#[derive(Clone)] +pub(crate) struct ConnectorHttpEventResponse(pub(crate) StandardEvent); + +pub(crate) type ConnectorHttpEvents = + CustomEvents; + +pub(crate) fn new_connector_http_events( + config: &Extendable< + ConnectorHttpEventsConfig, + Event, + >, +) -> ConnectorHttpEvents { + let custom_events = config + .custom + .iter() + .filter_map(|(event_name, event_cfg)| match &event_cfg.level { + EventLevel::Off => None, + _ => Some(CustomEvent { + inner: Mutex::new(CustomEventInner { + name: event_name.clone(), + level: event_cfg.level, + event_on: event_cfg.on, + message: event_cfg.message.clone(), + selectors: event_cfg.attributes.clone().into(), + condition: event_cfg.condition.clone(), + attributes: Vec::new(), + }), + }), + }) + .collect(); + + ConnectorHttpEvents { + request: config.attributes.request.clone().into(), + response: config.attributes.response.clone().into(), + error: config.attributes.error.clone().into(), + custom: custom_events, + } +} + +impl Instrumented + for CustomEvents +{ + type Request = HttpRequest; + type Response = HttpResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) { + if self.request.level() != EventLevel::Off { + if let Some(condition) = self.request.condition() { + if condition.lock().evaluate_request(request) != Some(true) { + return; + } + } + let mut attrs = Vec::with_capacity(5); + #[cfg(test)] + let headers = { + let mut headers: indexmap::IndexMap = request + .http_request + .headers() + .clone() + .into_iter() + .filter_map(|(name, val)| Some((name?.to_string(), val))) + .collect(); + headers.sort_keys(); + headers + }; + #[cfg(not(test))] + let headers = request.http_request.headers(); + + attrs.push(KeyValue::new( + Key::from_static_str("http.request.headers"), + opentelemetry::Value::String(format!("{:?}", headers).into()), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.request.method"), + opentelemetry::Value::String(format!("{}", request.http_request.method()).into()), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.request.uri"), + opentelemetry::Value::String(format!("{}", request.http_request.uri()).into()), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.request.version"), + opentelemetry::Value::String( + format!("{:?}", request.http_request.version()).into(), + ), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.request.body"), + opentelemetry::Value::String(format!("{:?}", request.http_request.body()).into()), + )); + log_event(self.request.level(), "connector.http.request", attrs, ""); + } + for custom_event in &self.custom { + custom_event.on_request(request); + } + } + + fn on_response(&self, response: &Self::Response) { + if self.response.level() != EventLevel::Off { + if let Some(condition) = self.response.condition() { + if !condition.lock().evaluate_response(response) { + return; + } + } + let mut attrs = Vec::with_capacity(4); + + #[cfg(test)] + let headers = { + let mut headers: indexmap::IndexMap = response + .http_response + .headers() + .clone() + .into_iter() + .filter_map(|(name, val)| Some((name?.to_string(), val))) + .collect(); + headers.sort_keys(); + headers + }; + #[cfg(not(test))] + let headers = response.http_response.headers(); + + attrs.push(KeyValue::new( + Key::from_static_str("http.response.headers"), + opentelemetry::Value::String(format!("{:?}", headers).into()), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.response.status"), + opentelemetry::Value::String(format!("{}", response.http_response.status()).into()), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.response.version"), + opentelemetry::Value::String( + format!("{:?}", response.http_response.version()).into(), + ), + )); + attrs.push(KeyValue::new( + Key::from_static_str("http.response.body"), + opentelemetry::Value::String(format!("{:?}", response.http_response.body()).into()), + )); + log_event(self.response.level(), "connector.http.response", attrs, ""); + } + for custom_event in &self.custom { + custom_event.on_response(response); + } + } + + fn on_error(&self, error: &BoxError, ctx: &Context) { + if self.error.level() != EventLevel::Off { + if let Some(condition) = self.error.condition() { + if !condition.lock().evaluate_error(error, ctx) { + return; + } + } + log_event( + self.error.level(), + "connector.http.error", + vec![KeyValue::new( + Key::from_static_str("error"), + opentelemetry::Value::String(error.to_string().into()), + )], + "", + ); + } + for custom_event in &self.custom { + custom_event.on_error(error, ctx); + } + } +} diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs index a2e7d9e023..aede418cc5 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs @@ -1,8 +1,10 @@ -use serde::Deserialize; use schemars::JsonSchema; +use serde::Deserialize; + use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; use crate::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig; -use crate::plugins::telemetry::config_new::connector::http::selectors::{ConnectorHttpSelector, ConnectorHttpValue}; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpValue; use crate::plugins::telemetry::config_new::extendable::Extendable; use crate::plugins::telemetry::config_new::instruments::Instrument; @@ -13,4 +15,4 @@ pub(crate) struct ConnectorInstrumentsKind { ConnectorHttpInstrumentsConfig, Instrument, >, -} \ No newline at end of file +} diff --git a/apollo-router/src/plugins/telemetry/config_new/events.rs b/apollo-router/src/plugins/telemetry/config_new/events.rs index a58264bfb7..512be7d66d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/events.rs @@ -20,6 +20,8 @@ use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; use crate::plugins::telemetry::config_new::conditions::Condition; +use crate::plugins::telemetry::config_new::connector::events::ConnectorEventsKind; +use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEvents; use crate::plugins::telemetry::config_new::extendable::Extendable; use crate::plugins::telemetry::config_new::selectors::RouterSelector; use crate::plugins::telemetry::config_new::selectors::SubgraphSelector; @@ -40,6 +42,8 @@ pub(crate) struct Events { supergraph: Extendable>, /// Supergraph service events subgraph: Extendable>, + /// Connector events + connector: ConnectorEventsKind, } impl Events { @@ -130,6 +134,10 @@ impl Events { } } + pub(crate) fn new_connector_http_events(&self) -> ConnectorHttpEvents { + super::connector::http::events::new_connector_http_events(&self.connector.http) + } + pub(crate) fn validate(&self) -> Result<(), String> { if let StandardEventConfig::Conditional { condition, .. } = &self.router.attributes.request { @@ -159,6 +167,16 @@ impl Events { { condition.validate(Some(Stage::Response))?; } + if let StandardEventConfig::Conditional { condition, .. } = + &self.connector.http.attributes.request + { + condition.validate(Some(Stage::Request))?; + } + if let StandardEventConfig::Conditional { condition, .. } = + &self.connector.http.attributes.response + { + condition.validate(Some(Stage::Response))?; + } for (name, custom_event) in &self.router.custom { custom_event.validate().map_err(|err| { format!("configuration error for router custom event {name:?}: {err}") @@ -174,6 +192,11 @@ impl Events { format!("configuration error for subgraph custom event {name:?}: {err}") })?; } + for (name, custom_event) in &self.connector.http.custom { + custom_event.validate().map_err(|err| { + format!("configuration error for connector HTTP custom event {name:?}: {err}") + })?; + } Ok(()) } @@ -197,10 +220,10 @@ where Attributes: Selectors + Default, Sel: Selector + Debug, { - request: StandardEvent, - response: StandardEvent, - error: StandardEvent, - custom: Vec>, + pub(super) request: StandardEvent, + pub(super) response: StandardEvent, + pub(super) error: StandardEvent, + pub(super) custom: Vec>, } impl Instrumented @@ -609,21 +632,21 @@ where E: Debug, { /// The log level of the event. - level: EventLevel, + pub(super) level: EventLevel, /// The event message. - message: Arc, + pub(super) message: Arc, /// When to trigger the event. - on: EventOn, + pub(super) on: EventOn, /// The event attributes. #[serde(default = "Extendable::empty_arc::")] - attributes: Arc>, + pub(super) attributes: Arc>, /// The event conditions. #[serde(default = "Condition::empty::")] - condition: Condition, + pub(super) condition: Condition, } impl Event @@ -660,21 +683,21 @@ where A: Selectors + Default, T: Selector + Debug, { - inner: Mutex>, + pub(super) inner: Mutex>, } -struct CustomEventInner +pub(super) struct CustomEventInner where A: Selectors + Default, T: Selector + Debug, { - name: String, - level: EventLevel, - event_on: EventOn, - message: Arc, - selectors: Option>>, - condition: Condition, - attributes: Vec, + pub(super) name: String, + pub(super) level: EventLevel, + pub(super) event_on: EventOn, + pub(super) message: Arc, + pub(super) selectors: Option>>, + pub(super) condition: Condition, + pub(super) attributes: Vec, } impl Instrumented for CustomEvent @@ -797,6 +820,7 @@ pub(crate) fn log_event(level: EventLevel, kind: &str, attributes: Vec #[cfg(test)] mod tests { + use apollo_federation::sources::connect::HTTPMethod; use http::header::CONTENT_LENGTH; use http::HeaderValue; use tracing::instrument::WithSubscriber; @@ -808,6 +832,10 @@ mod tests { use crate::graphql; use crate::plugins::telemetry::Telemetry; use crate::plugins::test::PluginTestHarness; + use crate::services::connector_service::ConnectorInfo; + use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; + use crate::services::http::HttpRequest; + use crate::services::http::HttpResponse; #[tokio::test(flavor = "multi_thread")] async fn test_router_events() { @@ -1135,4 +1163,88 @@ mod tests { .with_subscriber(assert_snapshot_subscriber!()) .await } + + #[tokio::test(flavor = "multi_thread")] + async fn test_connector_events_request() { + let test_harness: PluginTestHarness = PluginTestHarness::builder() + .config(include_str!("../testdata/custom_events.router.yaml")) + .build() + .await; + + async { + let connector_info = ConnectorInfo { + subgraph_name: "subgraph".to_string(), + source_name: Some("source".to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: "/test".to_string(), + }; + let context = Context::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + let mut http_request = http::Request::builder().body("".into()).unwrap(); + http_request + .headers_mut() + .insert("x-log-request", HeaderValue::from_static("log")); + let http_request = HttpRequest { + http_request, + context: context.clone(), + }; + test_harness + .call_http_client("subgraph", http_request, |http_request| HttpResponse { + http_response: http::Response::builder() + .status(200) + .header("x-log-request", HeaderValue::from_static("log")) + .body("".into()) + .expect("expecting valid response"), + context: http_request.context.clone(), + }) + .await + .expect("expecting successful response"); + } + .with_subscriber(assert_snapshot_subscriber!()) + .await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_connector_events_response() { + let test_harness: PluginTestHarness = PluginTestHarness::builder() + .config(include_str!("../testdata/custom_events.router.yaml")) + .build() + .await; + + async { + let connector_info = ConnectorInfo { + subgraph_name: "subgraph".to_string(), + source_name: Some("source".to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: "/test".to_string(), + }; + let context = Context::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + let mut http_request = http::Request::builder().body("".into()).unwrap(); + http_request + .headers_mut() + .insert("x-log-response", HeaderValue::from_static("log")); + let http_request = HttpRequest { + http_request, + context: context.clone(), + }; + test_harness + .call_http_client("subgraph", http_request, |http_request| HttpResponse { + http_response: http::Response::builder() + .status(200) + .header("x-log-response", HeaderValue::from_static("log")) + .body("".into()) + .expect("expecting valid response"), + context: http_request.context.clone(), + }) + .await + .expect("expecting successful response"); + } + .with_subscriber(assert_snapshot_subscriber!()) + .await + } } diff --git a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap new file mode 100644 index 0000000000..ae4b024e28 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap @@ -0,0 +1,12 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/events.rs +expression: yaml +--- +- fields: + kind: connector.http.request + level: INFO + message: "" +- fields: + kind: my.request.event + level: INFO + message: my request event message diff --git a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap new file mode 100644 index 0000000000..a7e1b50fbb --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap @@ -0,0 +1,12 @@ +--- +source: apollo-router/src/plugins/telemetry/config_new/events.rs +expression: yaml +--- +- fields: + kind: connector.http.response + level: WARN + message: "" +- fields: + kind: my.response.event + level: ERROR + message: my response event message diff --git a/apollo-router/src/plugins/telemetry/fmt_layer.rs b/apollo-router/src/plugins/telemetry/fmt_layer.rs index cf5eb49c8f..399a42cae0 100644 --- a/apollo-router/src/plugins/telemetry/fmt_layer.rs +++ b/apollo-router/src/plugins/telemetry/fmt_layer.rs @@ -264,6 +264,7 @@ mod tests { use std::sync::Mutex; use std::sync::MutexGuard; + use apollo_federation::sources::connect::HTTPMethod; use http::header::CONTENT_LENGTH; use http::HeaderValue; use tracing::error; @@ -283,6 +284,10 @@ mod tests { use crate::plugins::telemetry::config_new::logging::TextFormat; use crate::plugins::telemetry::dynamic_attribute::SpanDynAttribute; use crate::plugins::telemetry::otel; + use crate::services::connector_service::ConnectorInfo; + use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; + use crate::services::http::HttpRequest; + use crate::services::http::HttpResponse; use crate::services::router; use crate::services::subgraph; use crate::services::supergraph; @@ -362,7 +367,42 @@ subgraph: subgraph_response_status: code "my.custom.attribute": subgraph_response_data: "$.*" - default: "missing""#; + default: "missing" + +connector: + http: + # Standard events + request: info + response: warn + error: error + + # Custom events + my.connector.request.event: + message: "my request event message" + level: info + on: request + attributes: + subgraph.name: true + connector_source: + connector_source: name + http_method: + connector_http_method: true + url_template: + connector_url_template: true + my.connector.response.event: + message: "my response event message" + level: error + on: response + attributes: + subgraph.name: true + connector_source: + connector_source: name + http_method: + connector_http_method: true + url_template: + connector_url_template: true + response_status: + connector_http_response_status: code"#; #[derive(Default, Clone)] struct LogBuffer(Arc>>); @@ -787,6 +827,37 @@ subgraph: .build() .expect("expecting valid response"); subgraph_events.on_response(&subgraph_resp); + + let connector_info = ConnectorInfo { + subgraph_name: "connector_subgraph".to_string(), + source_name: Some("source".to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: "/test".to_string(), + }; + let context = crate::Context::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + let mut http_request = http::Request::builder().body("".into()).unwrap(); + http_request + .headers_mut() + .insert("x-log-request", HeaderValue::from_static("log")); + let http_request = HttpRequest { + http_request, + context, + }; + let connector_events = event_config.new_connector_http_events(); + connector_events.on_request(&http_request); + + let http_response = HttpResponse { + http_response: http::Response::builder() + .status(200) + .header("x-log-response", HeaderValue::from_static("log")) + .body("".into()) + .expect("expecting valid response"), + context: Default::default(), + }; + connector_events.on_response(&http_response); }, ); @@ -926,6 +997,37 @@ subgraph: .build() .expect("expecting valid response"); subgraph_events.on_response(&subgraph_resp); + + let connector_info = ConnectorInfo { + subgraph_name: "connector_subgraph".to_string(), + source_name: Some("source".to_string()), + http_method: HTTPMethod::Get.as_str().to_string(), + url_template: "/test".to_string(), + }; + let context = crate::Context::default(); + context + .insert(CONNECTOR_INFO_CONTEXT_KEY, connector_info) + .unwrap(); + let mut http_request = http::Request::builder().body("".into()).unwrap(); + http_request + .headers_mut() + .insert("x-log-request", HeaderValue::from_static("log")); + let http_request = HttpRequest { + http_request, + context, + }; + let connector_events = event_config.new_connector_http_events(); + connector_events.on_request(&http_request); + + let http_response = HttpResponse { + http_response: http::Response::builder() + .status(200) + .header("x-log-response", HeaderValue::from_static("log")) + .body("".into()) + .expect("expecting valid response"), + context: Default::default(), + }; + connector_events.on_response(&http_response); }, ); diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 3090885d49..39791477ae 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -102,6 +102,7 @@ use crate::plugins::telemetry::apollo_exporter::proto::reports::StatsContext; use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; +use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEvents; use crate::plugins::telemetry::config_new::cost::add_cost_attributes; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; @@ -876,14 +877,22 @@ impl PluginPrivate for Telemetry { .instruments .new_connector_instruments(static_connector_instruments.clone()); custom_instruments.on_request(http_request); - (http_request.context.clone(), Some(custom_instruments)) + let custom_events = req_fn_config + .instrumentation + .events + .new_connector_http_events(); + custom_events.on_request(http_request); + ( + http_request.context.clone(), + Some((custom_instruments, custom_events)), + ) } else { (http_request.context.clone(), None) } }, - move |(context, custom_instruments): ( + move |(context, custom_telemetry): ( Context, - Option, + Option<(ConnectorHttpInstruments, ConnectorHttpEvents)>, ), f: BoxFuture< 'static, @@ -891,13 +900,15 @@ impl PluginPrivate for Telemetry { >| { async move { let result = f.await; - if let Some(custom_instruments) = custom_instruments { + if let Some((custom_instruments, custom_events)) = custom_telemetry { match &result { Ok(resp) => { custom_instruments.on_response(resp); + custom_events.on_response(resp); } Err(err) => { custom_instruments.on_error(err, &context); + custom_events.on_error(err, &context); } } } diff --git a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap index 0169cc4c8d..b12075462b 100644 --- a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap +++ b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap @@ -15,3 +15,7 @@ expression: buff.to_string() {"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","my.custom.attribute":["{\"id\":1234,\"name\":\"first_name\"}","{\"id\":567,\"name\":\"second_name\"}"],"response_status":200,"subgraph.name":"subgraph","message":"my response event message","kind":"my.subgraph.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","message":"my event message","kind":"my.subgraph.request.event","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","my.custom.attribute":"[[{\"id\":1234,\"name\":\"first_name\"},{\"id\":567,\"name\":\"second_name\"}],{\"foo\":\"bar\"}]","response_status":200,"subgraph.name":"subgraph_bis","message":"my response event message","kind":"my.subgraph.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.request.body":"Body(Empty)","http.request.headers":"{\"x-log-request\": \"log\"}","http.request.method":"GET","http.request.uri":"/","http.request.version":"HTTP/1.1","message":"","kind":"connector.http.request","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","connector_source":"source","http_method":"GET","subgraph.name":"connector_subgraph","url_template":"/test","message":"my request event message","kind":"my.connector.request.event","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"WARN","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.response.body":"Body(Empty)","http.response.headers":"{\"x-log-response\": \"log\"}","http.response.status":"200 OK","http.response.version":"HTTP/1.1","message":"","kind":"connector.http.response","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","connector_source":"source","http_method":"GET","response_status":200,"subgraph.name":"connector_subgraph","url_template":"/test","message":"my response event message","kind":"my.connector.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} diff --git a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap index d329c82801..b4847455ed 100644 --- a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap +++ b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap @@ -15,3 +15,7 @@ expression: buff.to_string() [timestamp] ERROR my.custom.attribute=["{"id":1234,"name":"first_name"}","{"id":567,"name":"second_name"}"] response_status=200 subgraph.name=subgraph my response event message kind=my.subgraph.response.event [timestamp] INFO my event message kind=my.subgraph.request.event [timestamp] ERROR my.custom.attribute=[[{"id":1234,"name":"first_name"},{"id":567,"name":"second_name"}],{"foo":"bar"}] response_status=200 subgraph.name=subgraph_bis my response event message kind=my.subgraph.response.event +[timestamp] INFO http.request.body=Body(Empty) http.request.headers={"x-log-request": "log"} http.request.method=GET http.request.uri=/ http.request.version=HTTP/1.1 kind=connector.http.request +[timestamp] INFO connector_source=source http_method=GET subgraph.name=connector_subgraph url_template=/test my request event message kind=my.connector.request.event +[timestamp] WARN http.response.body=Body(Empty) http.response.headers={"x-log-response": "log"} http.response.status=200 OK http.response.version=HTTP/1.1 kind=connector.http.response +[timestamp] ERROR connector_source=source http_method=GET response_status=200 subgraph.name=connector_subgraph url_template=/test my response event message kind=my.connector.response.event diff --git a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml index c3c23cb68f..edb34136c1 100644 --- a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml +++ b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml @@ -144,4 +144,57 @@ telemetry: subgraph_response_status: code "my.custom.attribute": subgraph_response_data: "$.*" - default: "missing" \ No newline at end of file + default: "missing" + connector: + http: + # Standard events + request: + level: info + condition: + eq: + - connector_http_request_header: x-log-request + - "log" + response: + level: warn + condition: + all: + - eq: + - connector_http_response_header: x-log-response + - "log" + - eq: + - subgraph_name: true + - "subgraph" + error: error + + # Custom events + my.disabled_request.event: + message: "my disabled event message" + level: off + on: request + my.request.event: + message: "my request event message" + level: info + on: request + condition: + eq: + - connector_http_request_header: x-log-request + - "log" + my.response.event: + message: "my response event message" + level: error + on: response + condition: + all: + - eq: + - connector_http_response_header: x-log-response + - "log" + - eq: + - 200 + - connector_http_response_status: code + - eq: + - subgraph_name: true + - "subgraph" + attributes: + subgraph.name: true + response_status: + connector_http_response_status: code \ No newline at end of file From 8934b4c28c00fddf432bf7e40fed6149ef9d0b56 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Tue, 1 Oct 2024 17:28:56 -0600 Subject: [PATCH 07/18] Remove rstest --- .../config_new/connector/http/selectors.rs | 302 +++++++++++------- 1 file changed, 180 insertions(+), 122 deletions(-) diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs index 025138e026..00f5ae4497 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs @@ -290,13 +290,10 @@ impl Selector for ConnectorHttpSelector { mod tests { use apollo_federation::sources::connect::HTTPMethod; use http::StatusCode; - use rstest::fixture; - use rstest::rstest; use super::ConnectorSource; use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; use crate::plugins::telemetry::config_new::selectors::ResponseStatus; - use crate::plugins::telemetry::config_new::selectors::RouterSelector; use crate::plugins::telemetry::config_new::Selector; use crate::services::connector_service::ConnectorInfo; use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; @@ -311,7 +308,6 @@ mod tests { const TEST_HEADER_VALUE: &str = "test_header_value"; const TEST_STATIC: &str = "test_static"; - #[fixture] fn connector_info() -> ConnectorInfo { ConnectorInfo { subgraph_name: TEST_SUBGRAPH_NAME.to_string(), @@ -321,7 +317,6 @@ mod tests { } } - #[fixture] fn context(connector_info: ConnectorInfo) -> Context { let context = Context::default(); context @@ -330,7 +325,6 @@ mod tests { context } - #[fixture] fn http_request(context: Context) -> HttpRequest { HttpRequest { http_request: http::Request::builder().body("".into()).unwrap(), @@ -338,7 +332,6 @@ mod tests { } } - #[fixture] fn http_request_with_header(context: Context) -> HttpRequest { HttpRequest { http_request: http::Request::builder() @@ -349,11 +342,7 @@ mod tests { } } - #[fixture] - fn http_response( - context: Context, - #[default(StatusCode::OK)] status_code: StatusCode, - ) -> HttpResponse { + fn http_response(context: Context, status_code: StatusCode) -> HttpResponse { HttpResponse { http_response: http::Response::builder() .status(status_code) @@ -363,11 +352,7 @@ mod tests { } } - #[fixture] - fn http_response_with_header( - context: Context, - #[default(StatusCode::OK)] status_code: StatusCode, - ) -> HttpResponse { + fn http_response_with_header(context: Context, status_code: StatusCode) -> HttpResponse { HttpResponse { http_response: http::Response::builder() .status(status_code) @@ -378,128 +363,201 @@ mod tests { } } - #[rstest] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::SubgraphName { subgraph_name: true }, - Some(TEST_SUBGRAPH_NAME.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, - Some(TEST_SOURCE_NAME.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, - Some(TEST_URL_TEMPLATE.into()), - )] - #[case( - http_request(context(connector_info())), - ConnectorHttpSelector::ConnectorRequestHeader { + #[test] + fn connector_on_request_static_field() { + let selector = ConnectorHttpSelector::StaticField { + r#static: TEST_STATIC.into(), + }; + assert_eq!( + Some(TEST_STATIC.into()), + selector.on_request(&http_request(context(connector_info()))) + ); + } + + #[test] + fn connector_on_request_subgraph_name() { + let selector = ConnectorHttpSelector::SubgraphName { + subgraph_name: true, + }; + assert_eq!( + Some(TEST_SUBGRAPH_NAME.into()), + selector.on_request(&http_request(context(connector_info()))) + ); + } + + #[test] + fn connector_on_request_connector_source() { + let selector = ConnectorHttpSelector::ConnectorSource { + connector_source: ConnectorSource::Name, + }; + assert_eq!( + Some(TEST_SOURCE_NAME.into()), + selector.on_request(&http_request(context(connector_info()))) + ); + } + + #[test] + fn connector_on_request_url_template() { + let selector = ConnectorHttpSelector::ConnectorUrlTemplate { + connector_url_template: true, + }; + assert_eq!( + Some(TEST_URL_TEMPLATE.into()), + selector.on_request(&http_request(context(connector_info()))) + ); + } + + #[test] + fn connector_on_request_header_defaulted() { + let selector = ConnectorHttpSelector::ConnectorRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), - }, - Some("defaulted".into()), - )] - #[case( - http_request_with_header(context(connector_info())), - ConnectorHttpSelector::ConnectorRequestHeader { + }; + assert_eq!( + Some("defaulted".into()), + selector.on_request(&http_request(context(connector_info()))) + ); + } + + #[test] + fn connector_on_request_header_with_value() { + let selector = ConnectorHttpSelector::ConnectorRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: None, - }, - Some(TEST_HEADER_VALUE.into()), - )] - fn connector_on_request( - #[case] http_request: HttpRequest, - #[case] selector: ConnectorHttpSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_request(&http_request)); + }; + assert_eq!( + Some(TEST_HEADER_VALUE.into()), + selector.on_request(&http_request_with_header(context(connector_info()))) + ); + } + + #[test] + fn connector_on_response_static_field() { + let selector = ConnectorHttpSelector::StaticField { + r#static: TEST_STATIC.into(), + }; + assert_eq!( + Some(TEST_STATIC.into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); } - #[rstest] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::SubgraphName { subgraph_name: true }, - Some(TEST_SUBGRAPH_NAME.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorSource { connector_source: ConnectorSource::Name }, - Some(TEST_SOURCE_NAME.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorUrlTemplate { connector_url_template: true }, - Some(TEST_URL_TEMPLATE.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorResponseHeader { + #[test] + fn connector_on_response_subgraph_name() { + let selector = ConnectorHttpSelector::SubgraphName { + subgraph_name: true, + }; + assert_eq!( + Some(TEST_SUBGRAPH_NAME.into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_connector_source() { + let selector = ConnectorHttpSelector::ConnectorSource { + connector_source: ConnectorSource::Name, + }; + assert_eq!( + Some(TEST_SOURCE_NAME.into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_url_template() { + let selector = ConnectorHttpSelector::ConnectorUrlTemplate { + connector_url_template: true, + }; + assert_eq!( + Some(TEST_URL_TEMPLATE.into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_header_defaulted() { + let selector = ConnectorHttpSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), - }, - Some("defaulted".into()), - )] - #[case( - http_response_with_header(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorResponseHeader { + }; + assert_eq!( + Some("defaulted".into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_header_with_value() { + let selector = ConnectorHttpSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: None, - }, - Some(TEST_HEADER_VALUE.into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::NOT_FOUND), - ConnectorHttpSelector::ConnectorResponseStatus { + }; + assert_eq!( + Some(TEST_HEADER_VALUE.into()), + selector.on_response(&http_response_with_header( + context(connector_info()), + StatusCode::OK + )) + ); + } + + #[test] + fn connector_on_response_status_code() { + let selector = ConnectorHttpSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Code, - }, - Some(opentelemetry::Value::I64(404)), - )] - #[case( - http_response(context(connector_info()), StatusCode::NOT_FOUND), - ConnectorHttpSelector::ConnectorResponseStatus { + }; + assert_eq!( + Some(200.into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_status_reason_ok() { + let selector = ConnectorHttpSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Reason, - }, - Some("Not Found".into()), - )] - #[case( - http_response(context(connector_info()), StatusCode::OK), - ConnectorHttpSelector::ConnectorHttpMethod { connector_http_method: true }, - Some(HTTPMethod::Get.as_str().into()), - )] - fn connector_on_response( - #[case] http_response: HttpResponse, - #[case] selector: ConnectorHttpSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_response(&http_response)); + }; + assert_eq!( + Some("OK".into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); + } + + #[test] + fn connector_on_response_status_code_not_found() { + let selector = ConnectorHttpSelector::ConnectorResponseStatus { + connector_http_response_status: ResponseStatus::Reason, + }; + assert_eq!( + Some("Not Found".into()), + selector.on_response(&http_response( + context(connector_info()), + StatusCode::NOT_FOUND + )) + ); + } + + #[test] + fn connector_on_response_http_method() { + let selector = ConnectorHttpSelector::ConnectorHttpMethod { + connector_http_method: true, + }; + assert_eq!( + Some(HTTPMethod::Get.as_str().into()), + selector.on_response(&http_response(context(connector_info()), StatusCode::OK)) + ); } - #[rstest] - #[case( - RouterSelector::StaticField { r#static: TEST_STATIC.into() }, - Some(TEST_STATIC.into()), - )] - fn connector_on_drop( - #[case] selector: RouterSelector, - #[case] expected: Option, - ) { - assert_eq!(expected, selector.on_drop()); + #[test] + fn connector_on_drop_static_field() { + let selector = ConnectorHttpSelector::StaticField { + r#static: TEST_STATIC.into(), + }; + assert_eq!(Some(TEST_STATIC.into()), selector.on_drop()); } } From 997cd2b24178454d49d4130920d7241f87c80f81 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Thu, 3 Oct 2024 13:45:52 -0600 Subject: [PATCH 08/18] Update telemetry docs --- .../telemetry/instrumentation/events.mdx | 7 +++- .../telemetry/instrumentation/instruments.mdx | 32 +++++++++++++++++++ .../telemetry/instrumentation/selectors.mdx | 20 +++++++++++- .../instrumentation/standard-attributes.mdx | 12 +++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/docs/source/configuration/telemetry/instrumentation/events.mdx b/docs/source/configuration/telemetry/instrumentation/events.mdx index a4a3418f44..63e6b7e848 100644 --- a/docs/source/configuration/telemetry/instrumentation/events.mdx +++ b/docs/source/configuration/telemetry/instrumentation/events.mdx @@ -17,7 +17,7 @@ You can configure events for each service in `router.yaml`. Events can be standa -The `router`, `supergraph` and `subgraph` sections are used to define custom event configuration for each service: +The `router`, `supergraph`, `subgraph` and `conenctor` sections are used to define custom event configuration for each service: ```yaml title="future.router.yaml" telemetry: @@ -29,6 +29,8 @@ telemetry: # ... subgraph: # highlight-line # ... + connector: # highlight-line + # ... ``` ### Standard events @@ -229,6 +231,9 @@ telemetry: # Custom event configuration for supergraph service ... subgraph: # Custom event configuration for subgraph service ... + connector: + http: + # Custom event configuration for HTTP connectors ... ``` ## Event configuration reference diff --git a/docs/source/configuration/telemetry/instrumentation/instruments.mdx b/docs/source/configuration/telemetry/instrumentation/instruments.mdx index 0e612a30a3..4406476db5 100644 --- a/docs/source/configuration/telemetry/instrumentation/instruments.mdx +++ b/docs/source/configuration/telemetry/instrumentation/instruments.mdx @@ -27,6 +27,11 @@ OpenTelemetry specifies multiple [standard metric instruments](https://opentelem * `http.client.request.duration` - A histogram of request durations for requests handled by subgraphs. * `http.client.response.body.size` - A histogram of response body sizes for requests handled by subgraphs. +* For connector HTTP requests: + + * `http.client.request.body.size` - A histogram of request body sizes for connectors HTTP requests. + * `http.client.request.duration` - A histogram of request durations for connectors HTTP requests. + * `http.client.response.body.size` - A histogram of response body sizes for connectors HTTP responses. These instruments are configurable in `router.yaml`: @@ -42,6 +47,11 @@ telemetry: http.client.request.body.size: true # (default false) http.client.request.duration: true # (default false) http.client.response.body.size: true # (default false) + connector: + http: + http.client.request.body.size: true # (default false) + http.client.request.duration: true # (default false) + http.client.response.body.size: true # (default false) ``` They can be customized by attaching or removing attributes. See [attributes](#attributes) to learn more about configuring attributes. @@ -59,6 +69,11 @@ telemetry: http.client.request.duration: attributes: subgraph.name: true + connector: + http: + http.client.request.duration: + attributes: + connector.source.name: true ``` ### Apollo standard instruments @@ -77,6 +92,7 @@ The example configuration below defines four custom instruments: - `acme.request.duration` on the `router` service - `acme.graphql.requests` on the `supergraph` service - `acme.graphql.subgraph.errors` on the `subgraph` service +- `acme.user.not.found` on a connector HTTP response - `acme.graphql.list.lengths` on each JSON element returned to the client (defined on `graphql`) ```yaml title="router.yaml" @@ -113,6 +129,22 @@ telemetry: unit: count description: "my description" + connector: + http: + acme.user.not.found: + value: unit + type: counter + unit: count + description: "Count of 404 responses from the user API" + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name + graphql: acme.graphql.list.lengths: value: diff --git a/docs/source/configuration/telemetry/instrumentation/selectors.mdx b/docs/source/configuration/telemetry/instrumentation/selectors.mdx index 0234bb886d..b72d2d299a 100644 --- a/docs/source/configuration/telemetry/instrumentation/selectors.mdx +++ b/docs/source/configuration/telemetry/instrumentation/selectors.mdx @@ -22,7 +22,7 @@ telemetry: ## Selector configuration reference -Each service of the router pipeline (`router`, `supergraph`, `subgraph`) has its own available selectors. +Each service of the router pipeline (`router`, `supergraph`, `subgraph`, `connector`) has its own available selectors. You can also extract GraphQL metrics from the response data the router returns to clients. ### Router @@ -96,6 +96,24 @@ The subgraph service executes multiple times during query execution, with each e | `error` | No | `reason` | A string value containing error reason when it's a critical error | | `cache` | No | `hit`\|`miss` | Returns the number of cache hit or miss for this subgraph request | +### Connector + +#### HTTP + +Apollo Connectors for REST APIs make HTTP calls to the upstream HTTP API. These selectors let you extract metrics from these HTTP requests and responses. + +| Selector | Defaultable | Values | Description | +|----------------------------------|-------------|------------------|-------------------------------------------------------------------| +| `subgraph_name` | No | `true`\|`false` | The name of the subgraph containing the connector | +| `connector_source ` | No | `name` | The name of the `@source` associated with this connector, if any | +| `connector_http_request_header` | Yes | `true`\|`false` | The name of a connector request header | +| `connector_http_response_header` | Yes | `true`\|`false` | The name of a connector response header | +| `connector_http_response_status` | No | `code`\|`reason` | The status of a connector response | +| `connector_http_method` | No | `true`\|`false` | The HTTP method of a connector request | +| `connector_url_template ` | No | `true`\|`false` | The URL template of a connector request | +| `static` | No | | A static string value | +| `error` | No | `reason` | A string value containing error reason when it's a critical error | + ### GraphQL GraphQL metrics are extracted from the response data the router returns to client requests. diff --git a/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx b/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx index 5df84f52bf..392d11043f 100644 --- a/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx +++ b/docs/source/configuration/telemetry/instrumentation/standard-attributes.mdx @@ -108,3 +108,15 @@ Standard attributes of the `subgraph` service: | `subgraph.graphql.operation.name` | | The operation name from the subgraph query (need `spec_compliant` [mode](./spans/#mode) to disable it) | | `subgraph.graphql.operation.type` | `query`\|`mutation`\|`subscription` | The operation kind from the subgraph query | | `subgraph.graphql.document` | | The GraphQL query to the subgraph (need `spec_compliant` [mode](./spans/#mode) to disable it) | + + +#### Connector + +Standard attributes of the `connector` service: + +| Attribute | Values | Description | +|--------------------------|-------------------------------------|------------------------------------------------------------------| +| `subgraph.name` | | The name of the subgraph containing the connector | +| `connector.source.name` | | The name of the `@source` associated with this connector, if any | +| `connector.http.method` | | The HTTP method for the connector (`GET` or `POST`, for example) | +| `connector.url.template` | | The URL template for the connector | From 994d598f983d3995da1bf4d1a1f8fc24457a5ffd Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Fri, 4 Oct 2024 13:43:18 -0600 Subject: [PATCH 09/18] Suggestions from PR feedback --- .../telemetry/config_new/attributes.rs | 10 +++++ .../config_new/connector/http/events.rs | 27 +++++++++----- .../plugins/telemetry/config_new/events.rs | 37 ++++++++++++------- .../src/services/connector_service.rs | 4 +- .../telemetry/instrumentation/events.mdx | 2 +- .../telemetry/instrumentation/selectors.mdx | 4 +- 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/apollo-router/src/plugins/telemetry/config_new/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/attributes.rs index 53640cd87b..acea09b4d4 100644 --- a/apollo-router/src/plugins/telemetry/config_new/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/attributes.rs @@ -65,6 +65,16 @@ const NETWORK_LOCAL_PORT: Key = Key::from_static_str("network.local.port"); const NETWORK_PEER_ADDRESS: Key = Key::from_static_str("network.peer.address"); const NETWORK_PEER_PORT: Key = Key::from_static_str("network.peer.port"); +pub(super) const HTTP_REQUEST_HEADERS: Key = Key::from_static_str("http.request.headers"); +pub(super) const HTTP_REQUEST_URI: Key = Key::from_static_str("http.request.uri"); +pub(super) const HTTP_REQUEST_VERSION: Key = Key::from_static_str("http.request.version"); +pub(super) const HTTP_REQUEST_BODY: Key = Key::from_static_str("http.request.body"); + +pub(super) const HTTP_RESPONSE_HEADERS: Key = Key::from_static_str("http.response.headers"); +pub(super) const HTTP_RESPONSE_STATUS: Key = Key::from_static_str("http.response.status"); +pub(super) const HTTP_RESPONSE_VERSION: Key = Key::from_static_str("http.response.version"); +pub(super) const HTTP_RESPONSE_BODY: Key = Key::from_static_str("http.response.body"); + #[derive(Deserialize, JsonSchema, Clone, Debug, Default, Copy)] #[serde(deny_unknown_fields, rename_all = "snake_case")] pub(crate) enum DefaultAttributeRequirementLevel { diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs index 242d705e11..175803b136 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs @@ -1,10 +1,19 @@ use opentelemetry_api::Key; use opentelemetry_api::KeyValue; +use opentelemetry_semantic_conventions::trace::HTTP_REQUEST_METHOD; use parking_lot::Mutex; use schemars::JsonSchema; use serde::Deserialize; use tower::BoxError; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_URI; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_VERSION; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_STATUS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_VERSION; use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; use crate::plugins::telemetry::config_new::events::log_event; @@ -105,25 +114,25 @@ impl Instrumented let headers = request.http_request.headers(); attrs.push(KeyValue::new( - Key::from_static_str("http.request.headers"), + HTTP_REQUEST_HEADERS, opentelemetry::Value::String(format!("{:?}", headers).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.method"), + HTTP_REQUEST_METHOD, opentelemetry::Value::String(format!("{}", request.http_request.method()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.uri"), + HTTP_REQUEST_URI, opentelemetry::Value::String(format!("{}", request.http_request.uri()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.version"), + HTTP_REQUEST_VERSION, opentelemetry::Value::String( format!("{:?}", request.http_request.version()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.body"), + HTTP_REQUEST_BODY, opentelemetry::Value::String(format!("{:?}", request.http_request.body()).into()), )); log_event(self.request.level(), "connector.http.request", attrs, ""); @@ -158,21 +167,21 @@ impl Instrumented let headers = response.http_response.headers(); attrs.push(KeyValue::new( - Key::from_static_str("http.response.headers"), + HTTP_RESPONSE_HEADERS, opentelemetry::Value::String(format!("{:?}", headers).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.status"), + HTTP_RESPONSE_STATUS, opentelemetry::Value::String(format!("{}", response.http_response.status()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.version"), + HTTP_RESPONSE_VERSION, opentelemetry::Value::String( format!("{:?}", response.http_response.version()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.body"), + HTTP_RESPONSE_BODY, opentelemetry::Value::String(format!("{:?}", response.http_response.body()).into()), )); log_event(self.response.level(), "connector.http.response", attrs, ""); diff --git a/apollo-router/src/plugins/telemetry/config_new/events.rs b/apollo-router/src/plugins/telemetry/config_new/events.rs index 512be7d66d..22f75c7d9c 100644 --- a/apollo-router/src/plugins/telemetry/config_new/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/events.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use http::HeaderValue; use opentelemetry::Key; use opentelemetry::KeyValue; +use opentelemetry_semantic_conventions::trace::HTTP_REQUEST_METHOD; use parking_lot::Mutex; use schemars::JsonSchema; use serde::Deserialize; @@ -19,6 +20,14 @@ use super::Stage; use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_URI; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_VERSION; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_STATUS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_VERSION; use crate::plugins::telemetry::config_new::conditions::Condition; use crate::plugins::telemetry::config_new::connector::events::ConnectorEventsKind; use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEvents; @@ -255,25 +264,25 @@ impl Instrumented let headers = request.router_request.headers(); attrs.push(KeyValue::new( - Key::from_static_str("http.request.headers"), + HTTP_REQUEST_HEADERS, opentelemetry::Value::String(format!("{:?}", headers).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.method"), + HTTP_REQUEST_METHOD, opentelemetry::Value::String(format!("{}", request.router_request.method()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.uri"), + HTTP_REQUEST_URI, opentelemetry::Value::String(format!("{}", request.router_request.uri()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.version"), + HTTP_REQUEST_VERSION, opentelemetry::Value::String( format!("{:?}", request.router_request.version()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.body"), + HTTP_REQUEST_BODY, opentelemetry::Value::String(format!("{:?}", request.router_request.body()).into()), )); log_event(self.request.level(), "router.request", attrs, ""); @@ -305,19 +314,19 @@ impl Instrumented #[cfg(not(test))] let headers = response.response.headers(); attrs.push(KeyValue::new( - Key::from_static_str("http.response.headers"), + HTTP_RESPONSE_HEADERS, opentelemetry::Value::String(format!("{:?}", headers).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.status"), + HTTP_RESPONSE_STATUS, opentelemetry::Value::String(format!("{}", response.response.status()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.version"), + HTTP_RESPONSE_VERSION, opentelemetry::Value::String(format!("{:?}", response.response.version()).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.response.body"), + HTTP_RESPONSE_BODY, opentelemetry::Value::String(format!("{:?}", response.response.body()).into()), )); log_event(self.response.level(), "router.response", attrs, ""); @@ -383,29 +392,29 @@ impl Instrumented #[cfg(not(test))] let headers = request.supergraph_request.headers(); attrs.push(KeyValue::new( - Key::from_static_str("http.request.headers"), + HTTP_REQUEST_HEADERS, opentelemetry::Value::String(format!("{:?}", headers).into()), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.method"), + HTTP_REQUEST_METHOD, opentelemetry::Value::String( format!("{}", request.supergraph_request.method()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.uri"), + HTTP_REQUEST_URI, opentelemetry::Value::String( format!("{}", request.supergraph_request.uri()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.version"), + HTTP_REQUEST_VERSION, opentelemetry::Value::String( format!("{:?}", request.supergraph_request.version()).into(), ), )); attrs.push(KeyValue::new( - Key::from_static_str("http.request.body"), + HTTP_REQUEST_BODY, opentelemetry::Value::String( serde_json::to_string(request.supergraph_request.body()) .unwrap_or_default() diff --git a/apollo-router/src/services/connector_service.rs b/apollo-router/src/services/connector_service.rs index 3345fb58c8..e115c9c2e5 100644 --- a/apollo-router/src/services/connector_service.rs +++ b/apollo-router/src/services/connector_service.rs @@ -14,7 +14,7 @@ use serde::Deserialize; use serde::Serialize; use tower::BoxError; use tower::ServiceExt; -use tracing::warn; +use tracing::error; use tracing::Instrument; use super::connect::BoxService; @@ -183,7 +183,7 @@ async fn execute( .insert(CONNECTOR_INFO_CONTEXT_KEY, ConnectorInfo::from(connector)) .is_err() { - warn!("Failed to store connector info in context - instruments may be inaccurate"); + error!("Failed to store connector info in context - instruments may be inaccurate"); } let original_subgraph_name = original_subgraph_name.clone(); let request_limit = request_limit.clone(); diff --git a/docs/source/configuration/telemetry/instrumentation/events.mdx b/docs/source/configuration/telemetry/instrumentation/events.mdx index 63e6b7e848..bf31c78467 100644 --- a/docs/source/configuration/telemetry/instrumentation/events.mdx +++ b/docs/source/configuration/telemetry/instrumentation/events.mdx @@ -17,7 +17,7 @@ You can configure events for each service in `router.yaml`. Events can be standa -The `router`, `supergraph`, `subgraph` and `conenctor` sections are used to define custom event configuration for each service: +The `router`, `supergraph`, `subgraph` and `connector` sections are used to define custom event configuration for each service: ```yaml title="future.router.yaml" telemetry: diff --git a/docs/source/configuration/telemetry/instrumentation/selectors.mdx b/docs/source/configuration/telemetry/instrumentation/selectors.mdx index b72d2d299a..7cc64939ab 100644 --- a/docs/source/configuration/telemetry/instrumentation/selectors.mdx +++ b/docs/source/configuration/telemetry/instrumentation/selectors.mdx @@ -106,8 +106,8 @@ Apollo Connectors for REST APIs make HTTP calls to the upstream HTTP API. These |----------------------------------|-------------|------------------|-------------------------------------------------------------------| | `subgraph_name` | No | `true`\|`false` | The name of the subgraph containing the connector | | `connector_source ` | No | `name` | The name of the `@source` associated with this connector, if any | -| `connector_http_request_header` | Yes | `true`\|`false` | The name of a connector request header | -| `connector_http_response_header` | Yes | `true`\|`false` | The name of a connector response header | +| `connector_http_request_header` | Yes | | The name of a connector request header | +| `connector_http_response_header` | Yes | | The name of a connector response header | | `connector_http_response_status` | No | `code`\|`reason` | The status of a connector response | | `connector_http_method` | No | `true`\|`false` | The HTTP method of a connector request | | `connector_url_template ` | No | `true`\|`false` | The URL template of a connector request | From 27667cd582fb513cee9de182336cde984fe67a73 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Fri, 4 Oct 2024 14:22:57 -0600 Subject: [PATCH 10/18] Add doc note about Connectors selectors not being supported on Spans --- .../configuration/telemetry/instrumentation/selectors.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/configuration/telemetry/instrumentation/selectors.mdx b/docs/source/configuration/telemetry/instrumentation/selectors.mdx index 7cc64939ab..6002dbc271 100644 --- a/docs/source/configuration/telemetry/instrumentation/selectors.mdx +++ b/docs/source/configuration/telemetry/instrumentation/selectors.mdx @@ -114,6 +114,10 @@ Apollo Connectors for REST APIs make HTTP calls to the upstream HTTP API. These | `static` | No | | A static string value | | `error` | No | `reason` | A string value containing error reason when it's a critical error | + +The above Apollo Connectors selectors are not currently supported for Spans, only for Instruments, Events, and Conditions. + + ### GraphQL GraphQL metrics are extracted from the response data the router returns to client requests. From cabb35e3a2b7d79f3ac3c42532f711e30370e5ca Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Mon, 7 Oct 2024 07:28:12 -0400 Subject: [PATCH 11/18] feat(telemetry): add ability to set span attributes on connector span Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- .../telemetry/config_new/connector/events.rs | 1 + .../connector/{http.rs => http/mod.rs} | 0 .../config_new/connector/instruments.rs | 1 + .../{connector.rs => connector/mod.rs} | 3 +- .../telemetry/config_new/connector/spans.rs | 27 ++++++ .../src/plugins/telemetry/config_new/spans.rs | 6 ++ .../plugins/telemetry/dynamic_attribute.rs | 90 +++++++++++++++++++ apollo-router/src/plugins/telemetry/mod.rs | 63 ++++++++++--- .../src/services/connector_service.rs | 3 + 9 files changed, 179 insertions(+), 15 deletions(-) rename apollo-router/src/plugins/telemetry/config_new/connector/{http.rs => http/mod.rs} (100%) rename apollo-router/src/plugins/telemetry/config_new/{connector.rs => connector/mod.rs} (72%) create mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/spans.rs diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs index a2ae3bba9f..7d0392eb12 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs @@ -10,6 +10,7 @@ use crate::plugins::telemetry::config_new::extendable::Extendable; #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct ConnectorEventsKind { + // TODO: I don't think we need this, we should be consistent with what we have today and everything is namespaced like http.* pub(crate) http: Extendable< ConnectorHttpEventsConfig, Event, diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/mod.rs similarity index 100% rename from apollo-router/src/plugins/telemetry/config_new/connector/http.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/http/mod.rs diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs index aede418cc5..cac4e36d7c 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs @@ -11,6 +11,7 @@ use crate::plugins::telemetry::config_new::instruments::Instrument; #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] pub(crate) struct ConnectorInstrumentsKind { + // TODO: I don't think we need this, we should be consistent with what we have today and everything is namespaced like http.* pub(crate) http: Extendable< ConnectorHttpInstrumentsConfig, Instrument, diff --git a/apollo-router/src/plugins/telemetry/config_new/connector.rs b/apollo-router/src/plugins/telemetry/config_new/connector/mod.rs similarity index 72% rename from apollo-router/src/plugins/telemetry/config_new/connector.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/mod.rs index c4cd7a2525..119e39a43d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/mod.rs @@ -1,5 +1,4 @@ -//! Connectors telemetry. - pub(crate) mod events; pub(crate) mod http; pub(crate) mod instruments; +pub(crate) mod spans; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/spans.rs b/apollo-router/src/plugins/telemetry/config_new/connector/spans.rs new file mode 100644 index 0000000000..a0e38b3863 --- /dev/null +++ b/apollo-router/src/plugins/telemetry/config_new/connector/spans.rs @@ -0,0 +1,27 @@ +use schemars::JsonSchema; +use serde::Deserialize; + +use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; +use crate::plugins::telemetry::config_new::conditional::Conditional; +use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; +use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::DefaultForLevel; +use crate::plugins::telemetry::otlp::TelemetryDataKind; + +#[derive(Deserialize, JsonSchema, Clone, Default, Debug)] +#[serde(deny_unknown_fields, default)] +pub(crate) struct ConnectorSpans { + /// Custom attributes that are attached to the connector span. + pub(crate) attributes: Extendable>, +} + +impl DefaultForLevel for ConnectorSpans { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + kind: TelemetryDataKind, + ) { + self.attributes.defaults_for_level(requirement_level, kind); + } +} diff --git a/apollo-router/src/plugins/telemetry/config_new/spans.rs b/apollo-router/src/plugins/telemetry/config_new/spans.rs index 0abf12b797..929f25c220 100644 --- a/apollo-router/src/plugins/telemetry/config_new/spans.rs +++ b/apollo-router/src/plugins/telemetry/config_new/spans.rs @@ -2,6 +2,8 @@ use schemars::JsonSchema; use serde::Deserialize; use super::conditional::Conditional; +use super::connector::http::selectors::ConnectorHttpSelector; +use super::connector::spans::ConnectorSpans; use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; @@ -35,6 +37,10 @@ pub(crate) struct Spans { /// Attributes to include on the subgraph span. /// Subgraph spans contain information about the subgraph request and response and therefore contain subgraph specific attributes. pub(crate) subgraph: SubgraphSpans, + + /// Attributes to include on the connector span. + /// Connector spans contain information about the connector request and response and therefore contain connector specific attributes. + pub(crate) connector: ConnectorSpans, } impl Spans { diff --git a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs index d9cde3ca21..42873f1e8b 100644 --- a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs +++ b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs @@ -69,6 +69,11 @@ impl DynAttributeLayer { pub(crate) trait SpanDynAttribute { fn set_span_dyn_attribute(&self, key: Key, value: opentelemetry::Value); fn set_span_dyn_attributes(&self, attributes: impl IntoIterator); + fn set_span_dyn_attributes_for_span_name( + &self, + span_name: &str, + attributes: impl IntoIterator, + ); } impl SpanDynAttribute for ::tracing::Span { @@ -193,6 +198,91 @@ impl SpanDynAttribute for ::tracing::Span { } }); } + + fn set_span_dyn_attributes_for_span_name( + &self, + span_name: &str, + attributes: impl IntoIterator, + ) { + let mut attributes = attributes.into_iter().peekable(); + if attributes.peek().is_none() { + return; + } + self.with_subscriber(move |(id, dispatch)| { + if let Some(reg) = dispatch.downcast_ref::() { + match reg.span(id) { + None => eprintln!("no spanref, this is a bug"), + Some(mut s) => { + if s.name() != span_name { + while let Some(parent_span) = s.parent() { + if parent_span.name() == span_name { + s = parent_span; + break; + } + s = parent_span; + } + } + + if s.is_sampled() { + let mut extensions = s.extensions_mut(); + match extensions.get_mut::() { + Some(otel_data) => { + if otel_data.builder.attributes.is_none() { + otel_data.builder.attributes = Some( + attributes + .inspect(|attr| { + update_otel_data( + otel_data, + &attr.key, + &attr.value, + ) + }) + .collect(), + ); + } else { + let attributes: Vec = attributes + .inspect(|attr| { + update_otel_data(otel_data, &attr.key, &attr.value) + }) + .collect(); + otel_data + .builder + .attributes + .as_mut() + .unwrap() + .extend(attributes); + } + } + None => { + // Can't use ::tracing::error! because it could create deadlock on extensions + eprintln!("no OtelData, this is a bug"); + } + } + } else { + let mut attributes = attributes + .filter(|kv| !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) + .peekable(); + if attributes.peek().is_none() { + return; + } + let mut extensions = s.extensions_mut(); + match extensions.get_mut::() { + Some(registered_attributes) => { + registered_attributes.extend(attributes); + } + None => { + // Can't use ::tracing::error! because it could create deadlock on extensions + eprintln!("no LogAttributes, this is a bug"); + } + } + } + } + }; + } else { + ::tracing::error!("no Registry, this is a bug"); + } + }); + } } fn update_otel_data(otel_data: &mut OtelData, key: &Key, value: &opentelemetry::Value) { diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 39791477ae..123ad176e2 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -83,6 +83,7 @@ pub(crate) use self::span_factory::SpanMode; use self::tracing::apollo_telemetry::APOLLO_PRIVATE_DURATION_NS; use self::tracing::apollo_telemetry::CLIENT_NAME_KEY; use self::tracing::apollo_telemetry::CLIENT_VERSION_KEY; +use super::connectors::tracing::CONNECT_SPAN_NAME; use crate::apollo_studio_interop::ExtendedReferenceStats; use crate::apollo_studio_interop::ReferencedEnums; use crate::context::CONTAINS_GRAPHQL_ERROR; @@ -864,6 +865,7 @@ impl PluginPrivate for Telemetry { service: crate::services::http::BoxService, ) -> crate::services::http::BoxService { let req_fn_config = self.config.clone(); + let res_fn_config = self.config.clone(); let static_connector_instruments = self.connector_custom_instruments.read().clone(); ServiceBuilder::new() .map_future_with_request_data( @@ -882,9 +884,17 @@ impl PluginPrivate for Telemetry { .events .new_connector_http_events(); custom_events.on_request(http_request); + + let custom_span_attributes = req_fn_config + .instrumentation + .spans + .connector + .attributes + .on_request(http_request); + ( http_request.context.clone(), - Some((custom_instruments, custom_events)), + Some((custom_instruments, custom_events, custom_span_attributes)), ) } else { (http_request.context.clone(), None) @@ -892,27 +902,54 @@ impl PluginPrivate for Telemetry { }, move |(context, custom_telemetry): ( Context, - Option<(ConnectorHttpInstruments, ConnectorHttpEvents)>, + Option<(ConnectorHttpInstruments, ConnectorHttpEvents, Vec)>, ), f: BoxFuture< 'static, Result, >| { + let conf = res_fn_config.clone(); async move { - let result = f.await; - if let Some((custom_instruments, custom_events)) = custom_telemetry { - match &result { - Ok(resp) => { - custom_instruments.on_response(resp); - custom_events.on_response(resp); - } - Err(err) => { - custom_instruments.on_error(err, &context); - custom_events.on_error(err, &context); + match custom_telemetry { + Some((custom_instruments, custom_events, custom_span_attributes)) => { + let span = Span::current(); + span.set_span_dyn_attributes_for_span_name( + CONNECT_SPAN_NAME, + custom_span_attributes, + ); + let result = f.await; + + match &result { + Ok(resp) => { + span.set_span_dyn_attributes_for_span_name( + CONNECT_SPAN_NAME, + conf.instrumentation + .spans + .connector + .attributes + .on_response(resp), + ); + custom_instruments.on_response(resp); + custom_events.on_response(resp); + } + Err(err) => { + span.set_span_dyn_attributes_for_span_name( + CONNECT_SPAN_NAME, + conf.instrumentation + .spans + .connector + .attributes + .on_error(err, &context), + ); + custom_instruments.on_error(err, &context); + custom_events.on_error(err, &context); + } } + + result } + None => f.await, } - result } }, ) diff --git a/apollo-router/src/services/connector_service.rs b/apollo-router/src/services/connector_service.rs index e115c9c2e5..54de55c96d 100644 --- a/apollo-router/src/services/connector_service.rs +++ b/apollo-router/src/services/connector_service.rs @@ -124,6 +124,9 @@ impl tower::Service for ConnectorService { "apollo.connector.source.detail" = tracing::field::Empty, "apollo_private.sent_time_offset" = fetch_time_offset, ); + // TODO: I think we should get rid of these attributes by default and only add it from custom telemetry. We just need to double check it's not required for Studio. + + // These additionnal attributes will be added to custom telemetry feature // TODO: apollo.connector.field.alias // TODO: apollo.connector.field.return_type // TODO: apollo.connector.field.selection_set From d36e3d26c4080924a6a589f9312529281c3056d4 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Tue, 8 Oct 2024 11:44:12 -0600 Subject: [PATCH 12/18] Collapse http telemetry up into connector --- ...nfiguration__tests__schema_generation.snap | 198 +++++----- .../plugins/telemetry/config_new/connector.rs | 9 +- .../connector/{http => }/attributes.rs | 16 +- .../telemetry/config_new/connector/events.rs | 210 ++++++++++- .../telemetry/config_new/connector/http.rs | 6 - .../config_new/connector/http/events.rs | 215 ----------- .../config_new/connector/http/instruments.rs | 328 ----------------- .../config_new/connector/instruments.rs | 340 +++++++++++++++++- .../connector/{http => }/selectors.rs | 185 +++++----- .../plugins/telemetry/config_new/events.rs | 18 +- .../metrics.snap | 33 +- .../router.yaml | 33 +- .../connector/custom_histogram/metrics.snap | 29 +- .../connector/custom_histogram/router.yaml | 29 +- .../http_client_request_duration/router.yaml | 31 +- .../subgraph_and_connector/router.yaml | 7 +- .../telemetry/config_new/instruments.rs | 23 +- ..._tests__connector_events_request@logs.snap | 2 +- ...tests__connector_events_response@logs.snap | 2 +- .../src/plugins/telemetry/fmt_layer.rs | 69 ++-- apollo-router/src/plugins/telemetry/mod.rs | 27 +- ..._with_custom_events_with_instrumented.snap | 4 +- ..._with_custom_events_with_instrumented.snap | 4 +- .../testdata/custom_events.router.yaml | 97 +++-- .../telemetry/instrumentation/events.mdx | 1 - .../telemetry/instrumentation/instruments.mdx | 41 +-- 26 files changed, 950 insertions(+), 1007 deletions(-) rename apollo-router/src/plugins/telemetry/config_new/connector/{http => }/attributes.rs (93%) delete mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/http.rs delete mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs delete mode 100644 apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs rename apollo-router/src/plugins/telemetry/config_new/connector/{http => }/selectors.rs (72%) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 16427e60da..f273ffe378 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -624,7 +624,7 @@ expression: "&schema" } ] }, - "Condition_for_ConnectorHttpSelector": { + "Condition_for_ConnectorSelector": { "oneOf": [ { "additionalProperties": false, @@ -632,8 +632,8 @@ expression: "&schema" "properties": { "eq": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" }, "maxItems": 2, "minItems": 2, @@ -651,8 +651,8 @@ expression: "&schema" "properties": { "gt": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" }, "maxItems": 2, "minItems": 2, @@ -670,8 +670,8 @@ expression: "&schema" "properties": { "lt": { "items": { - "$ref": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector", - "description": "#/definitions/SelectorOrValue_for_ConnectorHttpSelector" + "$ref": "#/definitions/SelectorOrValue_for_ConnectorSelector", + "description": "#/definitions/SelectorOrValue_for_ConnectorSelector" }, "maxItems": 2, "minItems": 2, @@ -688,8 +688,8 @@ expression: "&schema" "description": "A condition to check a selection against a selector.", "properties": { "exists": { - "$ref": "#/definitions/ConnectorHttpSelector", - "description": "#/definitions/ConnectorHttpSelector" + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" } }, "required": [ @@ -703,8 +703,8 @@ expression: "&schema" "properties": { "all": { "items": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" }, "type": "array" } @@ -720,8 +720,8 @@ expression: "&schema" "properties": { "any": { "items": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" }, "type": "array" } @@ -736,8 +736,8 @@ expression: "&schema" "description": "The sub-condition must not be true", "properties": { "not": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" } }, "required": [ @@ -1917,17 +1917,7 @@ expression: "&schema" }, "type": "object" }, - "ConnectorEventsKind": { - "additionalProperties": false, - "properties": { - "http": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" - } - }, - "type": "object" - }, - "ConnectorHttpAttributes": { + "ConnectorAttributes": { "additionalProperties": false, "properties": { "connector.http.method": { @@ -1953,43 +1943,43 @@ expression: "&schema" }, "type": "object" }, - "ConnectorHttpEventsConfig": { + "ConnectorEventsConfig": { "additionalProperties": false, "properties": { "error": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" }, "request": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" }, "response": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" } }, "type": "object" }, - "ConnectorHttpInstrumentsConfig": { + "ConnectorInstrumentsConfig": { "additionalProperties": false, "properties": { "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" } }, "type": "object" }, - "ConnectorHttpSelector": { + "ConnectorSelector": { "anyOf": [ { "additionalProperties": false, @@ -2120,28 +2110,6 @@ expression: "&schema" } ] }, - "ConnectorHttpValue": { - "anyOf": [ - { - "$ref": "#/definitions/Standard", - "description": "#/definitions/Standard" - }, - { - "$ref": "#/definitions/ConnectorHttpSelector", - "description": "#/definitions/ConnectorHttpSelector" - } - ] - }, - "ConnectorInstrumentsKind": { - "additionalProperties": false, - "properties": { - "http": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" - } - }, - "type": "object" - }, "ConnectorSource": { "oneOf": [ { @@ -2153,6 +2121,18 @@ expression: "&schema" } ] }, + "ConnectorValue": { + "anyOf": [ + { + "$ref": "#/definitions/Standard", + "description": "#/definitions/Standard" + }, + { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + } + ] + }, "ConnectorsConfig": { "additionalProperties": false, "properties": { @@ -2476,7 +2456,7 @@ expression: "&schema" } ] }, - "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector": { + "DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector": { "anyOf": [ { "type": "null" @@ -2488,8 +2468,8 @@ expression: "&schema" "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" } }, "required": [ @@ -2742,16 +2722,16 @@ expression: "&schema" } ] }, - "Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector": { + "Event_for_ConnectorAttributes_and_ConnectorSelector": { "description": "An event that can be logged as part of a trace. The event has an implicit `type` attribute that matches the name of the event in the yaml and a message that can be used to provide additional information.", "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "condition": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" }, "level": { "$ref": "#/definitions/EventLevel", @@ -2903,8 +2883,8 @@ expression: "&schema" "description": "Events are", "properties": { "connector": { - "$ref": "#/definitions/ConnectorEventsKind", - "description": "#/definitions/ConnectorEventsKind" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::events::ConnectorEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::events::ConnectorEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event" }, "router": { "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::events::RouterEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event", @@ -3828,16 +3808,16 @@ expression: "&schema" ], "type": "object" }, - "Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue": { + "Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue": { "additionalProperties": false, "properties": { "attributes": { - "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "condition": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" }, "description": { "description": "The description of the instrument.", @@ -3852,8 +3832,8 @@ expression: "&schema" "type": "string" }, "value": { - "$ref": "#/definitions/ConnectorHttpValue", - "description": "#/definitions/ConnectorHttpValue" + "$ref": "#/definitions/ConnectorValue", + "description": "#/definitions/ConnectorValue" } }, "required": [ @@ -4035,8 +4015,8 @@ expression: "&schema" "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::cache::CacheInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "connector": { - "$ref": "#/definitions/ConnectorInstrumentsKind", - "description": "#/definitions/ConnectorInstrumentsKind" + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument" }, "default_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", @@ -5801,15 +5781,15 @@ expression: "&schema" }, "type": "object" }, - "SelectorOrValue_for_ConnectorHttpSelector": { + "SelectorOrValue_for_ConnectorSelector": { "anyOf": [ { "$ref": "#/definitions/AttributeValue", "description": "#/definitions/AttributeValue" }, { - "$ref": "#/definitions/ConnectorHttpSelector", - "description": "#/definitions/ConnectorHttpSelector" + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" } ] }, @@ -6006,7 +5986,7 @@ expression: "&schema" } ] }, - "StandardEventConfig_for_ConnectorHttpSelector": { + "StandardEventConfig_for_ConnectorSelector": { "anyOf": [ { "$ref": "#/definitions/EventLevel", @@ -6015,8 +5995,8 @@ expression: "&schema" { "properties": { "condition": { - "$ref": "#/definitions/Condition_for_ConnectorHttpSelector", - "description": "#/definitions/Condition_for_ConnectorHttpSelector" + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" }, "level": { "$ref": "#/definitions/EventLevel", @@ -8435,10 +8415,10 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector": { "additionalProperties": { - "$ref": "#/definitions/ConnectorHttpSelector", - "description": "#/definitions/ConnectorHttpSelector" + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" }, "properties": { "connector.http.method": { @@ -8464,44 +8444,44 @@ expression: "&schema" }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::events::ConnectorEventsConfig_apollo_router::plugins::telemetry::config_new::events::Event": { "additionalProperties": { - "$ref": "#/definitions/Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector", - "description": "#/definitions/Event_for_ConnectorHttpAttributes_and_ConnectorHttpSelector" + "$ref": "#/definitions/Event_for_ConnectorAttributes_and_ConnectorSelector", + "description": "#/definitions/Event_for_ConnectorAttributes_and_ConnectorSelector" }, "properties": { "error": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" }, "request": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" }, "response": { - "$ref": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector", - "description": "#/definitions/StandardEventConfig_for_ConnectorHttpSelector" + "$ref": "#/definitions/StandardEventConfig_for_ConnectorSelector", + "description": "#/definitions/StandardEventConfig_for_ConnectorSelector" } }, "type": "object" }, - "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsConfig_apollo_router::plugins::telemetry::config_new::instruments::Instrument": { "additionalProperties": { - "$ref": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue", - "description": "#/definitions/Instrument_for_ConnectorHttpAttributes_and_ConnectorHttpSelector_and_ConnectorHttpValue" + "$ref": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue", + "description": "#/definitions/Instrument_for_ConnectorAttributes_and_ConnectorSelector_and_ConnectorValue" }, "properties": { "http.client.request.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "http.client.request.duration": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" }, "http.client.response.body.size": { - "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector", - "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes_apollo_router::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector" + "$ref": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/DefaultedStandardInstrument_for_extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" } }, "type": "object" diff --git a/apollo-router/src/plugins/telemetry/config_new/connector.rs b/apollo-router/src/plugins/telemetry/config_new/connector.rs index c4cd7a2525..32d6be43de 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector.rs @@ -1,5 +1,12 @@ //! Connectors telemetry. +use crate::services::http::HttpRequest; +use crate::services::http::HttpResponse; + +pub(crate) mod attributes; pub(crate) mod events; -pub(crate) mod http; pub(crate) mod instruments; +pub(crate) mod selectors; + +pub(crate) type ConnectorRequest = HttpRequest; +pub(crate) type ConnectorResponse = HttpResponse; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs b/apollo-router/src/plugins/telemetry/config_new/connector/attributes.rs similarity index 93% rename from apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/attributes.rs index cf95763d06..4b68d4aee3 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/attributes.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/attributes.rs @@ -1,5 +1,3 @@ -//! Attributes for HTTP connectors. - use opentelemetry_api::Key; use opentelemetry_api::KeyValue; use schemars::JsonSchema; @@ -9,13 +7,13 @@ use tower::BoxError; use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; use crate::plugins::telemetry::config_new::attributes::StandardAttribute; use crate::plugins::telemetry::config_new::attributes::SUBGRAPH_NAME; +use crate::plugins::telemetry::config_new::connector::ConnectorRequest; +use crate::plugins::telemetry::config_new::connector::ConnectorResponse; use crate::plugins::telemetry::config_new::DefaultForLevel; use crate::plugins::telemetry::config_new::Selectors; use crate::plugins::telemetry::otlp::TelemetryDataKind; use crate::services::connector_service::ConnectorInfo; use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; use crate::Context; const CONNECTOR_HTTP_METHOD: Key = Key::from_static_str("connector.http.method"); @@ -24,7 +22,7 @@ const CONNECTOR_URL_TEMPLATE: Key = Key::from_static_str("connector.url.template #[derive(Deserialize, JsonSchema, Clone, Default, Debug)] #[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorHttpAttributes { +pub(crate) struct ConnectorAttributes { /// The name of the subgraph containing the connector /// Examples: /// @@ -63,7 +61,7 @@ pub(crate) struct ConnectorHttpAttributes { connector_url_template: Option, } -impl DefaultForLevel for ConnectorHttpAttributes { +impl DefaultForLevel for ConnectorAttributes { fn defaults_for_level( &mut self, requirement_level: DefaultAttributeRequirementLevel, @@ -94,9 +92,9 @@ impl DefaultForLevel for ConnectorHttpAttributes { } } -impl Selectors for ConnectorHttpAttributes { - type Request = HttpRequest; - type Response = HttpResponse; +impl Selectors for ConnectorAttributes { + type Request = ConnectorRequest; + type Response = ConnectorResponse; type EventResponse = (); fn on_request(&self, request: &Self::Request) -> Vec { diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs index a2ae3bba9f..978b42ef8f 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/events.rs @@ -1,17 +1,211 @@ +use opentelemetry_api::Key; +use opentelemetry_api::KeyValue; +use opentelemetry_semantic_conventions::trace::HTTP_REQUEST_METHOD; +use parking_lot::Mutex; use schemars::JsonSchema; use serde::Deserialize; +use tower::BoxError; -use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEventsConfig; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_URI; +use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_VERSION; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_BODY; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_HEADERS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_STATUS; +use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_VERSION; +use crate::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorSelector; +use crate::plugins::telemetry::config_new::connector::ConnectorRequest; +use crate::plugins::telemetry::config_new::connector::ConnectorResponse; +use crate::plugins::telemetry::config_new::events::log_event; +use crate::plugins::telemetry::config_new::events::CustomEvent; +use crate::plugins::telemetry::config_new::events::CustomEventInner; +use crate::plugins::telemetry::config_new::events::CustomEvents; use crate::plugins::telemetry::config_new::events::Event; +use crate::plugins::telemetry::config_new::events::EventLevel; +use crate::plugins::telemetry::config_new::events::StandardEvent; +use crate::plugins::telemetry::config_new::events::StandardEventConfig; use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::instruments::Instrumented; +use crate::Context; #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorEventsKind { - pub(crate) http: Extendable< - ConnectorHttpEventsConfig, - Event, - >, +pub(crate) struct ConnectorEventsConfig { + /// Log the connector HTTP request + pub(crate) request: StandardEventConfig, + /// Log the connector HTTP response + pub(crate) response: StandardEventConfig, + /// Log the connector HTTP error + pub(crate) error: StandardEventConfig, +} + +#[derive(Clone)] +pub(crate) struct ConnectorEventRequest(pub(crate) StandardEvent); + +#[derive(Clone)] +pub(crate) struct ConnectorEventResponse(pub(crate) StandardEvent); + +pub(crate) type ConnectorEvents = + CustomEvents; + +pub(crate) fn new_connector_events( + config: &Extendable>, +) -> ConnectorEvents { + let custom_events = config + .custom + .iter() + .filter_map(|(event_name, event_cfg)| match &event_cfg.level { + EventLevel::Off => None, + _ => Some(CustomEvent { + inner: Mutex::new(CustomEventInner { + name: event_name.clone(), + level: event_cfg.level, + event_on: event_cfg.on, + message: event_cfg.message.clone(), + selectors: event_cfg.attributes.clone().into(), + condition: event_cfg.condition.clone(), + attributes: Vec::new(), + }), + }), + }) + .collect(); + + ConnectorEvents { + request: config.attributes.request.clone().into(), + response: config.attributes.response.clone().into(), + error: config.attributes.error.clone().into(), + custom: custom_events, + } +} + +impl Instrumented + for CustomEvents +{ + type Request = ConnectorRequest; + type Response = ConnectorResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) { + if self.request.level() != EventLevel::Off { + if let Some(condition) = self.request.condition() { + if condition.lock().evaluate_request(request) != Some(true) { + return; + } + } + let mut attrs = Vec::with_capacity(5); + #[cfg(test)] + let headers = { + let mut headers: indexmap::IndexMap = request + .http_request + .headers() + .clone() + .into_iter() + .filter_map(|(name, val)| Some((name?.to_string(), val))) + .collect(); + headers.sort_keys(); + headers + }; + #[cfg(not(test))] + let headers = request.http_request.headers(); + + attrs.push(KeyValue::new( + HTTP_REQUEST_HEADERS, + opentelemetry::Value::String(format!("{:?}", headers).into()), + )); + attrs.push(KeyValue::new( + HTTP_REQUEST_METHOD, + opentelemetry::Value::String(format!("{}", request.http_request.method()).into()), + )); + attrs.push(KeyValue::new( + HTTP_REQUEST_URI, + opentelemetry::Value::String(format!("{}", request.http_request.uri()).into()), + )); + attrs.push(KeyValue::new( + HTTP_REQUEST_VERSION, + opentelemetry::Value::String( + format!("{:?}", request.http_request.version()).into(), + ), + )); + attrs.push(KeyValue::new( + HTTP_REQUEST_BODY, + opentelemetry::Value::String(format!("{:?}", request.http_request.body()).into()), + )); + log_event(self.request.level(), "connector.request", attrs, ""); + } + for custom_event in &self.custom { + custom_event.on_request(request); + } + } + + fn on_response(&self, response: &Self::Response) { + if self.response.level() != EventLevel::Off { + if let Some(condition) = self.response.condition() { + if !condition.lock().evaluate_response(response) { + return; + } + } + let mut attrs = Vec::with_capacity(4); + #[cfg(test)] + let headers = { + let mut headers: indexmap::IndexMap = response + .http_response + .headers() + .clone() + .into_iter() + .filter_map(|(name, val)| Some((name?.to_string(), val))) + .collect(); + headers.sort_keys(); + headers + }; + #[cfg(not(test))] + let headers = response.http_response.headers(); + + attrs.push(KeyValue::new( + HTTP_RESPONSE_HEADERS, + opentelemetry::Value::String(format!("{:?}", headers).into()), + )); + attrs.push(KeyValue::new( + HTTP_RESPONSE_STATUS, + opentelemetry::Value::String(format!("{}", response.http_response.status()).into()), + )); + attrs.push(KeyValue::new( + HTTP_RESPONSE_VERSION, + opentelemetry::Value::String( + format!("{:?}", response.http_response.version()).into(), + ), + )); + attrs.push(KeyValue::new( + HTTP_RESPONSE_BODY, + opentelemetry::Value::String(format!("{:?}", response.http_response.body()).into()), + )); + log_event(self.response.level(), "connector.response", attrs, ""); + } + for custom_event in &self.custom { + custom_event.on_response(response); + } + } + + fn on_error(&self, error: &BoxError, ctx: &Context) { + if self.error.level() != EventLevel::Off { + if let Some(condition) = self.error.condition() { + if !condition.lock().evaluate_error(error, ctx) { + return; + } + } + log_event( + self.error.level(), + "connector.http.error", + vec![KeyValue::new( + Key::from_static_str("error"), + opentelemetry::Value::String(error.to_string().into()), + )], + "", + ); + } + for custom_event in &self.custom { + custom_event.on_error(error, ctx); + } + } } diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http.rs deleted file mode 100644 index 1c7f1b490d..0000000000 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Telemetry related to HTTP requests and responses for connectors. - -pub(crate) mod attributes; -pub(crate) mod events; -pub(crate) mod instruments; -pub(crate) mod selectors; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs deleted file mode 100644 index 175803b136..0000000000 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/events.rs +++ /dev/null @@ -1,215 +0,0 @@ -use opentelemetry_api::Key; -use opentelemetry_api::KeyValue; -use opentelemetry_semantic_conventions::trace::HTTP_REQUEST_METHOD; -use parking_lot::Mutex; -use schemars::JsonSchema; -use serde::Deserialize; -use tower::BoxError; - -use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_BODY; -use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_HEADERS; -use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_URI; -use crate::plugins::telemetry::config_new::attributes::HTTP_REQUEST_VERSION; -use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_BODY; -use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_HEADERS; -use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_STATUS; -use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_VERSION; -use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; -use crate::plugins::telemetry::config_new::events::log_event; -use crate::plugins::telemetry::config_new::events::CustomEvent; -use crate::plugins::telemetry::config_new::events::CustomEventInner; -use crate::plugins::telemetry::config_new::events::CustomEvents; -use crate::plugins::telemetry::config_new::events::Event; -use crate::plugins::telemetry::config_new::events::EventLevel; -use crate::plugins::telemetry::config_new::events::StandardEvent; -use crate::plugins::telemetry::config_new::events::StandardEventConfig; -use crate::plugins::telemetry::config_new::extendable::Extendable; -use crate::plugins::telemetry::config_new::instruments::Instrumented; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; -use crate::Context; - -#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] -#[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorHttpEventsConfig { - /// Log the connector HTTP request - pub(crate) request: StandardEventConfig, - /// Log the connector HTTP response - pub(crate) response: StandardEventConfig, - /// Log the connector HTTP error - pub(crate) error: StandardEventConfig, -} - -#[derive(Clone)] -pub(crate) struct ConnectorHttpEventRequest(pub(crate) StandardEvent); - -#[derive(Clone)] -pub(crate) struct ConnectorHttpEventResponse(pub(crate) StandardEvent); - -pub(crate) type ConnectorHttpEvents = - CustomEvents; - -pub(crate) fn new_connector_http_events( - config: &Extendable< - ConnectorHttpEventsConfig, - Event, - >, -) -> ConnectorHttpEvents { - let custom_events = config - .custom - .iter() - .filter_map(|(event_name, event_cfg)| match &event_cfg.level { - EventLevel::Off => None, - _ => Some(CustomEvent { - inner: Mutex::new(CustomEventInner { - name: event_name.clone(), - level: event_cfg.level, - event_on: event_cfg.on, - message: event_cfg.message.clone(), - selectors: event_cfg.attributes.clone().into(), - condition: event_cfg.condition.clone(), - attributes: Vec::new(), - }), - }), - }) - .collect(); - - ConnectorHttpEvents { - request: config.attributes.request.clone().into(), - response: config.attributes.response.clone().into(), - error: config.attributes.error.clone().into(), - custom: custom_events, - } -} - -impl Instrumented - for CustomEvents -{ - type Request = HttpRequest; - type Response = HttpResponse; - type EventResponse = (); - - fn on_request(&self, request: &Self::Request) { - if self.request.level() != EventLevel::Off { - if let Some(condition) = self.request.condition() { - if condition.lock().evaluate_request(request) != Some(true) { - return; - } - } - let mut attrs = Vec::with_capacity(5); - #[cfg(test)] - let headers = { - let mut headers: indexmap::IndexMap = request - .http_request - .headers() - .clone() - .into_iter() - .filter_map(|(name, val)| Some((name?.to_string(), val))) - .collect(); - headers.sort_keys(); - headers - }; - #[cfg(not(test))] - let headers = request.http_request.headers(); - - attrs.push(KeyValue::new( - HTTP_REQUEST_HEADERS, - opentelemetry::Value::String(format!("{:?}", headers).into()), - )); - attrs.push(KeyValue::new( - HTTP_REQUEST_METHOD, - opentelemetry::Value::String(format!("{}", request.http_request.method()).into()), - )); - attrs.push(KeyValue::new( - HTTP_REQUEST_URI, - opentelemetry::Value::String(format!("{}", request.http_request.uri()).into()), - )); - attrs.push(KeyValue::new( - HTTP_REQUEST_VERSION, - opentelemetry::Value::String( - format!("{:?}", request.http_request.version()).into(), - ), - )); - attrs.push(KeyValue::new( - HTTP_REQUEST_BODY, - opentelemetry::Value::String(format!("{:?}", request.http_request.body()).into()), - )); - log_event(self.request.level(), "connector.http.request", attrs, ""); - } - for custom_event in &self.custom { - custom_event.on_request(request); - } - } - - fn on_response(&self, response: &Self::Response) { - if self.response.level() != EventLevel::Off { - if let Some(condition) = self.response.condition() { - if !condition.lock().evaluate_response(response) { - return; - } - } - let mut attrs = Vec::with_capacity(4); - - #[cfg(test)] - let headers = { - let mut headers: indexmap::IndexMap = response - .http_response - .headers() - .clone() - .into_iter() - .filter_map(|(name, val)| Some((name?.to_string(), val))) - .collect(); - headers.sort_keys(); - headers - }; - #[cfg(not(test))] - let headers = response.http_response.headers(); - - attrs.push(KeyValue::new( - HTTP_RESPONSE_HEADERS, - opentelemetry::Value::String(format!("{:?}", headers).into()), - )); - attrs.push(KeyValue::new( - HTTP_RESPONSE_STATUS, - opentelemetry::Value::String(format!("{}", response.http_response.status()).into()), - )); - attrs.push(KeyValue::new( - HTTP_RESPONSE_VERSION, - opentelemetry::Value::String( - format!("{:?}", response.http_response.version()).into(), - ), - )); - attrs.push(KeyValue::new( - HTTP_RESPONSE_BODY, - opentelemetry::Value::String(format!("{:?}", response.http_response.body()).into()), - )); - log_event(self.response.level(), "connector.http.response", attrs, ""); - } - for custom_event in &self.custom { - custom_event.on_response(response); - } - } - - fn on_error(&self, error: &BoxError, ctx: &Context) { - if self.error.level() != EventLevel::Off { - if let Some(condition) = self.error.condition() { - if !condition.lock().evaluate_error(error, ctx) { - return; - } - } - log_event( - self.error.level(), - "connector.http.error", - vec![KeyValue::new( - Key::from_static_str("error"), - opentelemetry::Value::String(error.to_string().into()), - )], - "", - ); - } - for custom_event in &self.custom { - custom_event.on_error(error, ctx); - } - } -} diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs deleted file mode 100644 index b04bd42c27..0000000000 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/instruments.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Instruments for HTTP connectors. - -use std::collections::HashMap; -use std::sync::Arc; - -use opentelemetry::metrics::MeterProvider; -use opentelemetry_api::metrics::Unit; -use parking_lot::Mutex; -use schemars::JsonSchema; -use serde::Deserialize; -use tokio::time::Instant; -use tower::BoxError; - -use crate::metrics; -use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; -use crate::plugins::telemetry::config_new::conditions::Condition; -use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpValue; -use crate::plugins::telemetry::config_new::extendable::Extendable; -use crate::plugins::telemetry::config_new::instruments::CustomHistogram; -use crate::plugins::telemetry::config_new::instruments::CustomHistogramInner; -use crate::plugins::telemetry::config_new::instruments::CustomInstruments; -use crate::plugins::telemetry::config_new::instruments::DefaultedStandardInstrument; -use crate::plugins::telemetry::config_new::instruments::Increment; -use crate::plugins::telemetry::config_new::instruments::Instrument; -use crate::plugins::telemetry::config_new::instruments::Instrumented; -use crate::plugins::telemetry::config_new::instruments::StaticInstrument; -use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC; -use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_DURATION_METRIC; -use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC; -use crate::plugins::telemetry::config_new::instruments::METER_NAME; -use crate::plugins::telemetry::config_new::DefaultForLevel; -use crate::plugins::telemetry::otlp::TelemetryDataKind; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; -use crate::Context; - -#[derive(Clone, Deserialize, JsonSchema, Debug, Default)] -#[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorHttpInstrumentsConfig { - /// Histogram of client request duration - #[serde(rename = "http.client.request.duration")] - http_client_request_duration: - DefaultedStandardInstrument>, - - /// Histogram of client request body size - #[serde(rename = "http.client.request.body.size")] - http_client_request_body_size: - DefaultedStandardInstrument>, - - /// Histogram of client response body size - #[serde(rename = "http.client.response.body.size")] - http_client_response_body_size: - DefaultedStandardInstrument>, -} - -impl DefaultForLevel for ConnectorHttpInstrumentsConfig { - fn defaults_for_level( - &mut self, - requirement_level: DefaultAttributeRequirementLevel, - kind: TelemetryDataKind, - ) { - self.http_client_request_duration - .defaults_for_level(requirement_level, kind); - self.http_client_request_body_size - .defaults_for_level(requirement_level, kind); - self.http_client_response_body_size - .defaults_for_level(requirement_level, kind); - } -} - -pub(crate) struct ConnectorHttpInstruments { - http_client_request_duration: Option< - CustomHistogram, - >, - http_client_request_body_size: Option< - CustomHistogram, - >, - http_client_response_body_size: Option< - CustomHistogram, - >, - custom: ConnectorHttpCustomInstruments, -} - -impl ConnectorHttpInstruments { - pub(crate) fn new( - config: &Extendable< - ConnectorHttpInstrumentsConfig, - Instrument, - >, - static_instruments: Arc>, - ) -> Self { - let http_client_request_duration = - config - .attributes - .http_client_request_duration - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &config.attributes.http_client_request_duration { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Duration(Instant::now()), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_REQUEST_DURATION_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: None, - selectors, - updated: false, - }), - } - }); - let http_client_request_body_size = - config - .attributes - .http_client_request_body_size - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &config.attributes.http_client_request_body_size { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Custom(None), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorHttpSelector::ConnectorRequestHeader { - connector_http_request_header: "content-length".to_string(), - redact: None, - default: None, - })), - selectors, - updated: false, - }), - } - }); - let http_client_response_body_size = - config - .attributes - .http_client_response_body_size - .is_enabled() - .then(|| { - let mut nb_attributes = 0; - let selectors = match &config.attributes.http_client_response_body_size { - DefaultedStandardInstrument::Bool(_) - | DefaultedStandardInstrument::Unset => None, - DefaultedStandardInstrument::Extendable { attributes } => { - nb_attributes = attributes.custom.len(); - Some(attributes.clone()) - } - }; - CustomHistogram { - inner: Mutex::new(CustomHistogramInner { - increment: Increment::Custom(None), - condition: Condition::True, - histogram: Some(static_instruments - .get(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) - .expect( - "cannot get static instrument for connector; this should not happen", - ) - .as_histogram() - .cloned() - .expect( - "cannot convert instrument to histogram for connector; this should not happen", - ) - ), - attributes: Vec::with_capacity(nb_attributes), - selector: Some(Arc::new(ConnectorHttpSelector::ConnectorResponseHeader { - connector_http_response_header: "content-length".to_string(), - redact: None, - default: None, - })), - selectors, - updated: false, - }), - } - }); - ConnectorHttpInstruments { - http_client_request_duration, - http_client_request_body_size, - http_client_response_body_size, - custom: CustomInstruments::new(&config.custom, static_instruments), - } - } - - pub(crate) fn new_builtin( - config: &Extendable< - ConnectorHttpInstrumentsConfig, - Instrument, - >, - ) -> HashMap { - let meter = metrics::meter_provider().meter(METER_NAME); - let mut static_instruments = HashMap::with_capacity(3); - - if config.attributes.http_client_request_duration.is_enabled() { - static_instruments.insert( - HTTP_CLIENT_REQUEST_DURATION_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_REQUEST_DURATION_METRIC) - .with_unit(Unit::new("s")) - .with_description("Duration of HTTP client requests.") - .init(), - ), - ); - } - - if config.attributes.http_client_request_body_size.is_enabled() { - static_instruments.insert( - HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) - .with_unit(Unit::new("By")) - .with_description("Size of HTTP client request bodies.") - .init(), - ), - ); - } - - if config - .attributes - .http_client_response_body_size - .is_enabled() - { - static_instruments.insert( - HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC.to_string(), - StaticInstrument::Histogram( - meter - .f64_histogram(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) - .with_unit(Unit::new("By")) - .with_description("Size of HTTP client response bodies.") - .init(), - ), - ); - } - - static_instruments - } -} - -impl Instrumented for ConnectorHttpInstruments { - type Request = HttpRequest; - type Response = HttpResponse; - type EventResponse = (); - - fn on_request(&self, request: &Self::Request) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_request(request); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_request(request); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_request(request); - } - self.custom.on_request(request); - } - - fn on_response(&self, response: &Self::Response) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_response(response); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_response(response); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_response(response); - } - self.custom.on_response(response); - } - - fn on_error(&self, error: &BoxError, ctx: &Context) { - if let Some(http_client_request_duration) = &self.http_client_request_duration { - http_client_request_duration.on_error(error, ctx); - } - if let Some(http_client_request_body_size) = &self.http_client_request_body_size { - http_client_request_body_size.on_error(error, ctx); - } - if let Some(http_client_response_body_size) = &self.http_client_response_body_size { - http_client_response_body_size.on_error(error, ctx); - } - self.custom.on_error(error, ctx); - } -} - -pub(crate) type ConnectorHttpCustomInstruments = CustomInstruments< - HttpRequest, - HttpResponse, - ConnectorHttpAttributes, - ConnectorHttpSelector, - ConnectorHttpValue, ->; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs index aede418cc5..ebffe7ccc1 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/instruments.rs @@ -1,18 +1,342 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use opentelemetry::metrics::MeterProvider; +use opentelemetry_api::metrics::Unit; +use parking_lot::Mutex; use schemars::JsonSchema; use serde::Deserialize; +use tokio::time::Instant; +use tower::BoxError; -use crate::plugins::telemetry::config_new::connector::http::attributes::ConnectorHttpAttributes; -use crate::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstrumentsConfig; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; -use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpValue; +use crate::metrics; +use crate::plugins::telemetry::config_new::attributes::DefaultAttributeRequirementLevel; +use crate::plugins::telemetry::config_new::conditions::Condition; +use crate::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorSelector; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorValue; +use crate::plugins::telemetry::config_new::connector::ConnectorRequest; +use crate::plugins::telemetry::config_new::connector::ConnectorResponse; +use crate::plugins::telemetry::config_new::connector::HttpRequest; use crate::plugins::telemetry::config_new::extendable::Extendable; +use crate::plugins::telemetry::config_new::instruments::CustomHistogram; +use crate::plugins::telemetry::config_new::instruments::CustomHistogramInner; +use crate::plugins::telemetry::config_new::instruments::CustomInstruments; +use crate::plugins::telemetry::config_new::instruments::DefaultedStandardInstrument; +use crate::plugins::telemetry::config_new::instruments::Increment; use crate::plugins::telemetry::config_new::instruments::Instrument; +use crate::plugins::telemetry::config_new::instruments::Instrumented; +use crate::plugins::telemetry::config_new::instruments::StaticInstrument; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_REQUEST_DURATION_METRIC; +use crate::plugins::telemetry::config_new::instruments::HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC; +use crate::plugins::telemetry::config_new::instruments::METER_NAME; +use crate::plugins::telemetry::config_new::DefaultForLevel; +use crate::plugins::telemetry::otlp::TelemetryDataKind; +use crate::Context; #[derive(Clone, Deserialize, JsonSchema, Debug, Default)] #[serde(deny_unknown_fields, default)] -pub(crate) struct ConnectorInstrumentsKind { - pub(crate) http: Extendable< - ConnectorHttpInstrumentsConfig, - Instrument, +pub(crate) struct ConnectorInstrumentsConfig { + /// Histogram of client request duration + #[serde(rename = "http.client.request.duration")] + http_client_request_duration: + DefaultedStandardInstrument>, + + /// Histogram of client request body size + #[serde(rename = "http.client.request.body.size")] + http_client_request_body_size: + DefaultedStandardInstrument>, + + /// Histogram of client response body size + #[serde(rename = "http.client.response.body.size")] + http_client_response_body_size: + DefaultedStandardInstrument>, +} + +impl DefaultForLevel for ConnectorInstrumentsConfig { + fn defaults_for_level( + &mut self, + requirement_level: DefaultAttributeRequirementLevel, + kind: TelemetryDataKind, + ) { + self.http_client_request_duration + .defaults_for_level(requirement_level, kind); + self.http_client_request_body_size + .defaults_for_level(requirement_level, kind); + self.http_client_response_body_size + .defaults_for_level(requirement_level, kind); + } +} + +pub(crate) struct ConnectorInstruments { + http_client_request_duration: Option< + CustomHistogram< + ConnectorRequest, + ConnectorResponse, + ConnectorAttributes, + ConnectorSelector, + >, + >, + http_client_request_body_size: Option< + CustomHistogram< + ConnectorRequest, + ConnectorResponse, + ConnectorAttributes, + ConnectorSelector, + >, + >, + http_client_response_body_size: Option< + CustomHistogram< + ConnectorRequest, + ConnectorResponse, + ConnectorAttributes, + ConnectorSelector, + >, >, + custom: ConnectorCustomInstruments, +} + +impl ConnectorInstruments { + pub(crate) fn new( + config: &Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, + static_instruments: Arc>, + ) -> Self { + let http_client_request_duration = + config + .attributes + .http_client_request_duration + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_request_duration { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Duration(Instant::now()), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: None, + selectors, + updated: false, + }), + } + }); + let http_client_request_body_size = + config + .attributes + .http_client_request_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_request_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorSelector::HttpRequestHeader { + connector_http_request_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + let http_client_response_body_size = + config + .attributes + .http_client_response_body_size + .is_enabled() + .then(|| { + let mut nb_attributes = 0; + let selectors = match &config.attributes.http_client_response_body_size { + DefaultedStandardInstrument::Bool(_) + | DefaultedStandardInstrument::Unset => None, + DefaultedStandardInstrument::Extendable { attributes } => { + nb_attributes = attributes.custom.len(); + Some(attributes.clone()) + } + }; + CustomHistogram { + inner: Mutex::new(CustomHistogramInner { + increment: Increment::Custom(None), + condition: Condition::True, + histogram: Some(static_instruments + .get(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .expect( + "cannot get static instrument for connector; this should not happen", + ) + .as_histogram() + .cloned() + .expect( + "cannot convert instrument to histogram for connector; this should not happen", + ) + ), + attributes: Vec::with_capacity(nb_attributes), + selector: Some(Arc::new(ConnectorSelector::ConnectorResponseHeader { + connector_http_response_header: "content-length".to_string(), + redact: None, + default: None, + })), + selectors, + updated: false, + }), + } + }); + ConnectorInstruments { + http_client_request_duration, + http_client_request_body_size, + http_client_response_body_size, + custom: CustomInstruments::new(&config.custom, static_instruments), + } + } + + pub(crate) fn new_builtin( + config: &Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, + ) -> HashMap { + let meter = metrics::meter_provider().meter(METER_NAME); + let mut static_instruments = HashMap::with_capacity(3); + + if config.attributes.http_client_request_duration.is_enabled() { + static_instruments.insert( + HTTP_CLIENT_REQUEST_DURATION_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_DURATION_METRIC) + .with_unit(Unit::new("s")) + .with_description("Duration of HTTP client requests.") + .init(), + ), + ); + } + + if config.attributes.http_client_request_body_size.is_enabled() { + static_instruments.insert( + HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_REQUEST_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client request bodies.") + .init(), + ), + ); + } + + if config + .attributes + .http_client_response_body_size + .is_enabled() + { + static_instruments.insert( + HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC.to_string(), + StaticInstrument::Histogram( + meter + .f64_histogram(HTTP_CLIENT_RESPONSE_BODY_SIZE_METRIC) + .with_unit(Unit::new("By")) + .with_description("Size of HTTP client response bodies.") + .init(), + ), + ); + } + + static_instruments + } } + +impl Instrumented for ConnectorInstruments { + type Request = ConnectorRequest; + type Response = ConnectorResponse; + type EventResponse = (); + + fn on_request(&self, request: &Self::Request) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_request(request); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_request(request); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_request(request); + } + self.custom.on_request(request); + } + + fn on_response(&self, response: &Self::Response) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_response(response); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_response(response); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_response(response); + } + self.custom.on_response(response); + } + + fn on_error(&self, error: &BoxError, ctx: &Context) { + if let Some(http_client_request_duration) = &self.http_client_request_duration { + http_client_request_duration.on_error(error, ctx); + } + if let Some(http_client_request_body_size) = &self.http_client_request_body_size { + http_client_request_body_size.on_error(error, ctx); + } + if let Some(http_client_response_body_size) = &self.http_client_response_body_size { + http_client_response_body_size.on_error(error, ctx); + } + self.custom.on_error(error, ctx); + } +} + +pub(crate) type ConnectorCustomInstruments = CustomInstruments< + HttpRequest, + ConnectorResponse, + ConnectorAttributes, + ConnectorSelector, + ConnectorValue, +>; diff --git a/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs b/apollo-router/src/plugins/telemetry/config_new/connector/selectors.rs similarity index 72% rename from apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/selectors.rs index 00f5ae4497..dd2e1168d8 100644 --- a/apollo-router/src/plugins/telemetry/config_new/connector/http/selectors.rs +++ b/apollo-router/src/plugins/telemetry/config_new/connector/selectors.rs @@ -1,5 +1,3 @@ -//! Selectors for HTTP connectors. - use derivative::Derivative; use opentelemetry_api::Value; use schemars::JsonSchema; @@ -7,6 +5,8 @@ use serde::Deserialize; use tower::BoxError; use crate::plugins::telemetry::config::AttributeValue; +use crate::plugins::telemetry::config_new::connector::ConnectorRequest; +use crate::plugins::telemetry::config_new::connector::ConnectorResponse; use crate::plugins::telemetry::config_new::instruments::InstrumentValue; use crate::plugins::telemetry::config_new::instruments::Standard; use crate::plugins::telemetry::config_new::selectors::ErrorRepr; @@ -15,8 +15,6 @@ use crate::plugins::telemetry::config_new::Selector; use crate::plugins::telemetry::config_new::Stage; use crate::services::connector_service::ConnectorInfo; use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; -use crate::services::http::HttpRequest; -use crate::services::http::HttpResponse; use crate::Context; #[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)] @@ -28,24 +26,23 @@ pub(crate) enum ConnectorSource { #[derive(Deserialize, JsonSchema, Clone, Debug)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] -pub(crate) enum ConnectorHttpValue { +pub(crate) enum ConnectorValue { Standard(Standard), - Custom(ConnectorHttpSelector), + Custom(ConnectorSelector), } -impl From<&ConnectorHttpValue> for InstrumentValue { - fn from(value: &ConnectorHttpValue) -> Self { +impl From<&ConnectorValue> for InstrumentValue { + fn from(value: &ConnectorValue) -> Self { match value { - ConnectorHttpValue::Standard(s) => InstrumentValue::Standard(s.clone()), - ConnectorHttpValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), + ConnectorValue::Standard(s) => InstrumentValue::Standard(s.clone()), + ConnectorValue::Custom(selector) => InstrumentValue::Custom(selector.clone()), } } } - #[derive(Deserialize, JsonSchema, Clone, Derivative)] #[serde(deny_unknown_fields, rename_all = "snake_case", untagged)] #[derivative(Debug, PartialEq)] -pub(crate) enum ConnectorHttpSelector { +pub(crate) enum ConnectorSelector { SubgraphName { /// The subgraph name subgraph_name: bool, @@ -54,7 +51,7 @@ pub(crate) enum ConnectorHttpSelector { /// The connector source. connector_source: ConnectorSource, }, - ConnectorRequestHeader { + HttpRequestHeader { /// The name of a connector HTTP request header. connector_http_request_header: String, #[serde(skip)] @@ -96,9 +93,9 @@ pub(crate) enum ConnectorHttpSelector { }, } -impl Selector for ConnectorHttpSelector { - type Request = HttpRequest; - type Response = HttpResponse; +impl Selector for ConnectorSelector { + type Request = ConnectorRequest; + type Response = ConnectorResponse; type EventResponse = (); fn on_request(&self, request: &Self::Request) -> Option { @@ -106,33 +103,31 @@ impl Selector for ConnectorHttpSelector { .context .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { + ConnectorSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { + ConnectorSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorRequestHeader { + ConnectorSelector::HttpRequestHeader { connector_http_request_header: connector_request_header, default, .. @@ -143,7 +138,7 @@ impl Selector for ConnectorHttpSelector { .and_then(|h| Some(h.to_str().ok()?.to_string())) .or_else(|| default.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -153,33 +148,31 @@ impl Selector for ConnectorHttpSelector { .context .get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { + ConnectorSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { + ConnectorSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorResponseHeader { + ConnectorSelector::ConnectorResponseHeader { connector_http_response_header: connector_response_header, default, .. @@ -190,7 +183,7 @@ impl Selector for ConnectorHttpSelector { .and_then(|h| Some(h.to_str().ok()?.to_string())) .or_else(|| default.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorResponseStatus { + ConnectorSelector::ConnectorResponseStatus { connector_http_response_status: response_status, } => match response_status { ResponseStatus::Code => { @@ -202,7 +195,7 @@ impl Selector for ConnectorHttpSelector { .canonical_reason() .map(|reason| reason.into()), }, - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -210,41 +203,39 @@ impl Selector for ConnectorHttpSelector { fn on_error(&self, error: &BoxError, ctx: &Context) -> Option { let connector_info = ctx.get::<&str, ConnectorInfo>(CONNECTOR_INFO_CONTEXT_KEY); match self { - ConnectorHttpSelector::SubgraphName { subgraph_name } if *subgraph_name => { - connector_info - .ok() - .flatten() - .map(|info| info.subgraph_name.clone()) - .map(opentelemetry::Value::from) - } - ConnectorHttpSelector::ConnectorSource { .. } => connector_info + ConnectorSelector::SubgraphName { subgraph_name } if *subgraph_name => connector_info + .ok() + .flatten() + .map(|info| info.subgraph_name.clone()) + .map(opentelemetry::Value::from), + ConnectorSelector::ConnectorSource { .. } => connector_info .ok() .flatten() .and_then(|info| info.source_name.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorHttpMethod { + ConnectorSelector::ConnectorHttpMethod { connector_http_method, } if *connector_http_method => connector_info .ok() .flatten() .map(|info| info.http_method.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::ConnectorUrlTemplate { + ConnectorSelector::ConnectorUrlTemplate { connector_url_template, } if *connector_url_template => connector_info .ok() .flatten() .map(|info| info.url_template.clone()) .map(opentelemetry::Value::from), - ConnectorHttpSelector::Error { .. } => Some(error.to_string().into()), - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorSelector::Error { .. } => Some(error.to_string().into()), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } fn on_drop(&self) -> Option { match self { - ConnectorHttpSelector::StaticField { r#static } => Some(r#static.clone().into()), + ConnectorSelector::StaticField { r#static } => Some(r#static.clone().into()), _ => None, } } @@ -253,35 +244,35 @@ impl Selector for ConnectorHttpSelector { match stage { Stage::Request => matches!( self, - ConnectorHttpSelector::ConnectorRequestHeader { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } + ConnectorSelector::HttpRequestHeader { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } ), Stage::Response => matches!( self, - ConnectorHttpSelector::ConnectorResponseHeader { .. } - | ConnectorHttpSelector::ConnectorResponseStatus { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } + ConnectorSelector::ConnectorResponseHeader { .. } + | ConnectorSelector::ConnectorResponseStatus { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } ), Stage::ResponseEvent => false, Stage::ResponseField => false, Stage::Error => matches!( self, - ConnectorHttpSelector::Error { .. } - | ConnectorHttpSelector::SubgraphName { .. } - | ConnectorHttpSelector::ConnectorSource { .. } - | ConnectorHttpSelector::ConnectorHttpMethod { .. } - | ConnectorHttpSelector::ConnectorUrlTemplate { .. } - | ConnectorHttpSelector::StaticField { .. } + ConnectorSelector::Error { .. } + | ConnectorSelector::SubgraphName { .. } + | ConnectorSelector::ConnectorSource { .. } + | ConnectorSelector::ConnectorHttpMethod { .. } + | ConnectorSelector::ConnectorUrlTemplate { .. } + | ConnectorSelector::StaticField { .. } ), - Stage::Drop => matches!(self, ConnectorHttpSelector::StaticField { .. }), + Stage::Drop => matches!(self, ConnectorSelector::StaticField { .. }), } } } @@ -291,8 +282,10 @@ mod tests { use apollo_federation::sources::connect::HTTPMethod; use http::StatusCode; + use super::ConnectorSelector; use super::ConnectorSource; - use crate::plugins::telemetry::config_new::connector::http::selectors::ConnectorHttpSelector; + use crate::plugins::telemetry::config_new::connector::ConnectorRequest; + use crate::plugins::telemetry::config_new::connector::ConnectorResponse; use crate::plugins::telemetry::config_new::selectors::ResponseStatus; use crate::plugins::telemetry::config_new::Selector; use crate::services::connector_service::ConnectorInfo; @@ -325,14 +318,14 @@ mod tests { context } - fn http_request(context: Context) -> HttpRequest { + fn http_request(context: Context) -> ConnectorRequest { HttpRequest { http_request: http::Request::builder().body("".into()).unwrap(), context, } } - fn http_request_with_header(context: Context) -> HttpRequest { + fn http_request_with_header(context: Context) -> ConnectorRequest { HttpRequest { http_request: http::Request::builder() .header(TEST_HEADER_NAME, TEST_HEADER_VALUE) @@ -342,7 +335,7 @@ mod tests { } } - fn http_response(context: Context, status_code: StatusCode) -> HttpResponse { + fn http_response(context: Context, status_code: StatusCode) -> ConnectorResponse { HttpResponse { http_response: http::Response::builder() .status(status_code) @@ -352,7 +345,7 @@ mod tests { } } - fn http_response_with_header(context: Context, status_code: StatusCode) -> HttpResponse { + fn http_response_with_header(context: Context, status_code: StatusCode) -> ConnectorResponse { HttpResponse { http_response: http::Response::builder() .status(status_code) @@ -365,7 +358,7 @@ mod tests { #[test] fn connector_on_request_static_field() { - let selector = ConnectorHttpSelector::StaticField { + let selector = ConnectorSelector::StaticField { r#static: TEST_STATIC.into(), }; assert_eq!( @@ -376,7 +369,7 @@ mod tests { #[test] fn connector_on_request_subgraph_name() { - let selector = ConnectorHttpSelector::SubgraphName { + let selector = ConnectorSelector::SubgraphName { subgraph_name: true, }; assert_eq!( @@ -387,7 +380,7 @@ mod tests { #[test] fn connector_on_request_connector_source() { - let selector = ConnectorHttpSelector::ConnectorSource { + let selector = ConnectorSelector::ConnectorSource { connector_source: ConnectorSource::Name, }; assert_eq!( @@ -398,7 +391,7 @@ mod tests { #[test] fn connector_on_request_url_template() { - let selector = ConnectorHttpSelector::ConnectorUrlTemplate { + let selector = ConnectorSelector::ConnectorUrlTemplate { connector_url_template: true, }; assert_eq!( @@ -409,7 +402,7 @@ mod tests { #[test] fn connector_on_request_header_defaulted() { - let selector = ConnectorHttpSelector::ConnectorRequestHeader { + let selector = ConnectorSelector::HttpRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), @@ -422,7 +415,7 @@ mod tests { #[test] fn connector_on_request_header_with_value() { - let selector = ConnectorHttpSelector::ConnectorRequestHeader { + let selector = ConnectorSelector::HttpRequestHeader { connector_http_request_header: TEST_HEADER_NAME.to_string(), redact: None, default: None, @@ -435,7 +428,7 @@ mod tests { #[test] fn connector_on_response_static_field() { - let selector = ConnectorHttpSelector::StaticField { + let selector = ConnectorSelector::StaticField { r#static: TEST_STATIC.into(), }; assert_eq!( @@ -446,7 +439,7 @@ mod tests { #[test] fn connector_on_response_subgraph_name() { - let selector = ConnectorHttpSelector::SubgraphName { + let selector = ConnectorSelector::SubgraphName { subgraph_name: true, }; assert_eq!( @@ -457,7 +450,7 @@ mod tests { #[test] fn connector_on_response_connector_source() { - let selector = ConnectorHttpSelector::ConnectorSource { + let selector = ConnectorSelector::ConnectorSource { connector_source: ConnectorSource::Name, }; assert_eq!( @@ -468,7 +461,7 @@ mod tests { #[test] fn connector_on_response_url_template() { - let selector = ConnectorHttpSelector::ConnectorUrlTemplate { + let selector = ConnectorSelector::ConnectorUrlTemplate { connector_url_template: true, }; assert_eq!( @@ -479,7 +472,7 @@ mod tests { #[test] fn connector_on_response_header_defaulted() { - let selector = ConnectorHttpSelector::ConnectorResponseHeader { + let selector = ConnectorSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: Some("defaulted".into()), @@ -492,7 +485,7 @@ mod tests { #[test] fn connector_on_response_header_with_value() { - let selector = ConnectorHttpSelector::ConnectorResponseHeader { + let selector = ConnectorSelector::ConnectorResponseHeader { connector_http_response_header: TEST_HEADER_NAME.to_string(), redact: None, default: None, @@ -508,7 +501,7 @@ mod tests { #[test] fn connector_on_response_status_code() { - let selector = ConnectorHttpSelector::ConnectorResponseStatus { + let selector = ConnectorSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Code, }; assert_eq!( @@ -519,7 +512,7 @@ mod tests { #[test] fn connector_on_response_status_reason_ok() { - let selector = ConnectorHttpSelector::ConnectorResponseStatus { + let selector = ConnectorSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Reason, }; assert_eq!( @@ -530,7 +523,7 @@ mod tests { #[test] fn connector_on_response_status_code_not_found() { - let selector = ConnectorHttpSelector::ConnectorResponseStatus { + let selector = ConnectorSelector::ConnectorResponseStatus { connector_http_response_status: ResponseStatus::Reason, }; assert_eq!( @@ -544,7 +537,7 @@ mod tests { #[test] fn connector_on_response_http_method() { - let selector = ConnectorHttpSelector::ConnectorHttpMethod { + let selector = ConnectorSelector::ConnectorHttpMethod { connector_http_method: true, }; assert_eq!( @@ -555,7 +548,7 @@ mod tests { #[test] fn connector_on_drop_static_field() { - let selector = ConnectorHttpSelector::StaticField { + let selector = ConnectorSelector::StaticField { r#static: TEST_STATIC.into(), }; assert_eq!(Some(TEST_STATIC.into()), selector.on_drop()); diff --git a/apollo-router/src/plugins/telemetry/config_new/events.rs b/apollo-router/src/plugins/telemetry/config_new/events.rs index 22f75c7d9c..46150c8d16 100644 --- a/apollo-router/src/plugins/telemetry/config_new/events.rs +++ b/apollo-router/src/plugins/telemetry/config_new/events.rs @@ -29,8 +29,10 @@ use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_HEADERS; use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_STATUS; use crate::plugins::telemetry::config_new::attributes::HTTP_RESPONSE_VERSION; use crate::plugins::telemetry::config_new::conditions::Condition; -use crate::plugins::telemetry::config_new::connector::events::ConnectorEventsKind; -use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEvents; +use crate::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes; +use crate::plugins::telemetry::config_new::connector::events::ConnectorEvents; +use crate::plugins::telemetry::config_new::connector::events::ConnectorEventsConfig; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorSelector; use crate::plugins::telemetry::config_new::extendable::Extendable; use crate::plugins::telemetry::config_new::selectors::RouterSelector; use crate::plugins::telemetry::config_new::selectors::SubgraphSelector; @@ -52,7 +54,7 @@ pub(crate) struct Events { /// Supergraph service events subgraph: Extendable>, /// Connector events - connector: ConnectorEventsKind, + connector: Extendable>, } impl Events { @@ -143,8 +145,8 @@ impl Events { } } - pub(crate) fn new_connector_http_events(&self) -> ConnectorHttpEvents { - super::connector::http::events::new_connector_http_events(&self.connector.http) + pub(crate) fn new_connector_events(&self) -> ConnectorEvents { + super::connector::events::new_connector_events(&self.connector) } pub(crate) fn validate(&self) -> Result<(), String> { @@ -177,12 +179,12 @@ impl Events { condition.validate(Some(Stage::Response))?; } if let StandardEventConfig::Conditional { condition, .. } = - &self.connector.http.attributes.request + &self.connector.attributes.request { condition.validate(Some(Stage::Request))?; } if let StandardEventConfig::Conditional { condition, .. } = - &self.connector.http.attributes.response + &self.connector.attributes.response { condition.validate(Some(Stage::Response))?; } @@ -201,7 +203,7 @@ impl Events { format!("configuration error for subgraph custom event {name:?}: {err}") })?; } - for (name, custom_event) in &self.connector.http.custom { + for (name, custom_event) in &self.connector.custom { custom_event.validate().map_err(|err| { format!("configuration error for connector HTTP custom event {name:?}: {err}") })?; diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap index b22c408f07..df5e1cec39 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/metrics.snap @@ -8,23 +8,22 @@ info: instruments: default_requirement_level: none connector: - http: - not.found.count: - description: Count of 404 responses from the user API - type: counter - unit: count - value: unit - attributes: - url_template: - connector_url_template: true - condition: - all: - - eq: - - 404 - - connector_http_response_status: code - - eq: - - user_api - - connector_source: name + not.found.count: + description: Count of 404 responses from the user API + type: counter + unit: count + value: unit + attributes: + url_template: + connector_url_template: true + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - user_api + - connector_source: name --- - name: not.found.count description: Count of 404 responses from the user API diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml index 32cf12e538..a62a2ddce8 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_counter_with_conditions/router.yaml @@ -3,20 +3,19 @@ telemetry: instruments: default_requirement_level: none connector: - http: - not.found.count: - description: "Count of 404 responses from the user API" - type: counter - unit: count - value: unit - attributes: - "url_template": - connector_url_template: true - condition: - all: - - eq: - - 404 - - connector_http_response_status: code - - eq: - - "user_api" - - connector_source: name \ No newline at end of file + not.found.count: + description: "Count of 404 responses from the user API" + type: counter + unit: count + value: unit + attributes: + "url_template": + connector_url_template: true + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap index 1520ee35a0..ed4504a203 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/metrics.snap @@ -8,21 +8,20 @@ info: instruments: default_requirement_level: none connector: - http: - rate.limit: - value: - connector_http_response_header: x-ratelimit-remaining - unit: count - type: histogram - description: Rate limit remaining - condition: - all: - - eq: - - 200 - - connector_http_response_status: code - - eq: - - user_api - - connector_source: name + rate.limit: + value: + connector_http_response_header: x-ratelimit-remaining + unit: count + type: histogram + description: Rate limit remaining + condition: + all: + - eq: + - 200 + - connector_http_response_status: code + - eq: + - user_api + - connector_source: name --- - name: rate.limit description: Rate limit remaining diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml index 8d487b6efa..c91eacbc00 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/custom_histogram/router.yaml @@ -3,18 +3,17 @@ telemetry: instruments: default_requirement_level: none connector: - http: - rate.limit: - value: - connector_http_response_header: "x-ratelimit-remaining" - unit: count - type: histogram - description: "Rate limit remaining" - condition: - all: - - eq: - - 200 - - connector_http_response_status: code - - eq: - - "user_api" - - connector_source: name + rate.limit: + value: + connector_http_response_header: "x-ratelimit-remaining" + unit: count + type: histogram + description: "Rate limit remaining" + condition: + all: + - eq: + - 200 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml index 5d79f1f9f5..276dfe9e6d 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/http_client_request_duration/router.yaml @@ -3,19 +3,18 @@ telemetry: instruments: default_requirement_level: none connector: - http: - http.client.request.duration: - attributes: - subgraph.name: true - connector.source: - connector_source: name - connector.http.method: true - connector.url.template: true - custom.request.header.attribute: - connector_http_request_header: "custom_request_header" - custom.response.header.attribute: - connector_http_response_header: "custom_response_header" - custom.response.status.attribute: - connector_http_response_status: code - custom.static.attribute: - static: "custom_value" \ No newline at end of file + http.client.request.duration: + attributes: + subgraph.name: true + connector.source: + connector_source: name + connector.http.method: true + connector.url.template: true + custom.request.header.attribute: + connector_http_request_header: "custom_request_header" + custom.response.header.attribute: + connector_http_response_header: "custom_response_header" + custom.response.status.attribute: + connector_http_response_status: code + custom.static.attribute: + static: "custom_value" \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml index 348e87700f..054f4931aa 100644 --- a/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml +++ b/apollo-router/src/plugins/telemetry/config_new/fixtures/connector/subgraph_and_connector/router.yaml @@ -7,7 +7,6 @@ telemetry: attributes: subgraph.name: true connector: - http: - http.client.request.duration: - attributes: - subgraph.name: true \ No newline at end of file + http.client.request.duration: + attributes: + subgraph.name: true \ No newline at end of file diff --git a/apollo-router/src/plugins/telemetry/config_new/instruments.rs b/apollo-router/src/plugins/telemetry/config_new/instruments.rs index e551027f1d..2314ebabfc 100644 --- a/apollo-router/src/plugins/telemetry/config_new/instruments.rs +++ b/apollo-router/src/plugins/telemetry/config_new/instruments.rs @@ -38,8 +38,11 @@ use crate::plugins::telemetry::config_new::attributes::RouterAttributes; use crate::plugins::telemetry::config_new::attributes::SubgraphAttributes; use crate::plugins::telemetry::config_new::attributes::SupergraphAttributes; use crate::plugins::telemetry::config_new::conditions::Condition; -use crate::plugins::telemetry::config_new::connector::http::instruments::ConnectorHttpInstruments; -use crate::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsKind; +use crate::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes; +use crate::plugins::telemetry::config_new::connector::instruments::ConnectorInstruments; +use crate::plugins::telemetry::config_new::connector::instruments::ConnectorInstrumentsConfig; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorSelector; +use crate::plugins::telemetry::config_new::connector::selectors::ConnectorValue; use crate::plugins::telemetry::config_new::cost::CostInstruments; use crate::plugins::telemetry::config_new::cost::CostInstrumentsConfig; use crate::plugins::telemetry::config_new::extendable::Extendable; @@ -84,7 +87,10 @@ pub(crate) struct InstrumentsConfig { Instrument, >, /// Connector service instruments. - pub(crate) connector: ConnectorInstrumentsKind, + pub(crate) connector: Extendable< + ConnectorInstrumentsConfig, + Instrument, + >, /// GraphQL response field instruments. pub(crate) graphql: Extendable< GraphQLInstrumentsConfig, @@ -133,7 +139,7 @@ impl InstrumentsConfig { format!("error for custom cache instrument {name:?} in condition: {err}") })?; } - for (name, custom) in &self.connector.http.custom { + for (name, custom) in &self.connector.custom { custom.condition.validate(None).map_err(|err| { format!("error for custom connector instrument {name:?} in condition: {err}") })?; @@ -154,7 +160,6 @@ impl InstrumentsConfig { self.graphql .defaults_for_levels(self.default_requirement_level, TelemetryDataKind::Metrics); self.connector - .http .defaults_for_levels(self.default_requirement_level, TelemetryDataKind::Metrics); } @@ -700,15 +705,15 @@ impl InstrumentsConfig { pub(crate) fn new_connector_instruments( &self, static_instruments: Arc>, - ) -> ConnectorHttpInstruments { - ConnectorHttpInstruments::new(&self.connector.http, static_instruments) + ) -> ConnectorInstruments { + ConnectorInstruments::new(&self.connector, static_instruments) } pub(crate) fn new_builtin_connector_instruments(&self) -> HashMap { let meter = metrics::meter_provider().meter(METER_NAME); - let mut static_instruments = ConnectorHttpInstruments::new_builtin(&self.connector.http); + let mut static_instruments = ConnectorInstruments::new_builtin(&self.connector); - for (instrument_name, instrument) in &self.connector.http.custom { + for (instrument_name, instrument) in &self.connector.custom { match instrument.ty { InstrumentType::Counter => { static_instruments.insert( diff --git a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap index ae4b024e28..0a8dbbea06 100644 --- a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap +++ b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_request@logs.snap @@ -3,7 +3,7 @@ source: apollo-router/src/plugins/telemetry/config_new/events.rs expression: yaml --- - fields: - kind: connector.http.request + kind: connector.request level: INFO message: "" - fields: diff --git a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap index a7e1b50fbb..3ff31c6764 100644 --- a/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap +++ b/apollo-router/src/plugins/telemetry/config_new/snapshots/apollo_router__plugins__telemetry__config_new__events__tests__connector_events_response@logs.snap @@ -3,7 +3,7 @@ source: apollo-router/src/plugins/telemetry/config_new/events.rs expression: yaml --- - fields: - kind: connector.http.response + kind: connector.response level: WARN message: "" - fields: diff --git a/apollo-router/src/plugins/telemetry/fmt_layer.rs b/apollo-router/src/plugins/telemetry/fmt_layer.rs index 399a42cae0..6373924e37 100644 --- a/apollo-router/src/plugins/telemetry/fmt_layer.rs +++ b/apollo-router/src/plugins/telemetry/fmt_layer.rs @@ -370,39 +370,38 @@ subgraph: default: "missing" connector: - http: - # Standard events - request: info - response: warn - error: error - - # Custom events - my.connector.request.event: - message: "my request event message" - level: info - on: request - attributes: - subgraph.name: true - connector_source: - connector_source: name - http_method: - connector_http_method: true - url_template: - connector_url_template: true - my.connector.response.event: - message: "my response event message" - level: error - on: response - attributes: - subgraph.name: true - connector_source: - connector_source: name - http_method: - connector_http_method: true - url_template: - connector_url_template: true - response_status: - connector_http_response_status: code"#; + # Standard events + request: info + response: warn + error: error + + # Custom events + my.connector.request.event: + message: "my request event message" + level: info + on: request + attributes: + subgraph.name: true + connector_source: + connector_source: name + http_method: + connector_http_method: true + url_template: + connector_url_template: true + my.connector.response.event: + message: "my response event message" + level: error + on: response + attributes: + subgraph.name: true + connector_source: + connector_source: name + http_method: + connector_http_method: true + url_template: + connector_url_template: true + response_status: + connector_http_response_status: code"#; #[derive(Default, Clone)] struct LogBuffer(Arc>>); @@ -846,7 +845,7 @@ connector: http_request, context, }; - let connector_events = event_config.new_connector_http_events(); + let connector_events = event_config.new_connector_events(); connector_events.on_request(&http_request); let http_response = HttpResponse { @@ -1016,7 +1015,7 @@ connector: http_request, context, }; - let connector_events = event_config.new_connector_http_events(); + let connector_events = event_config.new_connector_events(); connector_events.on_request(&http_request); let http_response = HttpResponse { diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 39791477ae..25f8a5d134 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -10,7 +10,7 @@ use ::tracing::info_span; use ::tracing::Span; use axum::headers::HeaderName; use config_new::cache::CacheInstruments; -use config_new::connector::http::instruments::ConnectorHttpInstruments; +use config_new::connector::instruments::ConnectorInstruments; use config_new::instruments::InstrumentsConfig; use config_new::instruments::StaticInstrument; use config_new::Selectors; @@ -102,7 +102,7 @@ use crate::plugins::telemetry::apollo_exporter::proto::reports::StatsContext; use crate::plugins::telemetry::config::AttributeValue; use crate::plugins::telemetry::config::MetricsCommon; use crate::plugins::telemetry::config::TracingCommon; -use crate::plugins::telemetry::config_new::connector::http::events::ConnectorHttpEvents; +use crate::plugins::telemetry::config_new::connector::events::ConnectorEvents; use crate::plugins::telemetry::config_new::cost::add_cost_attributes; use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; @@ -138,6 +138,7 @@ use crate::query_planner::OperationKind; use crate::router_factory::Endpoint; use crate::services::connector_service::CONNECTOR_INFO_CONTEXT_KEY; use crate::services::execution; +use crate::services::http::HttpRequest; use crate::services::router; use crate::services::subgraph; use crate::services::subgraph::Request; @@ -867,7 +868,7 @@ impl PluginPrivate for Telemetry { let static_connector_instruments = self.connector_custom_instruments.read().clone(); ServiceBuilder::new() .map_future_with_request_data( - move |http_request: &crate::services::http::HttpRequest| { + move |http_request: &HttpRequest| { if http_request .context .contains_key(CONNECTOR_INFO_CONTEXT_KEY) @@ -877,10 +878,8 @@ impl PluginPrivate for Telemetry { .instruments .new_connector_instruments(static_connector_instruments.clone()); custom_instruments.on_request(http_request); - let custom_events = req_fn_config - .instrumentation - .events - .new_connector_http_events(); + let custom_events = + req_fn_config.instrumentation.events.new_connector_events(); custom_events.on_request(http_request); ( http_request.context.clone(), @@ -892,27 +891,29 @@ impl PluginPrivate for Telemetry { }, move |(context, custom_telemetry): ( Context, - Option<(ConnectorHttpInstruments, ConnectorHttpEvents)>, + Option<(ConnectorInstruments, ConnectorEvents)>, ), f: BoxFuture< 'static, Result, >| { async move { - let result = f.await; if let Some((custom_instruments, custom_events)) = custom_telemetry { + let result = f.await; match &result { - Ok(resp) => { - custom_instruments.on_response(resp); - custom_events.on_response(resp); + Ok(http_response) => { + custom_instruments.on_response(http_response); + custom_events.on_response(http_response); } Err(err) => { custom_instruments.on_error(err, &context); custom_events.on_error(err, &context); } } + result + } else { + f.await } - result } }, ) diff --git a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap index b12075462b..677e4faec6 100644 --- a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap +++ b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__json_logging_with_custom_events_with_instrumented.snap @@ -15,7 +15,7 @@ expression: buff.to_string() {"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","my.custom.attribute":["{\"id\":1234,\"name\":\"first_name\"}","{\"id\":567,\"name\":\"second_name\"}"],"response_status":200,"subgraph.name":"subgraph","message":"my response event message","kind":"my.subgraph.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","message":"my event message","kind":"my.subgraph.request.event","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","my.custom.attribute":"[[{\"id\":1234,\"name\":\"first_name\"},{\"id\":567,\"name\":\"second_name\"}],{\"foo\":\"bar\"}]","response_status":200,"subgraph.name":"subgraph_bis","message":"my response event message","kind":"my.subgraph.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} -{"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.request.body":"Body(Empty)","http.request.headers":"{\"x-log-request\": \"log\"}","http.request.method":"GET","http.request.uri":"/","http.request.version":"HTTP/1.1","message":"","kind":"connector.http.request","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.request.body":"Body(Empty)","http.request.headers":"{\"x-log-request\": \"log\"}","http.request.method":"GET","http.request.uri":"/","http.request.version":"HTTP/1.1","message":"","kind":"connector.request","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"INFO","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","connector_source":"source","http_method":"GET","subgraph.name":"connector_subgraph","url_template":"/test","message":"my request event message","kind":"my.connector.request.event","target":"apollo_router::plugins::telemetry::config_new::events"} -{"timestamp":"[timestamp]","level":"WARN","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.response.body":"Body(Empty)","http.response.headers":"{\"x-log-response\": \"log\"}","http.response.status":"200 OK","http.response.version":"HTTP/1.1","message":"","kind":"connector.http.response","target":"apollo_router::plugins::telemetry::config_new::events"} +{"timestamp":"[timestamp]","level":"WARN","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","http.response.body":"Body(Empty)","http.response.headers":"{\"x-log-response\": \"log\"}","http.response.status":"200 OK","http.response.version":"HTTP/1.1","message":"","kind":"connector.response","target":"apollo_router::plugins::telemetry::config_new::events"} {"timestamp":"[timestamp]","level":"ERROR","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","connector_source":"source","http_method":"GET","response_status":200,"subgraph.name":"connector_subgraph","url_template":"/test","message":"my response event message","kind":"my.connector.response.event","target":"apollo_router::plugins::telemetry::config_new::events"} diff --git a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap index b4847455ed..eca09851ee 100644 --- a/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap +++ b/apollo-router/src/plugins/telemetry/snapshots/apollo_router__plugins__telemetry__fmt_layer__tests__text_logging_with_custom_events_with_instrumented.snap @@ -15,7 +15,7 @@ expression: buff.to_string() [timestamp] ERROR my.custom.attribute=["{"id":1234,"name":"first_name"}","{"id":567,"name":"second_name"}"] response_status=200 subgraph.name=subgraph my response event message kind=my.subgraph.response.event [timestamp] INFO my event message kind=my.subgraph.request.event [timestamp] ERROR my.custom.attribute=[[{"id":1234,"name":"first_name"},{"id":567,"name":"second_name"}],{"foo":"bar"}] response_status=200 subgraph.name=subgraph_bis my response event message kind=my.subgraph.response.event -[timestamp] INFO http.request.body=Body(Empty) http.request.headers={"x-log-request": "log"} http.request.method=GET http.request.uri=/ http.request.version=HTTP/1.1 kind=connector.http.request +[timestamp] INFO http.request.body=Body(Empty) http.request.headers={"x-log-request": "log"} http.request.method=GET http.request.uri=/ http.request.version=HTTP/1.1 kind=connector.request [timestamp] INFO connector_source=source http_method=GET subgraph.name=connector_subgraph url_template=/test my request event message kind=my.connector.request.event -[timestamp] WARN http.response.body=Body(Empty) http.response.headers={"x-log-response": "log"} http.response.status=200 OK http.response.version=HTTP/1.1 kind=connector.http.response +[timestamp] WARN http.response.body=Body(Empty) http.response.headers={"x-log-response": "log"} http.response.status=200 OK http.response.version=HTTP/1.1 kind=connector.response [timestamp] ERROR connector_source=source http_method=GET response_status=200 subgraph.name=connector_subgraph url_template=/test my response event message kind=my.connector.response.event diff --git a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml index edb34136c1..cd97d0604e 100644 --- a/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml +++ b/apollo-router/src/plugins/telemetry/testdata/custom_events.router.yaml @@ -146,55 +146,54 @@ telemetry: subgraph_response_data: "$.*" default: "missing" connector: - http: - # Standard events - request: - level: info - condition: - eq: - - connector_http_request_header: x-log-request + # Standard events + request: + level: info + condition: + eq: + - connector_http_request_header: x-log-request + - "log" + response: + level: warn + condition: + all: + - eq: + - connector_http_response_header: x-log-response - "log" - response: - level: warn - condition: - all: - - eq: - - connector_http_response_header: x-log-response - - "log" - - eq: - - subgraph_name: true - - "subgraph" + - eq: + - subgraph_name: true + - "subgraph" error: error - # Custom events - my.disabled_request.event: - message: "my disabled event message" - level: off - on: request - my.request.event: - message: "my request event message" - level: info - on: request - condition: - eq: - - connector_http_request_header: x-log-request - - "log" - my.response.event: - message: "my response event message" - level: error - on: response - condition: - all: - - eq: - - connector_http_response_header: x-log-response - - "log" - - eq: - - 200 - - connector_http_response_status: code - - eq: - - subgraph_name: true - - "subgraph" - attributes: - subgraph.name: true - response_status: - connector_http_response_status: code \ No newline at end of file + # Custom events + my.disabled_request.event: + message: "my disabled event message" + level: off + on: request + my.request.event: + message: "my request event message" + level: info + on: request + condition: + eq: + - connector_http_request_header: x-log-request + - "log" + my.response.event: + message: "my response event message" + level: error + on: response + condition: + all: + - eq: + - connector_http_response_header: x-log-response + - "log" + - eq: + - 200 + - connector_http_response_status: code + - eq: + - subgraph_name: true + - "subgraph" + attributes: + subgraph.name: true + response_status: + connector_http_response_status: code \ No newline at end of file diff --git a/docs/source/configuration/telemetry/instrumentation/events.mdx b/docs/source/configuration/telemetry/instrumentation/events.mdx index bf31c78467..ce74bf99c2 100644 --- a/docs/source/configuration/telemetry/instrumentation/events.mdx +++ b/docs/source/configuration/telemetry/instrumentation/events.mdx @@ -232,7 +232,6 @@ telemetry: subgraph: # Custom event configuration for subgraph service ... connector: - http: # Custom event configuration for HTTP connectors ... ``` diff --git a/docs/source/configuration/telemetry/instrumentation/instruments.mdx b/docs/source/configuration/telemetry/instrumentation/instruments.mdx index 4406476db5..342a889f4a 100644 --- a/docs/source/configuration/telemetry/instrumentation/instruments.mdx +++ b/docs/source/configuration/telemetry/instrumentation/instruments.mdx @@ -48,10 +48,9 @@ telemetry: http.client.request.duration: true # (default false) http.client.response.body.size: true # (default false) connector: - http: - http.client.request.body.size: true # (default false) - http.client.request.duration: true # (default false) - http.client.response.body.size: true # (default false) + http.client.request.body.size: true # (default false) + http.client.request.duration: true # (default false) + http.client.response.body.size: true # (default false) ``` They can be customized by attaching or removing attributes. See [attributes](#attributes) to learn more about configuring attributes. @@ -70,10 +69,9 @@ telemetry: attributes: subgraph.name: true connector: - http: - http.client.request.duration: - attributes: - connector.source.name: true + http.client.request.duration: + attributes: + connector.source.name: true ``` ### Apollo standard instruments @@ -130,20 +128,19 @@ telemetry: description: "my description" connector: - http: - acme.user.not.found: - value: unit - type: counter - unit: count - description: "Count of 404 responses from the user API" - condition: - all: - - eq: - - 404 - - connector_http_response_status: code - - eq: - - "user_api" - - connector_source: name + acme.user.not.found: + value: unit + type: counter + unit: count + description: "Count of 404 responses from the user API" + condition: + all: + - eq: + - 404 + - connector_http_response_status: code + - eq: + - "user_api" + - connector_source: name graphql: acme.graphql.list.lengths: From 9a442ca30f82b1e6106f53d44d81cf05fbcde381 Mon Sep 17 00:00:00 2001 From: Matthew Hawkins Date: Tue, 8 Oct 2024 13:56:27 -0600 Subject: [PATCH 13/18] Move connector.rs to mod.rs --- .../telemetry/config_new/{connector.rs => connector/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apollo-router/src/plugins/telemetry/config_new/{connector.rs => connector/mod.rs} (100%) diff --git a/apollo-router/src/plugins/telemetry/config_new/connector.rs b/apollo-router/src/plugins/telemetry/config_new/connector/mod.rs similarity index 100% rename from apollo-router/src/plugins/telemetry/config_new/connector.rs rename to apollo-router/src/plugins/telemetry/config_new/connector/mod.rs From 98ea9858ae120055b817834aca179277b69deea5 Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:27:20 +0200 Subject: [PATCH 14/18] wip Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- apollo-router/src/plugins/connectors/tests.rs | 2 +- .../src/plugins/connectors/tracing.rs | 1 - apollo-router/src/plugins/telemetry/consts.rs | 1 + .../src/plugins/telemetry/dynamic_attribute.rs | 18 ++++++++++++++---- .../src/plugins/telemetry/formatters/json.rs | 11 +++++++++-- .../src/plugins/telemetry/formatters/mod.rs | 3 +++ apollo-router/src/plugins/telemetry/mod.rs | 2 +- .../src/plugins/telemetry/tracing/mod.rs | 15 +++++++++------ .../src/services/connector_service.rs | 3 +-- 9 files changed, 39 insertions(+), 17 deletions(-) diff --git a/apollo-router/src/plugins/connectors/tests.rs b/apollo-router/src/plugins/connectors/tests.rs index daa5564ee5..523c3417d1 100644 --- a/apollo-router/src/plugins/connectors/tests.rs +++ b/apollo-router/src/plugins/connectors/tests.rs @@ -26,7 +26,7 @@ use wiremock::MockServer; use wiremock::ResponseTemplate; use crate::json_ext::ValueExt; -use crate::plugins::connectors::tracing::CONNECT_SPAN_NAME; +use crate::plugins::telemetry::consts::CONNECT_SPAN_NAME; use crate::plugins::telemetry::consts::OTEL_STATUS_CODE; use crate::router_factory::RouterSuperServiceFactory; use crate::router_factory::YamlRouterFactory; diff --git a/apollo-router/src/plugins/connectors/tracing.rs b/apollo-router/src/plugins/connectors/tracing.rs index 6e2f448a77..c215116fe2 100644 --- a/apollo-router/src/plugins/connectors/tracing.rs +++ b/apollo-router/src/plugins/connectors/tracing.rs @@ -1,2 +1 @@ -pub(crate) const CONNECT_SPAN_NAME: &str = "connect"; pub(crate) const CONNECTOR_TYPE_HTTP: &str = "http"; diff --git a/apollo-router/src/plugins/telemetry/consts.rs b/apollo-router/src/plugins/telemetry/consts.rs index c82d7b202b..dbf34f4532 100644 --- a/apollo-router/src/plugins/telemetry/consts.rs +++ b/apollo-router/src/plugins/telemetry/consts.rs @@ -19,6 +19,7 @@ pub(crate) const QUERY_PLANNING_SPAN_NAME: &str = "query_planning"; pub(crate) const HTTP_REQUEST_SPAN_NAME: &str = "http_request"; pub(crate) const SUBGRAPH_REQUEST_SPAN_NAME: &str = "subgraph_request"; pub(crate) const QUERY_PARSING_SPAN_NAME: &str = "parse_query"; +pub(crate) const CONNECT_SPAN_NAME: &str = "connect"; pub(crate) const BUILT_IN_SPAN_NAMES: [&str; 9] = [ REQUEST_SPAN_NAME, diff --git a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs index 42873f1e8b..8b893f1add 100644 --- a/apollo-router/src/plugins/telemetry/dynamic_attribute.rs +++ b/apollo-router/src/plugins/telemetry/dynamic_attribute.rs @@ -10,6 +10,7 @@ use super::consts::OTEL_KIND; use super::consts::OTEL_NAME; use super::consts::OTEL_STATUS_CODE; use super::consts::OTEL_STATUS_MESSAGE; +use super::formatters::APOLLO_CONNECTOR_PREFIX; use super::formatters::APOLLO_PRIVATE_PREFIX; use super::otel::layer::str_to_span_kind; use super::otel::layer::str_to_status; @@ -106,7 +107,7 @@ impl SpanDynAttribute for ::tracing::Span { } } } else { - if key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) { + if key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) || key.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) { return; } let mut extensions = s.extensions_mut(); @@ -175,7 +176,10 @@ impl SpanDynAttribute for ::tracing::Span { } } else { let mut attributes = attributes - .filter(|kv| !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) + .filter(|kv| { + !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) + && !kv.key.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) + }) .peekable(); if attributes.peek().is_none() { return; @@ -260,7 +264,10 @@ impl SpanDynAttribute for ::tracing::Span { } } else { let mut attributes = attributes - .filter(|kv| !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) + .filter(|kv| { + !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) + && !kv.key.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) + }) .peekable(); if attributes.peek().is_none() { return; @@ -355,7 +362,10 @@ impl EventDynAttribute for ::tracing::Span { } } else { let mut attributes = attributes - .filter(|kv| !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) + .filter(|kv| { + !kv.key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) + && !kv.key.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) + }) .peekable(); if attributes.peek().is_none() { return; diff --git a/apollo-router/src/plugins/telemetry/formatters/json.rs b/apollo-router/src/plugins/telemetry/formatters/json.rs index 8f6551ce14..fc53ac1499 100644 --- a/apollo-router/src/plugins/telemetry/formatters/json.rs +++ b/apollo-router/src/plugins/telemetry/formatters/json.rs @@ -20,6 +20,7 @@ use tracing_subscriber::registry::SpanRef; use super::get_trace_and_span_id; use super::EventFormatter; +use super::APOLLO_CONNECTOR_PREFIX; use super::APOLLO_PRIVATE_PREFIX; use super::EXCLUDED_ATTRIBUTES; use crate::plugins::telemetry::config::AttributeValue; @@ -128,7 +129,9 @@ where if let Some(otel_attributes) = otel_attributes { for (key, value) in otel_attributes.iter().filter(|(key, _)| { let key_name = key.as_str(); - !key_name.starts_with(APOLLO_PRIVATE_PREFIX) && !self.1.contains(&key_name) + !key_name.starts_with(APOLLO_PRIVATE_PREFIX) + && !key_name.starts_with(APOLLO_CONNECTOR_PREFIX) + && !self.1.contains(&key_name) }) { serializer.serialize_entry(key.as_str(), &value.as_str())?; } @@ -147,7 +150,9 @@ where }; for kv in custom_attributes.iter().filter(|kv| { let key_name = kv.key.as_str(); - !key_name.starts_with(APOLLO_PRIVATE_PREFIX) && !self.1.contains(&key_name) + !key_name.starts_with(APOLLO_PRIVATE_PREFIX) + && !key_name.starts_with(APOLLO_CONNECTOR_PREFIX) + && !self.1.contains(&key_name) }) { match &kv.value { Value::Bool(value) => { @@ -403,6 +408,7 @@ where .filter(|(key, _)| { let key_name = key.as_str(); !key_name.starts_with(APOLLO_PRIVATE_PREFIX) + && !key_name.starts_with(APOLLO_CONNECTOR_PREFIX) && include_attributes.contains(key_name) }) .map(|(key, val)| (key.clone(), val.clone())), @@ -427,6 +433,7 @@ where .filter(|kv| { let key_name = kv.key.as_str(); !key_name.starts_with(APOLLO_PRIVATE_PREFIX) + && !key_name.starts_with(APOLLO_CONNECTOR_PREFIX) && include_attributes.contains(key_name) }) .map(|kv| (kv.key.clone(), kv.value.clone())), diff --git a/apollo-router/src/plugins/telemetry/formatters/mod.rs b/apollo-router/src/plugins/telemetry/formatters/mod.rs index f99bc6a91c..17a4abb5c2 100644 --- a/apollo-router/src/plugins/telemetry/formatters/mod.rs +++ b/apollo-router/src/plugins/telemetry/formatters/mod.rs @@ -32,6 +32,9 @@ use crate::metrics::layer::METRIC_PREFIX_VALUE; use crate::plugins::telemetry::otel::OtelData; pub(crate) const APOLLO_PRIVATE_PREFIX: &str = "apollo_private."; +// FIXME: this is a temporary solution to avoid exposing hardcoded attributes in connector spans instead of using the custom telemetry features. +// The reason this is introduced right now is to directly avoid people relying on these attributes and then creating a breaking change in the future. +pub(crate) const APOLLO_CONNECTOR_PREFIX: &str = "apollo.connector."; // This list comes from Otel https://opentelemetry.io/docs/specs/semconv/attributes-registry/code/ and pub(crate) const EXCLUDED_ATTRIBUTES: [&str; 5] = [ "code.filepath", diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 28c453302e..e44762b412 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -83,7 +83,6 @@ pub(crate) use self::span_factory::SpanMode; use self::tracing::apollo_telemetry::APOLLO_PRIVATE_DURATION_NS; use self::tracing::apollo_telemetry::CLIENT_NAME_KEY; use self::tracing::apollo_telemetry::CLIENT_VERSION_KEY; -use super::connectors::tracing::CONNECT_SPAN_NAME; use crate::apollo_studio_interop::ExtendedReferenceStats; use crate::apollo_studio_interop::ReferencedEnums; use crate::context::CONTAINS_GRAPHQL_ERROR; @@ -109,6 +108,7 @@ use crate::plugins::telemetry::config_new::graphql::GraphQLInstruments; use crate::plugins::telemetry::config_new::instruments::SupergraphInstruments; use crate::plugins::telemetry::config_new::trace_id; use crate::plugins::telemetry::config_new::DatadogId; +use crate::plugins::telemetry::consts::CONNECT_SPAN_NAME; use crate::plugins::telemetry::consts::EXECUTION_SPAN_NAME; use crate::plugins::telemetry::consts::OTEL_NAME; use crate::plugins::telemetry::consts::OTEL_STATUS_CODE; diff --git a/apollo-router/src/plugins/telemetry/tracing/mod.rs b/apollo-router/src/plugins/telemetry/tracing/mod.rs index 0172f3e094..ce83b1b29d 100644 --- a/apollo-router/src/plugins/telemetry/tracing/mod.rs +++ b/apollo-router/src/plugins/telemetry/tracing/mod.rs @@ -16,6 +16,7 @@ use serde::Deserialize; use tower::BoxError; use super::config_new::spans::Spans; +use super::formatters::APOLLO_CONNECTOR_PREFIX; use super::formatters::APOLLO_PRIVATE_PREFIX; use crate::plugins::telemetry::config::TracingCommon; @@ -50,17 +51,19 @@ impl SpanProcessor for ApolloFilterSpanProcessor { } fn on_end(&self, span: SpanData) { - if span - .attributes - .iter() - .any(|(key, _)| key.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) - { + if span.attributes.iter().any(|(key, _)| { + key.as_str().starts_with(APOLLO_PRIVATE_PREFIX) + || key.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) + }) { let attributes_len = span.attributes.len(); let span = SpanData { attributes: span .attributes .into_iter() - .filter(|(k, _)| !k.as_str().starts_with(APOLLO_PRIVATE_PREFIX)) + .filter(|(k, _)| { + !k.as_str().starts_with(APOLLO_PRIVATE_PREFIX) + && !k.as_str().starts_with(APOLLO_CONNECTOR_PREFIX) + }) .fold( EvictedHashMap::new(attributes_len as u32, attributes_len), |mut m, (k, v)| { diff --git a/apollo-router/src/services/connector_service.rs b/apollo-router/src/services/connector_service.rs index d26226a39f..11be8c8f1d 100644 --- a/apollo-router/src/services/connector_service.rs +++ b/apollo-router/src/services/connector_service.rs @@ -31,8 +31,8 @@ use crate::plugins::connectors::make_requests::make_requests; use crate::plugins::connectors::plugin::ConnectorContext; use crate::plugins::connectors::request_limit::RequestLimits; use crate::plugins::connectors::tracing::CONNECTOR_TYPE_HTTP; -use crate::plugins::connectors::tracing::CONNECT_SPAN_NAME; use crate::plugins::subscription::SubscriptionConfig; +use crate::plugins::telemetry::consts::CONNECT_SPAN_NAME; use crate::services::ConnectRequest; use crate::services::ConnectResponse; use crate::spec::Schema; @@ -111,7 +111,6 @@ impl tower::Service for ConnectorService { let Some(http_client_factory) = http_client_factory else { return Err("no http client found".into()); }; - let fetch_time_offset = request.context.created_at.elapsed().as_nanos() as i64; let span = tracing::info_span!( CONNECT_SPAN_NAME, From f29ac6279b70fdf29720e93ceea06fe9ac246d23 Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:39:59 +0200 Subject: [PATCH 15/18] test wip Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- .../tests/fixtures/supergraph_connect.graphql | 73 +++++++++++++++++++ .../tests/integration/telemetry/jaeger.rs | 33 +++++++++ 2 files changed, 106 insertions(+) create mode 100644 apollo-router/tests/fixtures/supergraph_connect.graphql diff --git a/apollo-router/tests/fixtures/supergraph_connect.graphql b/apollo-router/tests/fixtures/supergraph_connect.graphql new file mode 100644 index 0000000000..a1ed2a27a1 --- /dev/null +++ b/apollo-router/tests/fixtures/supergraph_connect.graphql @@ -0,0 +1,73 @@ +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) + @link(url: "https://specs.apollo.dev/connect/v0.1", for: EXECUTION) + @join__directive(graphs: [POSTS], name: "link", args: {url: "https://specs.apollo.dev/connect/v0.1", import: ["@connect", "@source"]}) + @join__directive(graphs: [POSTS], name: "source", args: {name: "jsonPlaceholder", http: {baseURL: "https://jsonplaceholder.typicode.com/"}}) + @join__directive(graphs: [POSTS], name: "source", args: {name: "routerHealth", http: {baseURL: "http://localhost:4000/"}}) +{ + query: Query +} + +directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String, contextArguments: [join__ContextArgument!]) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! +} + +scalar join__DirectiveArguments + +scalar join__FieldSet + +scalar join__FieldValue + +enum join__Graph { + POSTS @join__graph(name: "posts", url: "http://localhost") +} + +scalar link__Import + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +type Post + @join__type(graph: POSTS) +{ + id: ID! + body: String + title: String + status: String +} + +type Query + @join__type(graph: POSTS) +{ + posts: [Post] @join__directive(graphs: [POSTS], name: "connect", args: {source: "jsonPlaceholder", http: {GET: "/posts"}, selection: "id\ntitle\nbody"}) + post(id: ID!): Post @join__directive(graphs: [POSTS], name: "connect", args: {source: "jsonPlaceholder", http: {GET: "/posts/{$args.id}"}, selection: "id\ntitle\nbody"}) @join__directive(graphs: [POSTS], name: "connect", args: {source: "routerHealth", http: {GET: "/health?_={$args.id}"}, selection: "id: $args.id\nstatus", entity: true}) +} diff --git a/apollo-router/tests/integration/telemetry/jaeger.rs b/apollo-router/tests/integration/telemetry/jaeger.rs index fcf59e4ef5..276c779e3f 100644 --- a/apollo-router/tests/integration/telemetry/jaeger.rs +++ b/apollo-router/tests/integration/telemetry/jaeger.rs @@ -1,6 +1,7 @@ extern crate core; use std::collections::HashSet; +use std::path::PathBuf; use std::time::Duration; use anyhow::anyhow; @@ -214,6 +215,38 @@ async fn test_default_operation() -> Result<(), BoxError> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn test_rest_connectors() -> Result<(), BoxError> { + let mut router = IntegrationTest::builder() + .telemetry(Telemetry::Jaeger) + .supergraph(PathBuf::from("tests/fixtures/supergraph_connect.graphql")) + .config(include_str!("fixtures/jaeger.connectors.router.yaml")) + .build() + .await; + + router.start().await; + router.assert_started().await; + let query = json!({"query":"query Posts { posts { id body status } }","variables":{}}); + + let (id, result) = router.execute_query(&query).await; + assert!(!result + .headers() + .get("apollo-custom-trace-id") + .unwrap() + .is_empty()); + // TODO use the same mechanism than for otlp tests with a builder and so one + validate_trace( + id, + &query, + Some("Posts"), + &["client", "router", "subgraph"], + false, + ) + .await?; + router.graceful_shutdown().await; + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn test_anonymous_operation() -> Result<(), BoxError> { let mut router = IntegrationTest::builder() From 6a8d4b28db94c948a655adf787a73ee2981c9d11 Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:20:51 +0100 Subject: [PATCH 16/18] add tests for span connector Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- apollo-router/tests/common.rs | 6 +- .../fixtures/jaeger.connectors.router.yaml | 55 +++++++++ .../tests/integration/telemetry/jaeger.rs | 111 +++++++++++++----- .../telemetry/instrumentation/spans.mdx | 8 +- 4 files changed, 147 insertions(+), 33 deletions(-) create mode 100644 apollo-router/tests/integration/telemetry/fixtures/jaeger.connectors.router.yaml diff --git a/apollo-router/tests/common.rs b/apollo-router/tests/common.rs index 4a9506a658..4e349d3bb8 100644 --- a/apollo-router/tests/common.rs +++ b/apollo-router/tests/common.rs @@ -297,7 +297,9 @@ impl IntegrationTest { let url = format!("http://{address}/"); // Add a default override for products, if not specified - subgraph_overrides.entry("products".into()).or_insert(url); + subgraph_overrides + .entry("products".into()) + .or_insert(url.clone()); // Insert the overrides into the config let config_str = merge_overrides(&config, &subgraph_overrides, None, &redis_namespace); @@ -387,7 +389,6 @@ impl IntegrationTest { .env("APOLLO_KEY", apollo_key) .env("APOLLO_GRAPH_REF", apollo_graph_ref); } - router .args(dbg!([ "--hr", @@ -413,7 +414,6 @@ impl IntegrationTest { let mut lines = reader.lines(); while let Ok(Some(line)) = lines.next_line().await { println!("{line}"); - // Extract the bind address from a log line that looks like this: GraphQL endpoint exposed at http://127.0.0.1:51087/ if let Some(captures) = bind_address_regex.captures(&line) { let address = captures.name("address").unwrap().as_str(); diff --git a/apollo-router/tests/integration/telemetry/fixtures/jaeger.connectors.router.yaml b/apollo-router/tests/integration/telemetry/fixtures/jaeger.connectors.router.yaml new file mode 100644 index 0000000000..6c0e4b4b84 --- /dev/null +++ b/apollo-router/tests/integration/telemetry/fixtures/jaeger.connectors.router.yaml @@ -0,0 +1,55 @@ +telemetry: + instrumentation: + spans: + connector: + attributes: + connector.source.name: true + connector.http.method: true + connector.http.response.status_code: + connector_http_response_status: code + connector.url.template: true + connector.http.response.header.content-type: + connector_http_response_header: "content-type" + exporters: + tracing: + experimental_response_trace_id: + enabled: true + header_name: apollo-custom-trace-id + propagation: + jaeger: true + common: + service_name: router + sampler: always_on + jaeger: + enabled: true + batch_processor: + scheduled_delay: 100ms + collector: + endpoint: http://127.0.0.1:14268/api/traces + logging: + experimental_when_header: + - name: apollo-router-log-request + value: test + headers: true # default: false + body: true # default: false + # log request for all requests coming from Iphones + - name: custom-header + match: ^foo.* + headers: true + +override_subgraph_url: + products: http://localhost:4005 +include_subgraph_errors: + all: true + +supergraph: + listen: "127.0.0.1:50642" +health_check: + enabled: true + listen: "127.0.0.1:50642" +preview_connectors: + subgraphs: + posts: # The name of the subgraph + sources: + routerHealth: # Refers to @source(name: "routerHealth") + override_url: http://127.0.0.1:50642 diff --git a/apollo-router/tests/integration/telemetry/jaeger.rs b/apollo-router/tests/integration/telemetry/jaeger.rs index 276c779e3f..bfeb3ccd1f 100644 --- a/apollo-router/tests/integration/telemetry/jaeger.rs +++ b/apollo-router/tests/integration/telemetry/jaeger.rs @@ -39,6 +39,7 @@ async fn test_reload() -> Result<(), BoxError> { Some("ExampleQuery"), &["client", "router", "subgraph"], false, + false, ) .await?; router.touch_config().await; @@ -72,6 +73,7 @@ async fn test_remote_root() -> Result<(), BoxError> { Some("ExampleQuery"), &["client", "router", "subgraph"], false, + false, ) .await?; @@ -103,6 +105,7 @@ async fn test_local_root() -> Result<(), BoxError> { Some("ExampleQuery"), &["router", "subgraph"], false, + false, ) .await?; @@ -151,6 +154,7 @@ async fn test_local_root_50_percent_sample() -> Result<(), BoxError> { Some("ExampleQuery"), &["router", "subgraph"], false, + false, ) .await .is_ok() @@ -209,6 +213,7 @@ async fn test_default_operation() -> Result<(), BoxError> { Some("ExampleQuery1"), &["client", "router", "subgraph"], false, + false, ) .await?; router.graceful_shutdown().await; @@ -217,33 +222,28 @@ async fn test_default_operation() -> Result<(), BoxError> { #[tokio::test(flavor = "multi_thread")] async fn test_rest_connectors() -> Result<(), BoxError> { - let mut router = IntegrationTest::builder() - .telemetry(Telemetry::Jaeger) - .supergraph(PathBuf::from("tests/fixtures/supergraph_connect.graphql")) - .config(include_str!("fixtures/jaeger.connectors.router.yaml")) - .build() - .await; + if std::env::var("TEST_APOLLO_KEY").is_ok() && std::env::var("TEST_APOLLO_GRAPH_REF").is_ok() { + let mut router = IntegrationTest::builder() + .telemetry(Telemetry::Jaeger) + .supergraph(PathBuf::from("tests/fixtures/supergraph_connect.graphql")) + .config(include_str!("fixtures/jaeger.connectors.router.yaml")) + .build() + .await; - router.start().await; - router.assert_started().await; - let query = json!({"query":"query Posts { posts { id body status } }","variables":{}}); + router.start().await; + router.assert_started().await; + let query = json!({"query":"query ExampleQuery { posts { id } }","variables":{}, "operationName": "ExampleQuery"}); - let (id, result) = router.execute_query(&query).await; - assert!(!result - .headers() - .get("apollo-custom-trace-id") - .unwrap() - .is_empty()); - // TODO use the same mechanism than for otlp tests with a builder and so one - validate_trace( - id, - &query, - Some("Posts"), - &["client", "router", "subgraph"], - false, - ) - .await?; - router.graceful_shutdown().await; + let (id, result) = router.execute_query(&query).await; + assert!(!result + .headers() + .get("apollo-custom-trace-id") + .unwrap() + .is_empty()); + + validate_trace(id, &query, Some("ExampleQuery"), &["router"], false, true).await?; + router.graceful_shutdown().await; + } Ok(()) } @@ -266,7 +266,15 @@ async fn test_anonymous_operation() -> Result<(), BoxError> { .get("apollo-custom-trace-id") .unwrap() .is_empty()); - validate_trace(id, &query, None, &["client", "router", "subgraph"], false).await?; + validate_trace( + id, + &query, + None, + &["client", "router", "subgraph"], + false, + false, + ) + .await?; router.graceful_shutdown().await; Ok(()) } @@ -295,6 +303,7 @@ async fn test_selected_operation() -> Result<(), BoxError> { Some("ExampleQuery2"), &["client", "router", "subgraph"], false, + false, ) .await?; router.graceful_shutdown().await; @@ -321,6 +330,7 @@ async fn test_span_customization() -> Result<(), BoxError> { Some("ExampleQuery"), &["client", "router", "subgraph"], true, + false, ) .await?; router.graceful_shutdown().await; @@ -357,6 +367,7 @@ async fn test_decimal_trace_id() -> Result<(), BoxError> { Some("ExampleQuery1"), &["client", "router", "subgraph"], false, + false, ) .await?; router.graceful_shutdown().await; @@ -369,6 +380,7 @@ async fn validate_trace( operation_name: Option<&str>, services: &[&'static str], custom_span_instrumentation: bool, + check_connect_span: bool, ) -> Result<(), BoxError> { let params = url::form_urlencoded::Serializer::new(String::new()) .append_pair("service", services.first().expect("expected root service")) @@ -383,6 +395,7 @@ async fn validate_trace( operation_name, services, custom_span_instrumentation, + check_connect_span, ) .await .is_ok() @@ -397,6 +410,7 @@ async fn validate_trace( operation_name, services, custom_span_instrumentation, + check_connect_span, ) .await?; Ok(()) @@ -408,6 +422,7 @@ async fn find_valid_trace( operation_name: Option<&str>, services: &[&'static str], custom_span_instrumentation: bool, + check_connect_span: bool, ) -> Result<(), BoxError> { // A valid trace has: // * All three services @@ -425,7 +440,7 @@ async fn find_valid_trace( verify_trace_participants(&trace, services)?; // Verify that we got the expected span operation names - verify_spans_present(&trace, operation_name, services)?; + verify_spans_present(&trace, operation_name, services, check_connect_span)?; // Verify that all spans have a path to the root 'client_request' span verify_span_parenting(&trace, services)?; @@ -439,6 +454,10 @@ async fn find_valid_trace( // Verify that router span fields are present verify_router_span_fields(&trace, custom_span_instrumentation)?; + if check_connect_span { + verify_connect_span_fields(&trace)?; + } + Ok(()) } @@ -583,6 +602,37 @@ fn verify_supergraph_span_fields( Ok(()) } +fn verify_connect_span_fields(trace: &Value) -> Result<(), BoxError> { + // We can't actually assert the values on a span. Only that a field has been set. + let connect_span = trace.select_path("$..spans[?(@.operationName == 'connect')]")?[0]; + assert_eq!( + connect_span + .select_path("$.tags[?(@.key == 'connector.http.method')].value")? + .first(), + Some(&&Value::String("GET".to_string())) + ); + assert_eq!( + connect_span + .select_path("$.tags[?(@.key == 'connector.source.name')].value")? + .first(), + Some(&&Value::String("jsonPlaceholder".to_string())) + ); + assert_eq!( + connect_span + .select_path("$.tags[?(@.key == 'connector.url.template')].value")? + .first(), + Some(&&Value::String("/posts".to_string())) + ); + assert_eq!( + connect_span + .select_path("$.tags[?(@.key == 'subgraph.name')].value")? + .first(), + None + ); + + Ok(()) +} + fn verify_trace_participants(trace: &Value, services: &[&'static str]) -> Result<(), BoxError> { let actual_services: HashSet = trace .select_path("$..serviceName")? @@ -607,6 +657,7 @@ fn verify_spans_present( trace: &Value, operation_name: Option<&str>, services: &[&'static str], + check_connect_span: bool, ) -> Result<(), BoxError> { let operation_names: HashSet = trace .select_path("$..operationName")? @@ -616,7 +667,6 @@ fn verify_spans_present( let mut expected_operation_names: HashSet = HashSet::from( [ "execution", - "subgraph server", operation_name .map(|name| format!("query {name}")) .unwrap_or("query".to_string()) @@ -625,10 +675,13 @@ fn verify_spans_present( "fetch", //"parse_query", Parse query will only happen once //"query_planning", query planning will only happen once - "subgraph", ] .map(|s| s.into()), ); + if !check_connect_span { + expected_operation_names.insert("subgraph server".into()); + expected_operation_names.insert("subgraph".into()); + } if services.contains(&"client") { expected_operation_names.insert("client_request".into()); } diff --git a/docs/source/configuration/telemetry/instrumentation/spans.mdx b/docs/source/configuration/telemetry/instrumentation/spans.mdx index 73dddd58e0..696aecf626 100644 --- a/docs/source/configuration/telemetry/instrumentation/spans.mdx +++ b/docs/source/configuration/telemetry/instrumentation/spans.mdx @@ -14,7 +14,7 @@ A **span** captures contextual information about requests and responses as they' -The `router`, `supergraph` and `subgraph` sections are used to define custom span configuration for each service: +The `router`, `supergraph`, `subgraph` and `connector` sections are used to define custom span configuration for each service: ```yaml title="router.yaml" telemetry: @@ -29,6 +29,9 @@ telemetry: subgraph: # highlight-line attributes: {} # ... + connector: # highlight-line + attributes: {} + # ... ``` ### `attributes` @@ -238,6 +241,9 @@ telemetry: subgraph: attributes: {} # ... + connector: + attributes: {} + # ... ``` ## Spans configuration reference From da99fe2e1b178a0996c5b0792b4016c7a65b0bd1 Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:09:25 +0100 Subject: [PATCH 17/18] fix test connector Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- apollo-router/tests/integration/telemetry/jaeger.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apollo-router/tests/integration/telemetry/jaeger.rs b/apollo-router/tests/integration/telemetry/jaeger.rs index bfeb3ccd1f..eb3839b036 100644 --- a/apollo-router/tests/integration/telemetry/jaeger.rs +++ b/apollo-router/tests/integration/telemetry/jaeger.rs @@ -241,7 +241,15 @@ async fn test_rest_connectors() -> Result<(), BoxError> { .unwrap() .is_empty()); - validate_trace(id, &query, Some("ExampleQuery"), &["router"], false, true).await?; + validate_trace( + id, + &query, + Some("ExampleQuery"), + &["client", "router"], + false, + true, + ) + .await?; router.graceful_shutdown().await; } Ok(()) From 4e082b20b2698559a26d3449becaafa796144434 Mon Sep 17 00:00:00 2001 From: Benjamin <5719034+bnjjj@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:32:04 +0100 Subject: [PATCH 18/18] fix test Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com> --- ...nfiguration__tests__schema_generation.snap | 59 +++++++++++++++++++ .../telemetry/instrumentation/selectors.mdx | 3 - 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index f273ffe378..c126b050f0 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -2121,6 +2121,16 @@ expression: "&schema" } ] }, + "ConnectorSpans": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional", + "description": "#/definitions/extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional" + } + }, + "type": "object" + }, "ConnectorValue": { "anyOf": [ { @@ -5937,6 +5947,10 @@ expression: "&schema" "Spans": { "additionalProperties": false, "properties": { + "connector": { + "$ref": "#/definitions/ConnectorSpans", + "description": "#/definitions/ConnectorSpans" + }, "default_attribute_requirement_level": { "$ref": "#/definitions/DefaultAttributeRequirementLevel", "description": "#/definitions/DefaultAttributeRequirementLevel" @@ -7940,6 +7954,22 @@ expression: "&schema" ], "type": "string" }, + "conditional_attribute_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector": { + "anyOf": [ + { + "$ref": "#/definitions/ConnectorSelector", + "description": "#/definitions/ConnectorSelector" + }, + { + "properties": { + "condition": { + "$ref": "#/definitions/Condition_for_ConnectorSelector", + "description": "#/definitions/Condition_for_ConnectorSelector" + } + } + } + ] + }, "conditional_attribute_apollo_router::plugins::telemetry::config_new::selectors::RouterSelector": { "anyOf": [ { @@ -8415,6 +8445,35 @@ expression: "&schema" }, "type": "object" }, + "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::conditional::Conditional": { + "additionalProperties": { + "$ref": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector", + "description": "#/definitions/conditional_attribute_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector" + }, + "properties": { + "connector.http.method": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.source.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "connector.url.template": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + }, + "subgraph.name": { + "$ref": "#/definitions/StandardAttribute", + "description": "#/definitions/StandardAttribute", + "nullable": true + } + }, + "type": "object" + }, "extendable_attribute_apollo_router::plugins::telemetry::config_new::connector::attributes::ConnectorAttributes_apollo_router::plugins::telemetry::config_new::connector::selectors::ConnectorSelector": { "additionalProperties": { "$ref": "#/definitions/ConnectorSelector", diff --git a/docs/source/configuration/telemetry/instrumentation/selectors.mdx b/docs/source/configuration/telemetry/instrumentation/selectors.mdx index 6002dbc271..d63e9003a3 100644 --- a/docs/source/configuration/telemetry/instrumentation/selectors.mdx +++ b/docs/source/configuration/telemetry/instrumentation/selectors.mdx @@ -114,9 +114,6 @@ Apollo Connectors for REST APIs make HTTP calls to the upstream HTTP API. These | `static` | No | | A static string value | | `error` | No | `reason` | A string value containing error reason when it's a critical error | - -The above Apollo Connectors selectors are not currently supported for Spans, only for Instruments, Events, and Conditions. - ### GraphQL