-
Notifications
You must be signed in to change notification settings - Fork 263
feat(logs): euler-parity golden-line sources + mapping config (on #2075) #2076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2ef6c3a
9a45786
0d7f308
4949a10
7f56c52
5bab766
ec4da20
600da90
c8f44ff
4745df1
9a7da74
c88c403
7dc5933
76bc7b1
b2e6525
0a19920
248c3c5
d34f556
110a7da
1c4740b
3dc3a6e
7df4b57
b008bb3
7b83411
80f9c10
f59f02c
d49d062
b510b43
644b395
4153235
e4a4f90
c3d188a
6bb3894
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,9 @@ pub fn record_fields_from_header<B: hyper::body::Body>(request: &Request<B>) -> | |
| "request", | ||
| uri = %url_path, | ||
| version = ?request.version(), | ||
| // `action` = the real HTTP verb (GET/POST/…). gRPC-over-HTTP2 is always POST; | ||
| // the HTTP gateway carries the true verb. | ||
| action = %request.method(), | ||
| tenant_id = tracing::field::Empty, | ||
| request_id = tracing::field::Empty, | ||
| execution_mode = tracing::field::Empty, | ||
|
|
@@ -276,21 +279,51 @@ where | |
| .. | ||
| } = metadata_payload; | ||
| let current_span = tracing::Span::current(); | ||
| let masked_body = hyperswitch_masking::masked_serialize(&request_data.payload) | ||
| .map_err(|e| tracing::error!("Masked serialization error: {:?}", e)) | ||
| .ok(); | ||
| let connector_name = connector.get_connector_name(); | ||
| current_span.record("service_name", service_name); | ||
| match hyperswitch_masking::masked_serialize(&request_data.payload) { | ||
| Ok(masked_value) => { | ||
| record_json_fields_on_span(vec![("request_body", masked_value)]); | ||
| match masked_body.as_ref() { | ||
| Some(masked_value) => { | ||
| record_json_fields_on_span(vec![("request_body", masked_value.clone())]); | ||
| } | ||
| Err(e) => { | ||
| tracing::error!("Masked serialization error: {:?}", e); | ||
| None => { | ||
| current_span.record("request_body", "<masked serialization error>"); | ||
| } | ||
| }; | ||
| current_span.record("gateway", connector_name); | ||
| current_span.record("merchant_id", merchant_id); | ||
| current_span.record("tenant_id", tenant_id); | ||
| current_span.record("request_id", request_id); | ||
|
|
||
| // Standard request identifiers (order id / customer id / transaction id) surfaced as flat | ||
| // span fields so the log-field mapping can source them by flat key (it cannot reach into | ||
| // `request_body`), without declaring them on every handler's `#[instrument]`. | ||
| if let Some(ids) = masked_body.as_ref().map(|body| { | ||
| [ | ||
| ( | ||
| "merchant_order_id", | ||
| body.get("merchant_order_id").and_then(Value::as_str), | ||
| ), | ||
| ( | ||
| "customer_id", | ||
| body.get("customer") | ||
| .and_then(|customer| customer.get("id")) | ||
| .and_then(Value::as_str), | ||
| ), | ||
| ( | ||
| "merchant_transaction_id", | ||
| body.get("merchant_transaction_id").and_then(Value::as_str), | ||
| ), | ||
| ] | ||
|
Comment on lines
+303
to
+319
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the two merchant id are euler specific id's if they know the values they can send it in runtime headers?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are not extra euler-only values.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its ok they are part of request body |
||
| }) { | ||
| record_json_fields_on_span( | ||
| ids.into_iter() | ||
| .filter_map(|(key, value)| value.map(|value| (key, Value::from(value)))) | ||
| .collect(), | ||
| ); | ||
| } | ||
| tracing::info!("Golden Log Line (incoming - request)"); | ||
| Ok(()) | ||
| } | ||
|
|
@@ -306,6 +339,10 @@ pub fn log_after_initialization<T>( | |
|
|
||
| match &result { | ||
| Ok(response) => { | ||
| // Additive numeric `res_code` (success). `status_code` is left untouched so | ||
| // existing consumers of the gRPC-code field don't break. | ||
| record_json_fields_on_span(vec![("res_code", Value::from(200_i64))]); | ||
|
|
||
| let res_ref = response.get_ref(); | ||
|
|
||
| // Record response_body as structured JSON with masking | ||
|
|
@@ -340,7 +377,12 @@ pub fn log_after_initialization<T>( | |
| } | ||
| Err(status) => { | ||
| current_span.record("error_message", status.message()); | ||
| // Backward-compatible: keep main's gRPC code-name string on `status_code`. | ||
| current_span.record("status_code", status.code().to_string()); | ||
| // Additive numeric `res_code`: connector-aware HTTP status (e.g. 422) — matches the | ||
| // HTTP response the caller receives, not the coarse gRPC code. | ||
| let http_status = crate::http::error::http_status_for_status(status).as_u16(); | ||
| record_json_fields_on_span(vec![("res_code", Value::from(i64::from(http_status)))]); | ||
| } | ||
| } | ||
| // Apply unified log fields (transformations + static values) before emitting the golden log line | ||
|
|
@@ -356,6 +398,15 @@ pub fn log_after_initialization<T>( | |
| tracing::info!("Golden Log Line (incoming - response)"); | ||
| } | ||
|
|
||
| /// Record the additive numeric `latency_ms` on the current span. Shared by the streaming and | ||
| /// non-streaming logging wrappers to avoid drift. | ||
| fn record_latency_ms(duration: u128) { | ||
| record_json_fields_on_span(vec![( | ||
| "latency_ms", | ||
| Value::from(u64::try_from(duration).unwrap_or_default()), | ||
| )]); | ||
| } | ||
|
Comment on lines
+403
to
+408
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this needed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a small helper but it is called from two places (the streaming and non-streaming logging wrappers), so it exists to keep those two identical and avoid drift. I have simplified it to just call |
||
|
|
||
| /// Generic gRPC logging wrapper that accepts a custom parser function. | ||
| /// This allows different parsing strategies for different flow types | ||
| /// (e.g., authenticated flows vs unauthenticated webhook flows). | ||
|
|
@@ -398,6 +449,8 @@ where | |
|
|
||
| let duration = start_time.elapsed().as_millis(); | ||
| current_span.record("response_time", duration); | ||
| // Additive numeric latency alongside the existing `response_time`. | ||
| record_latency_ms(duration); | ||
| result | ||
| } | ||
| .await; | ||
|
|
@@ -466,6 +519,8 @@ where | |
|
|
||
| let duration = start_time.elapsed().as_millis(); | ||
| current_span.record("response_time", duration); | ||
| // Additive numeric latency alongside the existing `response_time`. | ||
| record_latency_ms(duration); | ||
| result | ||
| } | ||
| .await; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the difference between both the above fields
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both point to the same elapsed time.
latencyis the human-readable string we already had;latency_msis the exact same duration but as a plain number of milliseconds, so downstream consumers that want a numeric latency can read it directly instead of parsing the string. One is the string form, the other the numeric form.