mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(metrics): add response metrics (#1263)
This commit is contained in:
		| @ -25,13 +25,21 @@ where | |||||||
|     Q: Serialize + std::fmt::Debug + 'a, |     Q: Serialize + std::fmt::Debug + 'a, | ||||||
|     S: TryFrom<Q> + Serialize, |     S: TryFrom<Q> + Serialize, | ||||||
|     E: Serialize + error_stack::Context + actix_web::ResponseError + Clone, |     E: Serialize + error_stack::Context + actix_web::ResponseError + Clone, | ||||||
|  |     U: auth::AuthInfo, | ||||||
|     error_stack::Report<E>: services::EmbedError, |     error_stack::Report<E>: services::EmbedError, | ||||||
|     errors::ApiErrorResponse: ErrorSwitch<E>, |     errors::ApiErrorResponse: ErrorSwitch<E>, | ||||||
|     T: std::fmt::Debug, |     T: std::fmt::Debug, | ||||||
|     A: AppStateInfo, |     A: AppStateInfo, | ||||||
| { | { | ||||||
|     let resp: common_utils::errors::CustomResult<_, E> = |     let resp: common_utils::errors::CustomResult<_, E> = api::server_wrap_util( | ||||||
|         api::server_wrap_util(state, request, payload, func, api_authentication).await; |         &router_env::Flow::CompatibilityLayerRequest, | ||||||
|  |         state, | ||||||
|  |         request, | ||||||
|  |         payload, | ||||||
|  |         func, | ||||||
|  |         api_authentication, | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
|     match resp { |     match resp { | ||||||
|         Ok(api::ApplicationResponse::Json(router_resp)) => { |         Ok(api::ApplicationResponse::Json(router_resp)) => { | ||||||
|             let pg_resp = S::try_from(router_resp); |             let pg_resp = S::try_from(router_resp); | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ use crate::{ | |||||||
|         payments::helpers, |         payments::helpers, | ||||||
|     }, |     }, | ||||||
|     db::StorageInterface, |     db::StorageInterface, | ||||||
|  |     routes::metrics, | ||||||
|     services::api as service_api, |     services::api as service_api, | ||||||
|     types::{ |     types::{ | ||||||
|         self, api, |         self, api, | ||||||
| @ -378,10 +379,11 @@ pub async fn create_payment_connector( | |||||||
|         } |         } | ||||||
|         None => None, |         None => None, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let merchant_connector_account = storage::MerchantConnectorAccountNew { |     let merchant_connector_account = storage::MerchantConnectorAccountNew { | ||||||
|         merchant_id: Some(merchant_id.to_string()), |         merchant_id: Some(merchant_id.to_string()), | ||||||
|         connector_type: Some(req.connector_type.foreign_into()), |         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"), |         merchant_connector_id: utils::generate_id(consts::ID_LENGTH, "mca"), | ||||||
|         connector_account_details: req.connector_account_details, |         connector_account_details: req.connector_account_details, | ||||||
|         payment_methods_enabled, |         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)?; |     let mca_response = ForeignTryFrom::foreign_try_from(mca)?; | ||||||
|  |  | ||||||
|     Ok(service_api::ApplicationResponse::Json(mca_response)) |     Ok(service_api::ApplicationResponse::Json(mca_response)) | ||||||
|  | |||||||
| @ -132,7 +132,7 @@ pub async fn create_api_key( | |||||||
|     let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH); |     let plaintext_api_key = PlaintextApiKey::new(consts::API_KEY_LENGTH); | ||||||
|     let api_key = storage::ApiKeyNew { |     let api_key = storage::ApiKeyNew { | ||||||
|         key_id: PlaintextApiKey::new_key_id(), |         key_id: PlaintextApiKey::new_key_id(), | ||||||
|         merchant_id, |         merchant_id: merchant_id.to_owned(), | ||||||
|         name: api_key.name, |         name: api_key.name, | ||||||
|         description: api_key.description, |         description: api_key.description, | ||||||
|         hashed_api_key: plaintext_api_key.keyed_hash(hash_key.peek()).into(), |         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) |         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|         .attach_printable("Failed to insert new API key")?; |         .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( |     Ok(ApplicationResponse::Json( | ||||||
|         (api_key, plaintext_api_key).foreign_into(), |         (api_key, plaintext_api_key).foreign_into(), | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ use crate::{ | |||||||
|         errors::{self, RouterResponse, RouterResult}, |         errors::{self, RouterResponse, RouterResult}, | ||||||
|         payments::{self, helpers}, |         payments::{self, helpers}, | ||||||
|     }, |     }, | ||||||
|     routes::AppState, |     routes::{metrics, AppState}, | ||||||
|     services::{self, RedirectForm}, |     services::{self, RedirectForm}, | ||||||
|     types::{ |     types::{ | ||||||
|         self, api, |         self, api, | ||||||
| @ -165,7 +165,7 @@ where | |||||||
|             payment_data.address, |             payment_data.address, | ||||||
|             server, |             server, | ||||||
|             payment_data.connector_response.authentication_data, |             payment_data.connector_response.authentication_data, | ||||||
|             operation, |             &operation, | ||||||
|             payment_data.ephemeral_key, |             payment_data.ephemeral_key, | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| @ -256,7 +256,7 @@ pub fn payments_to_payments_response<R, Op>( | |||||||
|     address: PaymentAddress, |     address: PaymentAddress, | ||||||
|     server: &Server, |     server: &Server, | ||||||
|     redirection_data: Option<serde_json::Value>, |     redirection_data: Option<serde_json::Value>, | ||||||
|     operation: Op, |     operation: &Op, | ||||||
|     ephemeral_key_option: Option<ephemeral_key::EphemeralKey>, |     ephemeral_key_option: Option<ephemeral_key::EphemeralKey>, | ||||||
| ) -> RouterResponse<api::PaymentsResponse> | ) -> RouterResponse<api::PaymentsResponse> | ||||||
| where | where | ||||||
| @ -287,8 +287,19 @@ where | |||||||
|                 .collect(), |                 .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) => { |         Some(_request) => { | ||||||
|             if payments::is_start_pay(&operation) && redirection_data.is_some() { |             if payments::is_start_pay(&operation) && redirection_data.is_some() { | ||||||
|                 let redirection_data = redirection_data.get_required_value("redirection_data")?; |                 let redirection_data = redirection_data.get_required_value("redirection_data")?; | ||||||
| @ -479,7 +490,20 @@ where | |||||||
|             metadata: payment_intent.metadata, |             metadata: payment_intent.metadata, | ||||||
|             ..Default::default() |             ..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 { | impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { | ||||||
|  | |||||||
| @ -10,10 +10,12 @@ counter_metric!(AWS_KMS_FAILURES, GLOBAL_METER); // No. of AWS KMS API failures | |||||||
|  |  | ||||||
| // API Level Metrics | // API Level Metrics | ||||||
| counter_metric!(REQUESTS_RECEIVED, GLOBAL_METER); | counter_metric!(REQUESTS_RECEIVED, GLOBAL_METER); | ||||||
| counter_metric!(FAILED_REQUEST, GLOBAL_METER); | counter_metric!(REQUEST_STATUS, GLOBAL_METER); | ||||||
| histogram_metric!(REQUEST_TIME, GLOBAL_METER); | histogram_metric!(REQUEST_TIME, GLOBAL_METER); | ||||||
|  |  | ||||||
| // Operation Level Metrics | // Operation Level Metrics | ||||||
|  | counter_metric!(PAYMENT_OPS_COUNT, GLOBAL_METER); | ||||||
|  |  | ||||||
| counter_metric!(PAYMENT_COUNT, GLOBAL_METER); | counter_metric!(PAYMENT_COUNT, GLOBAL_METER); | ||||||
| counter_metric!(SUCCESSFUL_PAYMENT, 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_CREATED, GLOBAL_METER); | ||||||
| counter_metric!(API_KEY_REVOKED, GLOBAL_METER); | counter_metric!(API_KEY_REVOKED, GLOBAL_METER); | ||||||
|  |  | ||||||
|  | counter_metric!(MCA_CREATE, GLOBAL_METER); | ||||||
|  |  | ||||||
| // Flow Specific Metrics | // Flow Specific Metrics | ||||||
|  |  | ||||||
| counter_metric!(ACCESS_TOKEN_CREATION, GLOBAL_METER); | counter_metric!(ACCESS_TOKEN_CREATION, GLOBAL_METER); | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| use super::utils as metric_utils; | use super::utils as metric_utils; | ||||||
|  | use crate::services::ApplicationResponse; | ||||||
|  |  | ||||||
| pub async fn record_request_time_metric<F, R>( | pub async fn record_request_time_metric<F, R>( | ||||||
|     future: F, |     future: F, | ||||||
|     flow: impl router_env::types::FlowMetric, |     flow: &impl router_env::types::FlowMetric, | ||||||
| ) -> R | ) -> R | ||||||
| where | where | ||||||
|     F: futures::Future<Output = R>, |     F: futures::Future<Output = R>, | ||||||
| @ -37,3 +38,26 @@ pub fn add_attributes<T: Into<router_env::opentelemetry::Value>>( | |||||||
| ) -> router_env::opentelemetry::KeyValue { | ) -> router_env::opentelemetry::KeyValue { | ||||||
|     router_env::opentelemetry::KeyValue::new(key, value) |     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<Q>(response: &ApplicationResponse<Q>) -> i64 { | ||||||
|  |     match response { | ||||||
|  |         ApplicationResponse::Json(_) | ||||||
|  |         | ApplicationResponse::StatusOk | ||||||
|  |         | ApplicationResponse::TextPlain(_) | ||||||
|  |         | ApplicationResponse::Form(_) | ||||||
|  |         | ApplicationResponse::FileData(_) => 200, | ||||||
|  |         ApplicationResponse::JsonForRedirection(_) => 302, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ use std::{ | |||||||
|     time::{Duration, Instant}, |     time::{Duration, Instant}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| use actix_web::{body, HttpRequest, HttpResponse, Responder}; | use actix_web::{body, HttpRequest, HttpResponse, Responder, ResponseError}; | ||||||
| use common_utils::errors::ReportSwitchExt; | use common_utils::errors::ReportSwitchExt; | ||||||
| use error_stack::{report, IntoReport, Report, ResultExt}; | use error_stack::{report, IntoReport, Report, ResultExt}; | ||||||
| use masking::{ExposeOptionInterface, PeekInterface}; | use masking::{ExposeOptionInterface, PeekInterface}; | ||||||
| @ -528,6 +528,7 @@ pub enum AuthFlow { | |||||||
|  |  | ||||||
| #[instrument(skip(request, payload, state, func, api_auth))] | #[instrument(skip(request, payload, state, func, api_auth))] | ||||||
| pub async fn server_wrap_util<'a, 'b, A, U, T, Q, F, Fut, E, OErr>( | 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, |     state: &'b A, | ||||||
|     request: &'a HttpRequest, |     request: &'a HttpRequest, | ||||||
|     payload: T, |     payload: T, | ||||||
| @ -536,18 +537,36 @@ pub async fn server_wrap_util<'a, 'b, A, U, T, Q, F, Fut, E, OErr>( | |||||||
| ) -> CustomResult<ApplicationResponse<Q>, OErr> | ) -> CustomResult<ApplicationResponse<Q>, OErr> | ||||||
| where | where | ||||||
|     F: Fn(&'b A, U, T) -> Fut, |     F: Fn(&'b A, U, T) -> Fut, | ||||||
|  |     'b: 'a, | ||||||
|     Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>, |     Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>, | ||||||
|     Q: Serialize + Debug + 'a, |     Q: Serialize + Debug + 'a, | ||||||
|     T: Debug, |     T: Debug, | ||||||
|     A: AppStateInfo, |     A: AppStateInfo, | ||||||
|  |     U: auth::AuthInfo, | ||||||
|     CustomResult<ApplicationResponse<Q>, E>: ReportSwitchExt<ApplicationResponse<Q>, OErr>, |     CustomResult<ApplicationResponse<Q>, E>: ReportSwitchExt<ApplicationResponse<Q>, OErr>, | ||||||
|     CustomResult<U, errors::ApiErrorResponse>: ReportSwitchExt<U, OErr>, |     CustomResult<U, errors::ApiErrorResponse>: ReportSwitchExt<U, OErr>, | ||||||
|  |     OErr: ResponseError + Sync + Send + 'static, | ||||||
| { | { | ||||||
|     let auth_out = api_auth |     let auth_out = api_auth | ||||||
|         .authenticate_and_fetch(request.headers(), state) |         .authenticate_and_fetch(request.headers(), state) | ||||||
|         .await |         .await | ||||||
|         .switch()?; |         .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( | #[instrument( | ||||||
| @ -567,6 +586,7 @@ where | |||||||
|     Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>, |     Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>, | ||||||
|     Q: Serialize + Debug + 'a, |     Q: Serialize + Debug + 'a, | ||||||
|     T: Debug, |     T: Debug, | ||||||
|  |     U: auth::AuthInfo, | ||||||
|     A: AppStateInfo, |     A: AppStateInfo, | ||||||
|     CustomResult<ApplicationResponse<Q>, E>: |     CustomResult<ApplicationResponse<Q>, E>: | ||||||
|         ReportSwitchExt<ApplicationResponse<Q>, api_models::errors::types::ApiErrorResponse>, |         ReportSwitchExt<ApplicationResponse<Q>, api_models::errors::types::ApiErrorResponse>, | ||||||
| @ -579,8 +599,8 @@ where | |||||||
|     let start_instant = Instant::now(); |     let start_instant = Instant::now(); | ||||||
|     logger::info!(tag = ?Tag::BeginRequest); |     logger::info!(tag = ?Tag::BeginRequest); | ||||||
|     let res = match metrics::request::record_request_time_metric( |     let res = match metrics::request::record_request_time_metric( | ||||||
|         server_wrap_util(state, request, payload, func, api_auth), |         server_wrap_util(&flow, state, request, payload, func, api_auth), | ||||||
|         flow, |         &flow, | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     { |     { | ||||||
| @ -636,7 +656,7 @@ where | |||||||
|  |  | ||||||
| pub fn log_and_return_error_response<T>(error: Report<T>) -> HttpResponse | pub fn log_and_return_error_response<T>(error: Report<T>) -> HttpResponse | ||||||
| where | where | ||||||
|     T: actix_web::ResponseError + error_stack::Context + Clone, |     T: error_stack::Context + Clone + ResponseError, | ||||||
|     Report<T>: EmbedError, |     Report<T>: EmbedError, | ||||||
| { | { | ||||||
|     logger::error!(?error); |     logger::error!(?error); | ||||||
|  | |||||||
| @ -20,10 +20,28 @@ use crate::{ | |||||||
|     types::storage, |     types::storage, | ||||||
|     utils::OptionExt, |     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] | #[async_trait] | ||||||
| pub trait AuthenticateAndFetch<T, A> | pub trait AuthenticateAndFetch<T, A> | ||||||
| where | where | ||||||
|     A: AppStateInfo, |     A: AppStateInfo, | ||||||
|  |     T: AuthInfo, | ||||||
| { | { | ||||||
|     async fn authenticate_and_fetch( |     async fn authenticate_and_fetch( | ||||||
|         &self, |         &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<T, A>, |     default_auth: &'a dyn AuthenticateAndFetch<T, A>, | ||||||
|     headers: &HeaderMap, |     headers: &HeaderMap, | ||||||
| ) -> Box<&'a dyn AuthenticateAndFetch<T, A>> | ) -> Box<&'a dyn AuthenticateAndFetch<T, A>> | ||||||
|  | |||||||
| @ -184,12 +184,14 @@ pub enum Flow { | |||||||
|     AttachDisputeEvidence, |     AttachDisputeEvidence, | ||||||
|     /// Retrieve Dispute Evidence flow |     /// Retrieve Dispute Evidence flow | ||||||
|     RetrieveDisputeEvidence, |     RetrieveDisputeEvidence, | ||||||
|  |     /// Request to compatibility layer | ||||||
|  |     CompatibilityLayerRequest, | ||||||
| } | } | ||||||
|  |  | ||||||
| /// | /// | ||||||
| /// Trait for providing generic behaviour to flow metric | /// 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 {} | impl FlowMetric for Flow {} | ||||||
|  |  | ||||||
| /// Category of log event. | /// Category of log event. | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Nishant Joshi
					Nishant Joshi