feat: emit oauth verification timeouts as metrics (not sentry) (#1694)
Some checks failed
Glean probe-scraper / glean-probe-scraper (push) Has been cancelled

and fix the sentry middleware to use ReportableError::tags

Closes STOR-201
This commit is contained in:
Philip Jenvey 2025-05-02 13:51:52 -07:00 committed by GitHub
parent 3680d0097d
commit 624eced1e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 30 deletions

View File

@ -93,10 +93,7 @@ where
// but we may need that information to debug a production issue. We can // but we may need that information to debug a production issue. We can
// add an info here, temporarily turn on info level debugging on a given server, // add an info here, temporarily turn on info level debugging on a given server,
// capture it, and then turn it off before we run out of money. // capture it, and then turn it off before we run out of money.
if let Some(label) = reportable_err.metric_label() { maybe_emit_metrics(&metrics, reportable_err);
debug!("Sentry: Sending error to metrics: {:?}", reportable_err);
let _ = metrics.incr(label);
}
debug!("Sentry: Not reporting error (service error): {:?}", error); debug!("Sentry: Not reporting error (service error): {:?}", error);
return Err(error); return Err(error);
} }
@ -115,10 +112,7 @@ where
if let Some(error) = response.response().error() { if let Some(error) = response.response().error() {
if let Some(reportable_err) = error.as_error::<E>() { if let Some(reportable_err) = error.as_error::<E>() {
if !reportable_err.is_sentry_event() { if !reportable_err.is_sentry_event() {
if let Some(label) = reportable_err.metric_label() { maybe_emit_metrics(&metrics, reportable_err);
debug!("Sentry: Sending error to metrics: {:?}", reportable_err);
let _ = metrics.incr(label);
}
debug!("Not reporting error (service error): {:?}", error); debug!("Not reporting error (service error): {:?}", error);
return Ok(response); return Ok(response);
} }
@ -134,6 +128,23 @@ where
} }
} }
/// Emit metrics when a [ReportableError::metric_label] is returned
fn maybe_emit_metrics<E>(metrics: &StatsdClient, err: &E)
where
E: ReportableError,
{
let Some(label) = err.metric_label() else {
return;
};
debug!("Sending error to metrics: {:?}", err);
let mut builder = metrics.incr_with_tags(label);
let tags = err.tags();
for (key, val) in &tags {
builder = builder.with_tag(key, val);
}
builder.send();
}
/// Build a Sentry request struct from the HTTP request /// Build a Sentry request struct from the HTTP request
fn sentry_request_from_http(request: &ServiceRequest) -> sentry::protocol::Request { fn sentry_request_from_http(request: &ServiceRequest) -> sentry::protocol::Request {
sentry::protocol::Request { sentry::protocol::Request {

View File

@ -115,7 +115,7 @@ impl TokenserverRequest {
warn!("Client attempted stale value"; "uid"=> self.user.uid, "client_state"=> self.user.client_state.clone()); warn!("Client attempted stale value"; "uid"=> self.user.uid, "client_state"=> self.user.client_state.clone());
return Err(TokenserverError::invalid_client_state( return Err(TokenserverError::invalid_client_state(
error_message, error_message,
Some(Box::new(vec![("is_stale", "true".to_owned())])), Some(vec![("is_stale", "true".to_owned())]),
)); ));
} }
@ -1200,7 +1200,7 @@ mod tests {
error, error,
TokenserverError::invalid_client_state( TokenserverError::invalid_client_state(
error_message, error_message,
Some(Box::new(vec![("is_stale", "true".to_owned())])) Some(vec![("is_stale", "true".to_owned())])
) )
); );
} }

View File

@ -176,10 +176,7 @@ impl VerifyToken for Verifier {
// than the specified number of seconds. // than the specified number of seconds.
time::timeout(Duration::from_secs(self.timeout), fut) time::timeout(Duration::from_secs(self.timeout), fut)
.await .await
.map_err(|_| TokenserverError { .map_err(|_| TokenserverError::oauth_timeout())?
context: "OAuth verification timeout".to_owned(),
..TokenserverError::resource_unavailable()
})?
} }
} }

View File

@ -23,7 +23,7 @@ pub struct TokenserverError {
/// distinguish between similar errors in Sentry. /// distinguish between similar errors in Sentry.
pub context: String, pub context: String,
pub backtrace: Box<Backtrace>, pub backtrace: Box<Backtrace>,
pub tags: Option<Box<Vec<(&'static str, String)>>>, pub tags: Option<Vec<(&'static str, String)>>,
/// TODO: refactor TokenserverError to include a TokenserverErrorKind, w/ /// TODO: refactor TokenserverError to include a TokenserverErrorKind, w/
/// variants for sources (currently just DbError). May require moving /// variants for sources (currently just DbError). May require moving
/// TokenserverError out of common (into syncserver) /// TokenserverError out of common (into syncserver)
@ -107,7 +107,7 @@ impl TokenserverError {
pub fn invalid_client_state( pub fn invalid_client_state(
description: String, description: String,
tags: Option<Box<Vec<(&'static str, String)>>>, tags: Option<Vec<(&'static str, String)>>,
) -> Self { ) -> Self {
Self { Self {
status: "invalid-client-state", status: "invalid-client-state",
@ -147,7 +147,15 @@ impl TokenserverError {
description: "Resource is not available".to_owned(), description: "Resource is not available".to_owned(),
http_status: StatusCode::SERVICE_UNAVAILABLE, http_status: StatusCode::SERVICE_UNAVAILABLE,
context: "Resource is not available".to_owned(), context: "Resource is not available".to_owned(),
..Default::default() ..Self::default()
}
}
pub fn oauth_timeout() -> Self {
Self {
context: "OAuth verification timeout".to_owned(),
tags: Some(vec![("reason", "oauth_verify_timeout".to_owned())]),
..Self::resource_unavailable()
} }
} }
@ -284,6 +292,9 @@ impl ReportableError for TokenserverError {
if let Some(source) = &self.source { if let Some(source) = &self.source {
return source.is_sentry_event(); return source.is_sentry_event();
} }
if self.http_status == StatusCode::SERVICE_UNAVAILABLE {
return false;
}
self.http_status.is_server_error() && self.metric_label().is_none() self.http_status.is_server_error() && self.metric_label().is_none()
} }
@ -291,25 +302,18 @@ impl ReportableError for TokenserverError {
if let Some(source) = &self.source { if let Some(source) = &self.source {
return source.metric_label(); return source.metric_label();
} }
if self.http_status == StatusCode::SERVICE_UNAVAILABLE {
return Some("request.error.resource_unavailable");
}
if self.http_status.is_client_error() { if self.http_status.is_client_error() {
return Some("request.error.oauth"); return Some("request.error.oauth");
} }
(self.status == "invalid-client-state").then_some("request.error.invalid_client_state")
if matches!(
self,
TokenserverError {
status: "invalid-client-state",
..
}
) {
Some("request.error.invalid_client_state")
} else {
None
}
} }
fn tags(&self) -> Vec<(&str, String)> { fn tags(&self) -> Vec<(&str, String)> {
*self.tags.clone().unwrap_or_default() self.tags.clone().unwrap_or_default()
} }
} }