additional context for pages (#1317)

This commit is contained in:
Brandon Thomas
2026-04-07 19:07:49 -04:00
committed by GitHub
parent 12e4ff7a29
commit a2f204b1be
9 changed files with 176 additions and 22 deletions
@@ -44,6 +44,18 @@ pub struct NotificationDetails {
/// Third-party identifier associated with the event, if any.
pub(crate) third_party_id: Option<String>,
/// IP address of the request that triggered this notification, if any.
pub(crate) request_ip_address: Option<String>,
/// Anonymous visitor tracking token extracted from the request, if any.
pub(crate) avt_cookie_token: Option<String>,
/// Raw (signed) user session token extracted from the request, if any.
pub(crate) session_token: Option<String>,
/// User token decoded from the request's user session cookie, if any.
pub(crate) session_user_token: Option<String>,
}
impl NotificationDetails {
@@ -164,6 +176,10 @@ impl NotificationDetails {
parts.push(format!("HTTP Status Code: {}", status_code));
}
if let Some(ip) = &self.request_ip_address {
parts.push(format!("Request IP: {}", ip));
}
if parts.is_empty() { None } else { Some(parts.join("\n")) }
}
@@ -187,6 +203,18 @@ impl NotificationDetails {
parts.push(format!("Third Party ID: {}", third_party_id));
}
if let Some(avt) = &self.avt_cookie_token {
parts.push(format!("AVT Cookie Token: {}", avt));
}
if let Some(session) = &self.session_token {
parts.push(format!("Session Token: {}", session));
}
if let Some(user) = &self.session_user_token {
parts.push(format!("Session User Token: {}", user));
}
if parts.is_empty() { None } else { Some(parts.join("\n")) }
}
@@ -22,6 +22,11 @@ pub struct NotificationDetailsBuilder {
pub(crate) media_file_token: Option<String>,
pub(crate) inference_job_token: Option<String>,
pub(crate) third_party_id: Option<String>,
pub(crate) request_ip_address: Option<String>,
pub(crate) avt_cookie_token: Option<String>,
pub(crate) session_token: Option<String>,
pub(crate) session_user_token: Option<String>,
}
impl NotificationDetailsBuilder {
@@ -43,6 +48,10 @@ impl NotificationDetailsBuilder {
media_file_token: None,
inference_job_token: None,
third_party_id: None,
request_ip_address: None,
avt_cookie_token: None,
session_token: None,
session_user_token: None,
}
}
@@ -68,6 +77,10 @@ impl NotificationDetailsBuilder {
media_file_token: None,
inference_job_token: None,
third_party_id: None,
request_ip_address: None,
avt_cookie_token: None,
session_token: None,
session_user_token: None,
}
}
@@ -128,6 +141,26 @@ impl NotificationDetailsBuilder {
self
}
pub fn set_request_ip_address(mut self, request_ip_address: Option<String>) -> Self {
self.request_ip_address = request_ip_address;
self
}
pub fn set_avt_cookie_token(mut self, avt_cookie_token: Option<String>) -> Self {
self.avt_cookie_token = avt_cookie_token;
self
}
pub fn set_session_token(mut self, session_token: Option<String>) -> Self {
self.session_token = session_token;
self
}
pub fn set_session_user_token(mut self, session_user_token: Option<String>) -> Self {
self.session_user_token = session_user_token;
self
}
// --- Build ---
pub fn build(self) -> NotificationDetails {
@@ -150,6 +183,10 @@ impl NotificationDetailsBuilder {
media_file_token: self.media_file_token,
inference_job_token: self.inference_job_token,
third_party_id: self.third_party_id,
request_ip_address: self.request_ip_address,
avt_cookie_token: self.avt_cookie_token,
session_token: self.session_token,
session_user_token: self.session_user_token,
}
}
@@ -1,4 +1,4 @@
use std::future::{Future, Ready, ready};
use std::future::{ready, Future, Ready};
use std::pin::Pin;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
@@ -6,12 +6,13 @@ use actix_web::Error;
use pager::client::pager::Pager;
use super::handlers::check_advanced_common_web_error::check_advanced_common_web_error;
use super::handlers::check_common_web_error::check_common_web_error;
use super::handlers::check_status_code_fallback::check_status_code_fallback;
use super::request_debugging_metadata::RequestDebuggingMetadata;
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::state::flags::paging_flags::PagingFlags;
use super::check_advanced_common_web_error::check_advanced_common_web_error;
use super::check_common_web_error::check_common_web_error;
use super::check_status_code_fallback::check_status_code_fallback;
// ======================== Transform (factory) ========================
@@ -85,19 +86,21 @@ impl<S, B> Service<ServiceRequest> for ErrorAlertingService<S>
return Box::pin(self.service.call(req));
}
let pager = self.pager.clone();
let method = req.method().to_string();
let path = req.path().to_string();
let pager = self.pager.clone();
let metadata = RequestDebuggingMetadata::from_service_request(&req);
let fut = self.service.call(req);
Box::pin(async move {
match fut.await {
Ok(res) => {
check_ok_response_for_alerts(&pager, &method, &path, &res);
check_ok_response_for_alerts(&pager, &method, &path, &metadata, &res);
Ok(res)
}
Err(err) => {
check_err_for_alerts(&pager, &method, &path, &err);
check_err_for_alerts(&pager, &method, &path, &metadata, &err);
Err(err)
}
}
@@ -115,6 +118,7 @@ fn check_ok_response_for_alerts<B>(
pager: &Pager,
method: &str,
path: &str,
metadata: &RequestDebuggingMetadata,
response: &ServiceResponse<B>,
) {
let status = response.status();
@@ -124,20 +128,20 @@ fn check_ok_response_for_alerts<B>(
// --- Typed matchers (add new check_*.rs modules and downcast branches here) ---
if let Some(advanced_err) = err.as_error::<AdvancedCommonWebError>() {
if check_advanced_common_web_error(pager, method, path, advanced_err) {
if check_advanced_common_web_error(pager, method, path, metadata, advanced_err) {
return;
}
}
if let Some(common_err) = err.as_error::<CommonWebError>() {
if check_common_web_error(pager, method, path, common_err) {
if check_common_web_error(pager, method, path, metadata, common_err) {
return;
}
}
}
// --- Status code fallback ---
check_status_code_fallback(pager, method, path, status.as_u16());
check_status_code_fallback(pager, method, path, metadata, status.as_u16());
}
/// Inspect an `actix_web::Error` returned from the handler or inner middleware.
@@ -145,23 +149,24 @@ fn check_err_for_alerts(
pager: &Pager,
method: &str,
path: &str,
metadata: &RequestDebuggingMetadata,
err: &Error,
) {
// --- Typed matchers (add new check_*.rs modules and downcast branches here) ---
if let Some(advanced_err) = err.as_error::<AdvancedCommonWebError>() {
if check_advanced_common_web_error(pager, method, path, advanced_err) {
if check_advanced_common_web_error(pager, method, path, metadata, advanced_err) {
return;
}
}
if let Some(common_err) = err.as_error::<CommonWebError>() {
if check_common_web_error(pager, method, path, common_err) {
if check_common_web_error(pager, method, path, metadata, common_err) {
return;
}
}
// --- Status code fallback ---
let status = err.as_response_error().status_code();
check_status_code_fallback(pager, method, path, status.as_u16());
check_status_code_fallback(pager, method, path, metadata, status.as_u16());
}
@@ -5,13 +5,15 @@ use pager::notification::notification_details_builder::NotificationDetailsBuilde
use pager::notification::notification_urgency::NotificationUrgency;
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::middleware::error_alerting_middleware::request_debugging_metadata::RequestDebuggingMetadata;
/// Check `AdvancedCommonWebError` and alert on uncaught server errors.
/// Returns `true` if the error was handled (alerted or intentionally skipped).
pub(super) fn check_advanced_common_web_error(
pub(crate) fn check_advanced_common_web_error(
pager: &Pager,
method: &str,
path: &str,
metadata: &RequestDebuggingMetadata,
error: &AdvancedCommonWebError,
) -> bool {
if !error.is_server_error() {
@@ -32,10 +34,14 @@ pub(super) fn check_advanced_common_web_error(
};
builder = builder
.set_urgency(Some(NotificationUrgency::Medium))
.set_http_method(Some(method.to_string()))
.set_http_path(Some(path.to_string()))
.set_http_status_code(Some(500))
.set_urgency(Some(NotificationUrgency::Medium));
.set_request_ip_address(metadata.request_ip_address.clone())
.set_avt_cookie_token(metadata.avt_cookie_token.clone())
.set_session_token(metadata.session_token.clone())
.set_session_user_token(metadata.session_user_token.clone());
let notification = builder.build();
@@ -5,13 +5,15 @@ use pager::notification::notification_details_builder::NotificationDetailsBuilde
use pager::notification::notification_urgency::NotificationUrgency;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::middleware::error_alerting_middleware::request_debugging_metadata::RequestDebuggingMetadata;
/// Check `CommonWebError` and alert on server errors.
/// Returns `true` if the error was handled (alerted or intentionally skipped).
pub(super) fn check_common_web_error(
pub(crate) fn check_common_web_error(
pager: &Pager,
method: &str,
path: &str,
metadata: &RequestDebuggingMetadata,
error: &CommonWebError,
) -> bool {
match error {
@@ -28,10 +30,14 @@ pub(super) fn check_common_web_error(
let notification = NotificationDetailsBuilder::from_title(summary)
.set_description(Some(description))
.set_urgency(Some(NotificationUrgency::Medium))
.set_http_method(Some(method.to_string()))
.set_http_path(Some(path.to_string()))
.set_http_status_code(Some(500))
.set_urgency(Some(NotificationUrgency::Medium))
.set_request_ip_address(metadata.request_ip_address.clone())
.set_avt_cookie_token(metadata.avt_cookie_token.clone())
.set_session_token(metadata.session_token.clone())
.set_session_user_token(metadata.session_user_token.clone())
.build();
if let Err(err) = pager.enqueue_page(notification) {
@@ -4,11 +4,14 @@ use pager::client::pager::Pager;
use pager::notification::notification_details_builder::NotificationDetailsBuilder;
use pager::notification::notification_urgency::NotificationUrgency;
use crate::http_server::middleware::error_alerting_middleware::request_debugging_metadata::RequestDebuggingMetadata;
/// Fallback alerting based on HTTP status code when no typed error matched.
pub(super) fn check_status_code_fallback(
pub(crate) fn check_status_code_fallback(
pager: &Pager,
method: &str,
path: &str,
metadata: &RequestDebuggingMetadata,
status_code: u16,
) {
match status_code {
@@ -24,10 +27,14 @@ pub(super) fn check_status_code_fallback(
let notification = NotificationDetailsBuilder::from_title(summary)
.set_description(Some(description))
.set_urgency(Some(NotificationUrgency::Medium))
.set_http_method(Some(method.to_string()))
.set_http_path(Some(path.to_string()))
.set_http_status_code(Some(status_code))
.set_urgency(Some(NotificationUrgency::Medium))
.set_request_ip_address(metadata.request_ip_address.clone())
.set_avt_cookie_token(metadata.avt_cookie_token.clone())
.set_session_token(metadata.session_token.clone())
.set_session_user_token(metadata.session_user_token.clone())
.build();
if let Err(err) = pager.enqueue_page(notification) {
@@ -0,0 +1,3 @@
pub(super) mod check_advanced_common_web_error;
pub(super) mod check_common_web_error;
pub(super) mod check_status_code_fallback;
@@ -1,5 +1,4 @@
mod check_advanced_common_web_error;
mod check_common_web_error;
mod check_status_code_fallback;
mod handlers;
mod request_debugging_metadata;
pub mod error_alerting_middleware;
@@ -0,0 +1,63 @@
use std::sync::Arc;
use actix_web::dev::ServiceRequest;
use actix_web::web;
use http_server_common::request::get_request_ip::get_request_ip;
use log::debug;
use crate::state::server_state::ServerState;
/// Optional per-request context extracted by the error alerting middleware
/// and forwarded to each handler-specific check. Every field fails open:
/// if extraction errors, the field is `None`.
#[derive(Debug, Default, Clone)]
pub(crate) struct RequestDebuggingMetadata {
pub request_ip_address: Option<String>,
pub avt_cookie_token: Option<String>,
pub session_token: Option<String>,
pub session_user_token: Option<String>,
}
impl RequestDebuggingMetadata {
/// Extract whatever we can from a `ServiceRequest`. Never fails — each
/// piece is best-effort and defaults to `None` on any error.
pub(crate) fn from_service_request(req: &ServiceRequest) -> Self {
let http_request = req.request();
let request_ip_address = Some(get_request_ip(http_request));
let server_state = req.app_data::<web::Data<Arc<ServerState>>>();
let avt_cookie_token = server_state
.and_then(|state| {
state
.avt_cookie_manager
.decode_cookie_payload_from_request(http_request)
.unwrap_or_else(|e| {
debug!("Error alerting middleware: avt cookie decode failed: {:?}", e);
None
})
})
.map(|payload| payload.avt_token.as_str().to_string());
let (session_token, session_user_token) = server_state
.and_then(|state| {
state
.session_cookie_manager
.decode_session_payload_from_request(http_request)
.unwrap_or_else(|e| {
debug!("Error alerting middleware: session cookie decode failed: {:?}", e);
None
})
})
.map(|payload| (Some(payload.session_token), payload.maybe_user_token))
.unwrap_or((None, None));
Self {
request_ip_address,
avt_cookie_token,
session_token,
session_user_token,
}
}
}