chore: add metrics for external api call (#2021)

This commit is contained in:
Kartikeya Hegde
2023-08-30 23:24:24 +05:30
committed by GitHub
parent 58105d4ae2
commit 08fb2a93c1
6 changed files with 95 additions and 58 deletions

View File

@ -39,3 +39,5 @@ pub(crate) const APPLEPAY_VALIDATION_URL: &str =
// Qr Image data source starts with this string
// The base64 image data will be appended to it to image data source
pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64";
pub(crate) const METRICS_HOST_TAG_NAME: &str = "host";

View File

@ -197,6 +197,7 @@ pub async fn add_card_to_locker(
})
},
&metrics::CARD_ADD_TIME,
&[],
)
.await
}
@ -221,6 +222,7 @@ pub async fn get_card_from_locker(
})
},
&metrics::CARD_GET_TIME,
&[],
)
.await
}
@ -243,6 +245,7 @@ pub async fn delete_card_from_locker(
})
},
&metrics::CARD_DELETE_TIME,
&[],
)
.await
}

View File

@ -12,6 +12,7 @@ counter_metric!(AWS_KMS_FAILURES, GLOBAL_METER); // No. of AWS KMS API failures
counter_metric!(REQUESTS_RECEIVED, GLOBAL_METER);
counter_metric!(REQUEST_STATUS, GLOBAL_METER);
histogram_metric!(REQUEST_TIME, GLOBAL_METER);
histogram_metric!(EXTERNAL_REQUEST_TIME, GLOBAL_METER);
// Operation Level Metrics
counter_metric!(PAYMENT_OPS_COUNT, GLOBAL_METER);

View File

@ -1,3 +1,5 @@
use router_env::opentelemetry;
use super::utils as metric_utils;
use crate::services::ApplicationResponse;
@ -23,12 +25,13 @@ where
pub async fn record_operation_time<F, R>(
future: F,
metric: &once_cell::sync::Lazy<router_env::opentelemetry::metrics::Histogram<f64>>,
key_value: &[opentelemetry::KeyValue],
) -> R
where
F: futures::Future<Output = R>,
{
let (result, time) = metric_utils::time_future(future).await;
metric.record(&super::CONTEXT, time.as_secs_f64(), &[]);
metric.record(&super::CONTEXT, time.as_secs_f64(), key_value);
result
}

View File

