diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index 87985e71f5..19e6c4f296 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -25,13 +25,21 @@ where Q: Serialize + std::fmt::Debug + 'a, S: TryFrom + Serialize, E: Serialize + error_stack::Context + actix_web::ResponseError + Clone, + U: auth::AuthInfo, error_stack::Report: services::EmbedError, errors::ApiErrorResponse: ErrorSwitch, T: std::fmt::Debug, A: AppStateInfo, { - let resp: common_utils::errors::CustomResult<_, E> = - api::server_wrap_util(state, request, payload, func, api_authentication).await; + let resp: common_utils::errors::CustomResult<_, E> = api::server_wrap_util( + &router_env::Flow::CompatibilityLayerRequest, + state, + request, + payload, + func, + api_authentication, + ) + .await; match resp { Ok(api::ApplicationResponse::Json(router_resp)) => { let pg_resp = S::try_from(router_resp); diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 5cb409a6da..5f945964fe 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -12,6 +12,7 @@ use crate::{ payments::helpers, }, db::StorageInterface, + routes::metrics, services::api as service_api, types::{ self, api, @@ -378,10 +379,11 @@ pub async fn create_payment_connector( } None => None, }; + let merchant_connector_account = storage::MerchantConnectorAccountNew { merchant_id: Some(merchant_id.to_string()), connector_type: Some(req.connector_type.foreign_into()), - connector_name: Some(req.connector_name), + connector_name: Some(req.connector_name.to_owned()), merchant_connector_id: utils::generate_id(consts::ID_LENGTH, "mca"), connector_account_details: req.connector_account_details, payment_methods_enabled, @@ -406,6 +408,15 @@ pub async fn create_payment_connector( }, )?; + metrics::MCA_CREATE.add( + &metrics::CONTEXT, + 1, + &[ + metrics::request::add_attributes("connector", req.connector_name.to_string()), + metrics::request::add_attributes("merchant", merchant_id.to_string()), + ], + ); + let mca_response = ForeignTryFrom::foreign_try_from(mca)?; Ok(service_api::ApplicationResponse::Json(mca_response)) diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs index 2d4066779a..51805b95b4 100644 --- a/crates/router/src/core/api_keys.rs +++ b/crates/router/src/core/api_keys.rs @@ -132,7 +132,7 @@ pub async fn create_api_key( let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH); let api_key = storage::ApiKeyNew { key_id: PlaintextApiKey::new_key_id(), - merchant_id, + merchant_id: merchant_id.to_owned(), name: api_key.name, description: api_key.description, hashed_api_key: plaintext_api_key.keyed_hash(hash_key.peek()).into(), @@ -148,7 +148,11 @@ pub async fn create_api_key( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to insert new API key")?; - metrics::API_KEY_CREATED.add(&metrics::CONTEXT, 1, &[]); + metrics::API_KEY_CREATED.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes("merchant", merchant_id)], + ); Ok(ApplicationResponse::Json( (api_key, plaintext_api_key).foreign_into(), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 878df61a31..7a0783d218 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -13,7 +13,7 @@ use crate::{ errors::{self, RouterResponse, RouterResult}, payments::{self, helpers}, }, - routes::AppState, + routes::{metrics, AppState}, services::{self, RedirectForm}, types::{ self, api, @@ -165,7 +165,7 @@ where payment_data.address, server, payment_data.connector_response.authentication_data, - operation, + &operation, payment_data.ephemeral_key, ) } @@ -256,7 +256,7 @@ pub fn payments_to_payments_response( address: PaymentAddress, server: &Server, redirection_data: Option, - operation: Op, + operation: &Op, ephemeral_key_option: Option, ) -> RouterResponse where @@ -287,8 +287,19 @@ where .collect(), ) }; + let merchant_id = payment_attempt.merchant_id.to_owned(); + let payment_method_type = payment_attempt + .payment_method_type + .as_ref() + .map(ToString::to_string) + .unwrap_or("".to_owned()); + let payment_method = payment_attempt + .payment_method + .as_ref() + .map(ToString::to_string) + .unwrap_or("".to_owned()); - Ok(match payment_request { + let output = Ok(match payment_request { Some(_request) => { if payments::is_start_pay(&operation) && redirection_data.is_some() { let redirection_data = redirection_data.get_required_value("redirection_data")?; @@ -479,7 +490,20 @@ where metadata: payment_intent.metadata, ..Default::default() }), - }) + }); + + metrics::PAYMENT_OPS_COUNT.add( + &metrics::CONTEXT, + 1, + &[ + metrics::request::add_attributes("operation", format!("{:?}", operation)), + metrics::request::add_attributes("merchant", merchant_id), + metrics::request::add_attributes("payment_method_type", payment_method_type), + metrics::request::add_attributes("payment_method", payment_method), + ], + ); + + output } impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 499e579235..3cfd000f0c 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -10,10 +10,12 @@ counter_metric!(AWS_KMS_FAILURES, GLOBAL_METER); // No. of AWS KMS API failures // API Level Metrics counter_metric!(REQUESTS_RECEIVED, GLOBAL_METER); -counter_metric!(FAILED_REQUEST, GLOBAL_METER); +counter_metric!(REQUEST_STATUS, GLOBAL_METER); histogram_metric!(REQUEST_TIME, GLOBAL_METER); // Operation Level Metrics +counter_metric!(PAYMENT_OPS_COUNT, GLOBAL_METER); + counter_metric!(PAYMENT_COUNT, GLOBAL_METER); counter_metric!(SUCCESSFUL_PAYMENT, GLOBAL_METER); @@ -42,6 +44,8 @@ counter_metric!(CUSTOMER_REDACTED, GLOBAL_METER); counter_metric!(API_KEY_CREATED, GLOBAL_METER); counter_metric!(API_KEY_REVOKED, GLOBAL_METER); +counter_metric!(MCA_CREATE, GLOBAL_METER); + // Flow Specific Metrics counter_metric!(ACCESS_TOKEN_CREATION, GLOBAL_METER); diff --git a/crates/router/src/routes/metrics/request.rs b/crates/router/src/routes/metrics/request.rs index 92f6d82378..68734bfd66 100644 --- a/crates/router/src/routes/metrics/request.rs +++ b/crates/router/src/routes/metrics/request.rs @@ -1,8 +1,9 @@ use super::utils as metric_utils; +use crate::services::ApplicationResponse; pub async fn record_request_time_metric( future: F, - flow: impl router_env::types::FlowMetric, + flow: &impl router_env::types::FlowMetric, ) -> R where F: futures::Future, @@ -37,3 +38,26 @@ pub fn add_attributes>( ) -> router_env::opentelemetry::KeyValue { router_env::opentelemetry::KeyValue::new(key, value) } + +pub fn status_code_metrics(status_code: i64, flow: String, merchant_id: String) { + super::REQUEST_STATUS.add( + &super::CONTEXT, + 1, + &[ + add_attributes("status_code", status_code), + add_attributes("flow", flow), + add_attributes("merchant_id", merchant_id), + ], + ) +} + +pub fn track_response_status_code(response: &ApplicationResponse) -> i64 { + match response { + ApplicationResponse::Json(_) + | ApplicationResponse::StatusOk + | ApplicationResponse::TextPlain(_) + | ApplicationResponse::Form(_) + | ApplicationResponse::FileData(_) => 200, + ApplicationResponse::JsonForRedirection(_) => 302, + } +} diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 0b25721f5e..8f3b372d38 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -9,7 +9,7 @@ use std::{ time::{Duration, Instant}, }; -use actix_web::{body, HttpRequest, HttpResponse, Responder}; +use actix_web::{body, HttpRequest, HttpResponse, Responder, ResponseError}; use common_utils::errors::ReportSwitchExt; use error_stack::{report, IntoReport, Report, ResultExt}; use masking::{ExposeOptionInterface, PeekInterface}; @@ -528,6 +528,7 @@ pub enum AuthFlow { #[instrument(skip(request, payload, state, func, api_auth))] pub async fn server_wrap_util<'a, 'b, A, U, T, Q, F, Fut, E, OErr>( + flow: &'a impl router_env::types::FlowMetric, state: &'b A, request: &'a HttpRequest, payload: T, @@ -536,18 +537,36 @@ pub async fn server_wrap_util<'a, 'b, A, U, T, Q, F, Fut, E, OErr>( ) -> CustomResult, OErr> where F: Fn(&'b A, U, T) -> Fut, + 'b: 'a, Fut: Future, E>>, Q: Serialize + Debug + 'a, T: Debug, A: AppStateInfo, + U: auth::AuthInfo, CustomResult, E>: ReportSwitchExt, OErr>, CustomResult: ReportSwitchExt, + OErr: ResponseError + Sync + Send + 'static, { let auth_out = api_auth .authenticate_and_fetch(request.headers(), state) .await .switch()?; - func(state, auth_out, payload).await.switch() + let metric_merchant_id = auth_out.get_merchant_id().unwrap_or("").to_string(); + + let output = func(state, auth_out, payload).await.switch(); + + let status_code = match output.as_ref() { + Ok(res) => metrics::request::track_response_status_code(res), + Err(err) => err.current_context().status_code().as_u16().into(), + }; + + metrics::request::status_code_metrics( + status_code, + flow.to_string(), + metric_merchant_id.to_string(), + ); + + output } #[instrument( @@ -567,6 +586,7 @@ where Fut: Future, E>>, Q: Serialize + Debug + 'a, T: Debug, + U: auth::AuthInfo, A: AppStateInfo, CustomResult, E>: ReportSwitchExt, api_models::errors::types::ApiErrorResponse>, @@ -579,8 +599,8 @@ where let start_instant = Instant::now(); logger::info!(tag = ?Tag::BeginRequest); let res = match metrics::request::record_request_time_metric( - server_wrap_util(state, request, payload, func, api_auth), - flow, + server_wrap_util(&flow, state, request, payload, func, api_auth), + &flow, ) .await { @@ -636,7 +656,7 @@ where pub fn log_and_return_error_response(error: Report) -> HttpResponse where - T: actix_web::ResponseError + error_stack::Context + Clone, + T: error_stack::Context + Clone + ResponseError, Report: EmbedError, { logger::error!(?error); diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 5fa4f3b53f..ca66993985 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -20,10 +20,28 @@ use crate::{ types::storage, utils::OptionExt, }; + +pub trait AuthInfo { + fn get_merchant_id(&self) -> Option<&str>; +} + +impl AuthInfo for () { + fn get_merchant_id(&self) -> Option<&str> { + None + } +} + +impl AuthInfo for storage::MerchantAccount { + fn get_merchant_id(&self) -> Option<&str> { + Some(&self.merchant_id) + } +} + #[async_trait] pub trait AuthenticateAndFetch where A: AppStateInfo, + T: AuthInfo, { async fn authenticate_and_fetch( &self, @@ -303,7 +321,7 @@ impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest { } } -pub fn jwt_auth_or<'a, T, A: AppStateInfo>( +pub fn jwt_auth_or<'a, T: AuthInfo, A: AppStateInfo>( default_auth: &'a dyn AuthenticateAndFetch, headers: &HeaderMap, ) -> Box<&'a dyn AuthenticateAndFetch> diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 0266373beb..e47b5d1c5f 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -184,12 +184,14 @@ pub enum Flow { AttachDisputeEvidence, /// Retrieve Dispute Evidence flow RetrieveDisputeEvidence, + /// Request to compatibility layer + CompatibilityLayerRequest, } /// /// Trait for providing generic behaviour to flow metric /// -pub trait FlowMetric: ToString + std::fmt::Debug {} +pub trait FlowMetric: ToString + std::fmt::Debug + Clone {} impl FlowMetric for Flow {} /// Category of log event.