mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
additional context for pages (#1317)
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-13
@@ -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());
|
||||
}
|
||||
|
||||
+8
-2
@@ -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();
|
||||
|
||||
+8
-2
@@ -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) {
|
||||
+9
-2
@@ -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) {
|
||||
+3
@@ -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;
|
||||
+2
-3
@@ -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;
|
||||
|
||||
+63
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user