-
Notifications
You must be signed in to change notification settings - Fork 8
Improve Prebid auction diagnostics #671
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
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 |
|---|---|---|
|
|
@@ -13,6 +13,28 @@ use super::config::AuctionConfig; | |
| use super::provider::AuctionProvider; | ||
| use super::types::{AuctionContext, AuctionRequest, AuctionResponse, Bid, BidStatus}; | ||
|
|
||
| const PROVIDER_ERROR_MESSAGE_CHARS: usize = 500; | ||
|
|
||
| fn provider_error_message(error: &Report<TrustedServerError>) -> String { | ||
| error | ||
| .current_context() | ||
| .to_string() | ||
| .chars() | ||
| .take(PROVIDER_ERROR_MESSAGE_CHARS) | ||
| .collect() | ||
| } | ||
|
|
||
| fn provider_error_response( | ||
| provider_name: &str, | ||
| response_time_ms: u64, | ||
| error_type: &str, | ||
| error: &Report<TrustedServerError>, | ||
| ) -> AuctionResponse { | ||
| AuctionResponse::error(provider_name, response_time_ms) | ||
| .with_metadata("error_type", serde_json::json!(error_type)) | ||
| .with_metadata("message", serde_json::json!(provider_error_message(error))) | ||
| } | ||
|
|
||
| /// Compute the remaining time budget from a deadline. | ||
| /// | ||
| /// Returns the number of milliseconds left before `timeout_ms` is exceeded, | ||
|
|
@@ -274,6 +296,7 @@ impl AuctionOrchestrator { | |
| let mut backend_to_provider: HashMap<String, (&str, Instant, &dyn AuctionProvider)> = | ||
| HashMap::new(); | ||
| let mut pending_requests: Vec<PlatformPendingRequest> = Vec::new(); | ||
| let mut responses = Vec::new(); | ||
|
ChristianPavilonis marked this conversation as resolved.
|
||
|
|
||
| for provider_name in provider_names { | ||
| let provider = match self.providers.get(provider_name) { | ||
|
|
@@ -353,11 +376,18 @@ impl AuctionOrchestrator { | |
| ); | ||
| } | ||
| Err(e) => { | ||
| let response_time_ms = start_time.elapsed().as_millis() as u64; | ||
| log::warn!( | ||
| "Provider '{}' failed to launch request: {:?}", | ||
| provider.provider_name(), | ||
| e | ||
| ); | ||
| responses.push(provider_error_response( | ||
|
Collaborator
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. 🔧 wrench — Launch-failure messages can surface internal identifiers to
Concrete example: when Suggested fix — give Err(e) => {
let response_time_ms = start_time.elapsed().as_millis() as u64;
log::warn!(
"Provider '{}' failed to launch request: {:?}",
provider.provider_name(),
e
);
let mut error_response = AuctionResponse::error(provider.provider_name(), response_time_ms)
.with_metadata("error_type", serde_json::json!("launch_failed"));
// Optionally surface error_type only here; full detail stays in logs.
responses.push(error_response);
}Or — if you want a per-error-type policy — split |
||
| provider.provider_name(), | ||
| response_time_ms, | ||
| "launch_failed", | ||
| &e, | ||
| )); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -377,7 +407,6 @@ impl AuctionOrchestrator { | |
| // transport timeout fires). Hard deadline enforcement therefore depends | ||
| // on every backend's `first_byte_timeout` being set to at most the | ||
| // remaining auction budget — which Phase 1 above guarantees. | ||
| let mut responses = Vec::new(); | ||
| let mut remaining = pending_requests; | ||
|
|
||
| while !remaining.is_empty() { | ||
|
|
@@ -419,9 +448,11 @@ impl AuctionOrchestrator { | |
| provider_name, | ||
| e | ||
| ); | ||
| responses.push(AuctionResponse::error( | ||
| responses.push(provider_error_response( | ||
| provider_name, | ||
| response_time_ms, | ||
| "parse_response", | ||
| &e, | ||
| )); | ||
| } | ||
| } | ||
|
|
@@ -432,8 +463,12 @@ impl AuctionOrchestrator { | |
| provider_name, | ||
| e | ||
| ); | ||
| responses | ||
| .push(AuctionResponse::error(provider_name, response_time_ms)); | ||
| responses.push(provider_error_response( | ||
| provider_name, | ||
| response_time_ms, | ||
| "unsupported_response_body", | ||
| &e, | ||
| )); | ||
| } | ||
| } | ||
| } else { | ||
|
|
@@ -635,8 +670,9 @@ mod tests { | |
| use crate::auction::config::AuctionConfig; | ||
| use crate::auction::test_support::create_test_auction_context; | ||
| use crate::auction::types::{ | ||
| AdFormat, AdSlot, AuctionRequest, Bid, MediaType, PublisherInfo, UserInfo, | ||
| AdFormat, AdSlot, AuctionRequest, Bid, BidStatus, MediaType, PublisherInfo, UserInfo, | ||
| }; | ||
| use crate::error::TrustedServerError; | ||
|
|
||
| // All-None ClientInfo used across tests that don't need real IP/TLS data. | ||
| // Defined as a const so &EMPTY_CLIENT_INFO has 'static lifetime, avoiding | ||
|
|
@@ -648,6 +684,7 @@ mod tests { | |
| }; | ||
| use crate::platform::test_support::noop_services; | ||
| use crate::test_support::tests::crate_test_settings_str; | ||
| use error_stack::Report; | ||
| use fastly::Request; | ||
| use std::collections::{HashMap, HashSet}; | ||
|
|
||
|
|
@@ -700,6 +737,59 @@ mod tests { | |
| crate::settings::Settings::from_toml(&settings_str).expect("should parse test settings") | ||
| } | ||
|
|
||
| #[test] | ||
| fn provider_error_response_includes_diagnostic_metadata() { | ||
| let error = Report::new(TrustedServerError::Auction { | ||
| message: "parse failed".to_string(), | ||
| }) | ||
| .attach("internal/source.rs:12:34"); | ||
|
|
||
| let response = super::provider_error_response("prebid", 37, "parse_response", &error); | ||
|
|
||
| assert_eq!( | ||
| response.status, | ||
| BidStatus::Error, | ||
| "should mark diagnostic provider responses as errors" | ||
| ); | ||
| assert_eq!( | ||
| response.metadata["error_type"], | ||
| serde_json::json!("parse_response"), | ||
| "should include the provider error classification" | ||
| ); | ||
|
|
||
| let message = response.metadata["message"] | ||
| .as_str() | ||
| .expect("should include provider error message"); | ||
| assert!( | ||
| message.contains("parse failed"), | ||
| "should include user-safe diagnostic detail" | ||
| ); | ||
| assert!( | ||
| !message.contains("internal/source.rs"), | ||
| "should not include attached internal details" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn provider_error_message_truncates_user_safe_context() { | ||
| let long_message = "x".repeat(super::PROVIDER_ERROR_MESSAGE_CHARS + 100); | ||
| let error = Report::new(TrustedServerError::Auction { | ||
| message: long_message, | ||
| }); | ||
|
|
||
| let message = super::provider_error_message(&error); | ||
|
|
||
| assert_eq!( | ||
| message.chars().count(), | ||
| super::PROVIDER_ERROR_MESSAGE_CHARS, | ||
| "should cap provider error messages" | ||
| ); | ||
| assert!( | ||
| message.starts_with("Auction error: "), | ||
| "should preserve the current context display text" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn filters_winning_bids_below_floor() { | ||
| let orchestrator = AuctionOrchestrator::new(AuctionConfig::default()); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.