Skip to content

Commit 7a76d6c

Browse files
feat(events): add events for incoming API requests (#2621)
Co-authored-by: Nishant Joshi <[email protected]>
1 parent da77d13 commit 7a76d6c

File tree

6 files changed

+77
-23
lines changed

6 files changed

+77
-23
lines changed

crates/router/src/events.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use serde::Serialize;
22

3+
pub mod api_logs;
34
pub mod event_logger;
45

56
pub trait EventHandler: Sync + Send + dyn_clone::DynClone {
6-
fn log_event<T: Event>(&self, event: T, previous: Option<T>);
7+
fn log_event<T: Event>(&self, event: T);
78
}
89

910
#[derive(Debug, Serialize)]

crates/router/src/events/api_logs.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use router_env::{tracing_actix_web::RequestId, types::FlowMetric};
2+
use serde::{Deserialize, Serialize};
3+
use time::OffsetDateTime;
4+
5+
use super::Event;
6+
7+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
8+
pub struct ApiEvent {
9+
api_flow: String,
10+
created_at_timestamp: i128,
11+
request_id: String,
12+
latency: u128,
13+
status_code: i64,
14+
}
15+
16+
impl ApiEvent {
17+
pub fn new(
18+
api_flow: &impl FlowMetric,
19+
request_id: &RequestId,
20+
latency: u128,
21+
status_code: i64,
22+
) -> Self {
23+
Self {
24+
api_flow: api_flow.to_string(),
25+
created_at_timestamp: OffsetDateTime::now_utc().unix_timestamp_nanos(),
26+
request_id: request_id.as_hyphenated().to_string(),
27+
latency,
28+
status_code,
29+
}
30+
}
31+
}
32+
33+
impl Event for ApiEvent {
34+
fn event_type() -> super::EventType {
35+
super::EventType::ApiLogs
36+
}
37+
38+
fn key(&self) -> String {
39+
self.request_id.to_string()
40+
}
41+
}

crates/router/src/events/event_logger.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ use crate::services::logger;
55
pub struct EventLogger {}
66

77
impl EventHandler for EventLogger {
8-
fn log_event<T: Event>(&self, event: T, previous: Option<T>) {
9-
if let Some(prev) = previous {
10-
logger::info!(previous = ?serde_json::to_string(&prev).unwrap_or(r#"{ "error": "Serialization failed" }"#.to_string()), current = ?serde_json::to_string(&event).unwrap_or(r#"{ "error": "Serialization failed" }"#.to_string()), event_type =? T::event_type(), event_id =? event.key(), log_type = "event");
11-
} else {
12-
logger::info!(current = ?serde_json::to_string(&event).unwrap_or(r#"{ "error": "Serialization failed" }"#.to_string()), event_type =? T::event_type(), event_id =? event.key(), log_type = "event");
13-
}
8+
fn log_event<T: Event>(&self, event: T) {
9+
logger::info!(current = ?serde_json::to_string(&event).unwrap_or(r#"{ "error": "Serialization failed" }"#.to_string()), event_type =? T::event_type(), event_id =? event.key(), log_type = "event");
1410
}
1511
}

crates/router/src/routes/app.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use actix_web::{web, Scope};
55
use external_services::email::{AwsSes, EmailClient};
66
#[cfg(feature = "kms")]
77
use external_services::kms::{self, decrypt::KmsDecrypt};
8+
use router_env::tracing_actix_web::RequestId;
89
use scheduler::SchedulerInterface;
910
use storage_impl::MockDb;
1011
use tokio::sync::oneshot;
@@ -58,7 +59,7 @@ pub trait AppStateInfo {
5859
fn event_handler(&self) -> &Self::Event;
5960
#[cfg(feature = "email")]
6061
fn email_client(&self) -> Arc<dyn EmailClient>;
61-
fn add_request_id(&mut self, request_id: Option<String>);
62+
fn add_request_id(&mut self, request_id: RequestId);
6263
fn add_merchant_id(&mut self, merchant_id: Option<String>);
6364
fn add_flow_name(&mut self, flow_name: String);
6465
fn get_request_id(&self) -> Option<String>;
@@ -79,7 +80,7 @@ impl AppStateInfo for AppState {
7980
fn event_handler(&self) -> &Self::Event {
8081
&self.event_handler
8182
}
82-
fn add_request_id(&mut self, request_id: Option<String>) {
83+
fn add_request_id(&mut self, request_id: RequestId) {
8384
self.api_client.add_request_id(request_id);
8485
}
8586
fn add_merchant_id(&mut self, merchant_id: Option<String>) {

crates/router/src/services/api.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use actix_web::{body, web, FromRequest, HttpRequest, HttpResponse, Responder, Re
1313
use api_models::enums::CaptureMethod;
1414
pub use client::{proxy_bypass_urls, ApiClient, MockApiClient, ProxyClient};
1515
pub use common_utils::request::{ContentType, Method, Request, RequestBuilder};
16-
use common_utils::{consts::X_HS_LATENCY, errors::ReportSwitchExt};
16+
use common_utils::{
17+
consts::X_HS_LATENCY,
18+
errors::{ErrorSwitch, ReportSwitchExt},
19+
};
1720
use error_stack::{report, IntoReport, Report, ResultExt};
1821
use masking::{ExposeOptionInterface, PeekInterface};
1922
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
@@ -30,6 +33,7 @@ use crate::{
3033
errors::{self, CustomResult},
3134
payments,
3235
},
36+
events::{api_logs::ApiEvent, EventHandler},
3337
logger,
3438
routes::{
3539
app::AppStateInfo,
@@ -750,19 +754,20 @@ where
750754
T: Debug,
751755
A: AppStateInfo + Clone,
752756
U: auth::AuthInfo,
753-
CustomResult<ApplicationResponse<Q>, E>: ReportSwitchExt<ApplicationResponse<Q>, OErr>,
754-
CustomResult<U, errors::ApiErrorResponse>: ReportSwitchExt<U, OErr>,
755-
CustomResult<(), errors::ApiErrorResponse>: ReportSwitchExt<(), OErr>,
756-
OErr: ResponseError + Sync + Send + 'static,
757+
E: ErrorSwitch<OErr> + error_stack::Context,
758+
OErr: ResponseError + error_stack::Context,
759+
errors::ApiErrorResponse: ErrorSwitch<OErr>,
757760
{
758761
let request_id = RequestId::extract(request)
759762
.await
760-
.ok()
761-
.map(|id| id.as_hyphenated().to_string());
763+
.into_report()
764+
.attach_printable("Unable to extract request id from request")
765+
.change_context(errors::ApiErrorResponse::InternalServerError.switch())?;
762766

763767
let mut request_state = state.get_ref().clone();
764768

765769
request_state.add_request_id(request_id);
770+
let start_instant = Instant::now();
766771

767772
let auth_out = api_auth
768773
.authenticate_and_fetch(request.headers(), &request_state)
@@ -795,11 +800,20 @@ where
795800
.switch()?;
796801
res
797802
};
803+
let request_duration = Instant::now()
804+
.saturating_duration_since(start_instant)
805+
.as_millis();
798806

799807
let status_code = match output.as_ref() {
800808
Ok(res) => metrics::request::track_response_status_code(res),
801809
Err(err) => err.current_context().status_code().as_u16().into(),
802810
};
811+
state.event_handler().log_event(ApiEvent::new(
812+
flow,
813+
&request_id,
814+
request_duration,
815+
status_code,
816+
));
803817

804818
metrics::request::status_code_metrics(status_code, flow.to_string(), merchant_id.to_string());
805819

@@ -827,8 +841,7 @@ where
827841
U: auth::AuthInfo,
828842
A: AppStateInfo + Clone,
829843
ApplicationResponse<Q>: Debug,
830-
CustomResult<ApplicationResponse<Q>, E>:
831-
ReportSwitchExt<ApplicationResponse<Q>, api_models::errors::types::ApiErrorResponse>,
844+
E: ErrorSwitch<api_models::errors::types::ApiErrorResponse> + error_stack::Context,
832845
{
833846
let request_method = request.method().as_str();
834847
let url_path = request.path();

crates/router/src/services/api/client.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use http::{HeaderValue, Method};
55
use masking::PeekInterface;
66
use once_cell::sync::OnceCell;
77
use reqwest::multipart::Form;
8+
use router_env::tracing_actix_web::RequestId;
89

910
use super::{request::Maskable, Request};
1011
use crate::{
@@ -167,10 +168,10 @@ where
167168
forward_to_kafka: bool,
168169
) -> CustomResult<reqwest::Response, ApiClientError>;
169170

170-
fn add_request_id(&mut self, _request_id: Option<String>);
171+
fn add_request_id(&mut self, request_id: RequestId);
171172
fn get_request_id(&self) -> Option<String>;
172173
fn add_merchant_id(&mut self, _merchant_id: Option<String>);
173-
fn add_flow_name(&mut self, _flow_name: String);
174+
fn add_flow_name(&mut self, flow_name: String);
174175
}
175176

176177
dyn_clone::clone_trait_object!(ApiClient);
@@ -350,8 +351,9 @@ impl ApiClient for ProxyClient {
350351
crate::services::send_request(state, request, option_timeout_secs).await
351352
}
352353

353-
fn add_request_id(&mut self, _request_id: Option<String>) {
354-
self.request_id = _request_id
354+
fn add_request_id(&mut self, request_id: RequestId) {
355+
self.request_id
356+
.replace(request_id.as_hyphenated().to_string());
355357
}
356358

357359
fn get_request_id(&self) -> Option<String> {
@@ -402,7 +404,7 @@ impl ApiClient for MockApiClient {
402404
Err(ApiClientError::UnexpectedState.into())
403405
}
404406

405-
fn add_request_id(&mut self, _request_id: Option<String>) {
407+
fn add_request_id(&mut self, _request_id: RequestId) {
406408
// [#2066]: Add Mock implementation for ApiClient
407409
}
408410

0 commit comments

Comments
 (0)