Skip to content

Commit

Permalink
feat: emit internal bb8 Pool errors to logs/sentry
Browse files Browse the repository at this point in the history
- add a keepalive setting
- fix: don't urldecode bso_ids from JSON
- pass the user-agent to sentry as an extra

Closes #786
Closes #785
Closes #764
Closes #787
  • Loading branch information
pjenvey committed Aug 18, 2020
1 parent af5234d commit ec25bc4
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 56 deletions.
21 changes: 19 additions & 2 deletions src/db/spanner/pool.rs
@@ -1,5 +1,5 @@
use async_trait::async_trait;
use bb8::Pool;
use bb8::{ErrorSink, Pool};

use std::{
collections::HashMap,
Expand Down Expand Up @@ -49,7 +49,8 @@ impl SpannerDbPool {
let max_size = settings.database_pool_max_size.unwrap_or(10);
let builder = bb8::Pool::builder()
.max_size(max_size)
.min_idle(settings.database_pool_min_idle);
.min_idle(settings.database_pool_min_idle)
.error_sink(Box::new(LoggingErrorSink));

Ok(Self {
pool: builder.build(manager).await?,
Expand Down Expand Up @@ -164,3 +165,19 @@ impl Default for CollectionCache {
}
}
}

/// Logs internal bb8 errors
#[derive(Debug, Clone, Copy)]
pub struct LoggingErrorSink;

impl<E: failure::Fail> ErrorSink<E> for LoggingErrorSink {
fn sink(&self, e: E) {
error!("bb8 Error: {}", e);
let event = sentry::integrations::failure::event_from_fail(&e);
sentry::capture_event(event);
}

fn boxed_clone(&self) -> Box<dyn ErrorSink<E>> {
Box::new(*self)
}
}
7 changes: 1 addition & 6 deletions src/error.rs
Expand Up @@ -76,9 +76,6 @@ pub enum ApiErrorKind {

#[fail(display = "{}", _0)]
Validation(#[cause] ValidationError),

#[fail(display = "Invalid Submission: {}", _0)]
InvalidSubmission(String),
}

impl ApiError {
Expand Down Expand Up @@ -221,7 +218,6 @@ impl From<Context<ApiErrorKind>> for ApiError {
StatusCode::INTERNAL_SERVER_ERROR
}
ApiErrorKind::Validation(error) => error.status,
ApiErrorKind::InvalidSubmission(_) => StatusCode::BAD_REQUEST,
};

Self { inner, status }
Expand Down Expand Up @@ -275,8 +271,7 @@ impl Serialize for ApiErrorKind {
match *self {
ApiErrorKind::Db(ref error) => serialize_string_to_array(serializer, error),
ApiErrorKind::Hawk(ref error) => serialize_string_to_array(serializer, error),
ApiErrorKind::Internal(ref description)
| ApiErrorKind::InvalidSubmission(ref description) => {
ApiErrorKind::Internal(ref description) => {
serialize_string_to_array(serializer, description)
}
ApiErrorKind::Validation(ref error) => Serialize::serialize(error, serializer),
Expand Down
14 changes: 9 additions & 5 deletions src/server/mod.rs
Expand Up @@ -165,7 +165,7 @@ impl Server {

spawn_pool_periodic_reporter(Duration::from_secs(10), metrics.clone(), db_pool.clone())?;

let server = HttpServer::new(move || {
let mut server = HttpServer::new(move || {
// Setup the server state
let state = ServerState {
db_pool: db_pool.clone(),
Expand All @@ -177,10 +177,14 @@ impl Server {
};

build_app!(state, limits)
})
.bind(format!("{}:{}", settings.host, settings.port))
.expect("Could not get Server in Server::with_settings")
.run();
});
if let Some(keep_alive) = settings.actix_keep_alive {
server = server.keep_alive(keep_alive as usize);
}
let server = server
.bind(format!("{}:{}", settings.host, settings.port))
.expect("Could not get Server in Server::with_settings")
.run();
Ok(server)
}
}
3 changes: 3 additions & 0 deletions src/settings.rs
Expand Up @@ -32,6 +32,8 @@ pub struct Settings {
#[cfg(test)]
pub database_use_test_transactions: bool,

pub actix_keep_alive: Option<u32>,

/// Server-enforced limits for request payloads.
pub limits: ServerLimits,

Expand All @@ -57,6 +59,7 @@ impl Default for Settings {
database_pool_min_idle: None,
#[cfg(test)]
database_use_test_transactions: false,
actix_keep_alive: None,
limits: ServerLimits::default(),
master_secret: Secrets::default(),
statsd_host: None,
Expand Down
46 changes: 11 additions & 35 deletions src/web/extractors.rs
Expand Up @@ -66,19 +66,11 @@ pub struct UidParam {
uid: u64,
}

fn clean_entry(s: &str) -> Result<String, ApiError> {
// URL decode and check that the string is all ascii.
let decoded: String = match urlencoding::decode(s) {
Ok(v) => v,
Err(e) => {
debug!("unclean entry: {:?} {:?}", s, e);
return Err(ApiErrorKind::Internal(e.to_string()).into());
}
};
if !decoded.is_ascii() {
debug!("unclean entry, non-ascii value in {:?}", decoded);
return Err(ApiErrorKind::InvalidSubmission("invalid value".into()).into());
};
fn urldecode(s: &str) -> Result<String, ApiError> {
let decoded: String = urlencoding::decode(s).map_err(|e| {
debug!("unclean entry: {:?} {:?}", s, e);
ApiErrorKind::Internal(e.to_string())
})?;
Ok(decoded)
}

Expand Down Expand Up @@ -316,20 +308,7 @@ impl FromRequest for BsoBodies {
}
// Save all id's we get, check for missing id, or duplicate.
let bso_id = if let Some(id) = bso.get("id").and_then(serde_json::Value::as_str) {
let id = match clean_entry(&id.to_string()) {
Ok(v) => v,
Err(_) => {
return future::err(
ValidationErrorKind::FromDetails(
"Invalid BSO ID".to_owned(),
RequestErrorLocation::Body,
Some("bsos".to_owned()),
None,
)
.into(),
);
}
};
let id = id.to_string();
if bso_ids.contains(&id) {
return future::err(
ValidationErrorKind::FromDetails(
Expand Down Expand Up @@ -532,13 +511,12 @@ pub struct BsoParam {
}

impl BsoParam {
pub fn bsoparam_from_path(uri: &Uri, tags: &Tags) -> Result<Self, Error> {
fn bsoparam_from_path(uri: &Uri, tags: &Tags) -> Result<Self, Error> {
// TODO: replace with proper path parser
// path: "/1.5/{uid}/storage/{collection}/{bso}"
let elements: Vec<&str> = uri.path().split('/').collect();
let elem = elements.get(3);
if elem.is_none() || elem != Some(&"storage") || elements.len() != 6 {
warn!("⚠️ Unexpected BSO URI: {:?}", uri.path(); tags);
return Err(ValidationErrorKind::FromDetails(
"Invalid BSO".to_owned(),
RequestErrorLocation::Path,
Expand All @@ -547,7 +525,7 @@ impl BsoParam {
))?;
}
if let Some(v) = elements.get(5) {
let sv = clean_entry(&String::from_str(v).map_err(|e| {
let sv = urldecode(&String::from_str(v).map_err(|e| {
warn!("⚠️ Invalid BsoParam Error: {:?} {:?}", v, e; tags);
ValidationErrorKind::FromDetails(
"Invalid BSO".to_owned(),
Expand Down Expand Up @@ -631,7 +609,7 @@ impl CollectionParam {
Some(tags.clone()),
)
})?;
sv = clean_entry(&sv).map_err(|_e| {
sv = urldecode(&sv).map_err(|_e| {
ValidationErrorKind::FromDetails(
"Invalid Collection".to_owned(),
RequestErrorLocation::Path,
Expand Down Expand Up @@ -1109,7 +1087,7 @@ impl HawkIdentifier {
// path: "/1.5/{uid}"
let elements: Vec<&str> = uri.path().split('/').collect();
if let Some(v) = elements.get(2) {
let clean = match clean_entry(v) {
let clean = match urldecode(v) {
Err(e) => {
warn!("⚠️ HawkIdentifier Error invalid UID {:?} {:?}", v, e);
return Err(ValidationErrorKind::FromDetails(
Expand Down Expand Up @@ -1709,9 +1687,7 @@ fn validate_body_bso_sortindex(sort: i32) -> Result<(), ValidationError> {

/// Verifies the BSO id string is valid
fn validate_body_bso_id(id: &str) -> Result<(), ValidationError> {
let clean =
clean_entry(id).map_err(|_| request_error("Invalid id", RequestErrorLocation::Body))?;
if !VALID_ID_REGEX.is_match(&clean) {
if !VALID_ID_REGEX.is_match(id) {
return Err(request_error("Invalid id", RequestErrorLocation::Body));
}
Ok(())
Expand Down
3 changes: 0 additions & 3 deletions src/web/middleware/sentry.rs
Expand Up @@ -100,7 +100,6 @@ where

fn call(&mut self, sreq: ServiceRequest) -> Self::Future {
let mut tags = Tags::from_request_head(sreq.head());
let uri = sreq.head().uri.to_string();
sreq.extensions_mut().insert(tags.clone());

Box::pin(self.service.call(sreq).and_then(move |mut sresp| {
Expand All @@ -119,8 +118,6 @@ where
tags.tags.insert(k, v);
}
};
// add the uri.path (which can cause influx to puke)
tags.extra.insert("uri.path".to_owned(), uri);
match sresp.response().error() {
None => {
// Middleware errors are eaten by current versions of Actix. Errors are now added
Expand Down
11 changes: 6 additions & 5 deletions src/web/tags.rs
Expand Up @@ -57,6 +57,7 @@ impl Tags {
// Return an Option<> type because the later consumers (ApiErrors) presume that
// tags are optional and wrapped by an Option<> type.
let mut tags = HashMap::new();
let mut extra = HashMap::new();
if let Some(ua) = req_head.headers().get(USER_AGENT) {
if let Ok(uas) = ua.to_str() {
let (ua_result, metrics_os, metrics_browser) = parse_user_agent(uas);
Expand All @@ -65,14 +66,14 @@ impl Tags {
insert_if_not_empty("ua.name", ua_result.name, &mut tags);
insert_if_not_empty("ua.os.ver", &ua_result.os_version.to_owned(), &mut tags);
insert_if_not_empty("ua.browser.ver", ua_result.version, &mut tags);
extra.insert("ua".to_owned(), uas.to_string());
}
}
// `uri.path` causes too much cardinality for influx.
tags.insert("uri.method".to_owned(), req_head.method.to_string());
Tags {
tags,
extra: HashMap::new(),
}
// `uri.path` causes too much cardinality for influx but keep it in
// extra for sentry
extra.insert("uri.path".to_owned(), req_head.uri.to_string());
Tags { tags, extra }
}

pub fn with_tags(tags: HashMap<String, String>) -> Tags {
Expand Down

0 comments on commit ec25bc4

Please sign in to comment.