feat(metrics): add response metrics (#1263)

This commit is contained in:
Nishant Joshi
2023-05-25 15:04:13 +05:30
committed by GitHub
parent 5e90a369db
commit 4ebd26f27e
9 changed files with 134 additions and 19 deletions

View File

@ -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);

View File

@ -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))

View File

@ -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(),

View File

@ -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 {

View File

@ -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);

View File

@ -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,
}
}

View File

@ -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);

View File

@ -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>>

View File

@ -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.