@ -28,7 +28,11 @@ use crate::{
payments,
},
logger,
routes::{app::AppStateInfo, metrics, AppState},
routes::{
app::AppStateInfo,
metrics::{self, request as metrics_request},
AppState,
},
services::authentication as auth,
types::{
self,
@ -407,12 +411,19 @@ pub async fn send_request(
option_timeout_secs: Option<u64>,
) -> CustomResult<reqwest::Response, errors::ApiClientError> {
logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request);
let url = &request.url;
let url = reqwest::Url::parse(&request.url)
.into_report()
.change_context(errors::ApiClientError::UrlEncodingFailed)?;
#[cfg(feature = "dummy_connector")]
let should_bypass_proxy = url.starts_with(&state.conf.connectors.dummyconnector.base_url)
|| client::proxy_bypass_urls(&state.conf.locker).contains(url);
let should_bypass_proxy = url
.as_str()
.starts_with(&state.conf.connectors.dummyconnector.base_url)
|| client::proxy_bypass_urls(&state.conf.locker).contains(&url.to_string());
#[cfg(not(feature = "dummy_connector"))]
let should_bypass_proxy = client::proxy_bypass_urls(&state.conf.locker).contains(url);
let should_bypass_proxy =
client::proxy_bypass_urls(&state.conf.locker).contains(&url.to_string());
let client = client::create_client(
&state.conf.proxy,
should_bypass_proxy,
@ -420,66 +431,81 @@ pub async fn send_request(
request.certificate_key,
)?;
let headers = request.headers.construct_header_map()?;
match request.method {
Method::Get => client.get(url),
Method::Post => {
let client = client.post(url);
match request.content_type {
Some(ContentType::Json) => client.json(&request.payload),
Some(ContentType::FormData) => client.multipart(
request
.form_data
.unwrap_or_else(reqwest::multipart::Form::new),
),
let metrics_tag = router_env::opentelemetry::KeyValue {
key: consts::METRICS_HOST_TAG_NAME.into(),
value: url.host_str().unwrap_or_default().to_string().into(),
};
// Currently this is not used remove this if not required
// If using this then handle the serde_part
Some(ContentType::FormUrlEncoded) => {
let payload = match request.payload.clone() {
Some(req) => serde_json::from_str(req.peek())
let send_request = async {
match request.method {
Method::Get => client.get(url),
Method::Post => {
let client = client.post(url);
match request.content_type {
Some(ContentType::Json) => client.json(&request.payload),
Some(ContentType::FormData) => client.multipart(
request
.form_data
.unwrap_or_else(reqwest::multipart::Form::new),
),
// Currently this is not used remove this if not required
// If using this then handle the serde_part
Some(ContentType::FormUrlEncoded) => {
let payload = match request.payload.clone() {
Some(req) => serde_json::from_str(req.peek())
.into_report()
.change_context(errors::ApiClientError::UrlEncodingFailed)?,
_ => json!(r#""#),
};
let url_encoded_payload = serde_urlencoded::to_string(&payload)
.into_report()
.change_context(errors::ApiClientError::UrlEncodingFailed)?,
_ => json!(r#""#),
};
let url_encoded_payload = serde_urlencoded::to_string(&payload)
.into_report()
.change_context(errors::ApiClientError::UrlEncodingFailed)
.attach_printable_lazy(|| {
format!(
"Unable to do url encoding on request: {:?}",
&request.payload
)
})?;
.change_context(errors::ApiClientError::UrlEncodingFailed)
.attach_printable_lazy(|| {
format!(
"Unable to do url encoding on request: {:?}",
&request.payload
)
})?;
logger::debug!(?url_encoded_payload);
client.body(url_encoded_payload)
logger::debug!(?url_encoded_payload);
client.body(url_encoded_payload)
}
// If payload needs processing the body cannot have default
None => client.body(request.payload.expose_option().unwrap_or_default()),
}
// If payload needs processing the body cannot have default
None => client.body(request.payload.expose_option().unwrap_or_default()),
}
}
Method::Put => client
.put(url)
.body(request.payload.expose_option().unwrap_or_default()), // If payload needs processing the body cannot have default
Method::Delete => client.delete(url),
}
.add_headers(headers)
.timeout(Duration::from_secs(
option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT),
))
.send()
.await
.map_err(|error| match error {
error if error.is_timeout() => {
metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]);
errors::ApiClientError::RequestTimeoutReceived
Method::Put => client
.put(url)
.body(request.payload.expose_option().unwrap_or_default()), // If payload needs processing the body cannot have default
Method::Delete => client.delete(url),
}
_ => errors::ApiClientError::RequestNotSent(error.to_string()),
})
.into_report()
.attach_printable("Unable to send request to connector")
.add_headers(headers)
.timeout(Duration::from_secs(
option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT),
))
.send()
.await
.map_err(|error| match error {
error if error.is_timeout() => {
metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]);
errors::ApiClientError::RequestTimeoutReceived
}
_ => errors::ApiClientError::RequestNotSent(error.to_string()),
})
.into_report()
.attach_printable("Unable to send request to connector")
};
metrics_request::record_operation_time(
send_request,
&metrics::EXTERNAL_REQUEST_TIME,
&[metrics_tag],
)
.await
}
#[instrument(skip_all)]

View File

@ -192,6 +192,7 @@ where
request::record_operation_time(
crypto::Encryptable::encrypt(inner, key, crypto::GcmAes256),
&ENCRYPTION_TIME,
&[],
)
.await
}
@ -220,6 +221,7 @@ where
request::record_operation_time(
inner.async_map(|item| crypto::Encryptable::decrypt(item, key, crypto::GcmAes256)),
&DECRYPTION_TIME,
&[],
)
.await
.transpose()