Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Commit

Permalink
feat: optionally include location info in /__error__
Browse files Browse the repository at this point in the history
showing up as a sentry event extra (for debugging purposes)

Closes #192
  • Loading branch information
pjenvey committed Jun 24, 2021
1 parent 11ff5ec commit 97aa2f3
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 16 deletions.
39 changes: 28 additions & 11 deletions src/server/location.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
//! Resolve a given IP into a stripped location
//!
//! This uses the MaxMindDB geoip2-City database.
use std::collections::BTreeMap;
use std::net::IpAddr;
use std::sync::Arc;
use std::{collections::BTreeMap, fmt, net::IpAddr, sync::Arc};

use actix_http::http::HeaderValue;
use actix_http::RequestHead;
use actix_http::{http::HeaderValue, RequestHead};
use config::ConfigError;
use maxminddb::{self, geoip2::City, MaxMindDBError};
use serde::{self, Serialize};
Expand All @@ -20,6 +17,20 @@ use crate::{

const GOOG_LOC_HEADER: &str = "x-client-geo-location";

#[derive(Serialize, Debug, Clone, Copy)]
pub enum Provider {
MaxMind,
LoadBalancer,
Fallback,
}

impl fmt::Display for Provider {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = format!("{:?}", self).to_lowercase();
write!(fmt, "{}", name)
}
}

/// The returned, stripped location.
#[derive(Serialize, Debug, Clone)]
pub struct LocationResult {
Expand All @@ -35,6 +46,7 @@ pub struct LocationResult {
/// Not currently used
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
pub provider: Provider,
}

impl From<&Settings> for LocationResult {
Expand All @@ -44,6 +56,7 @@ impl From<&Settings> for LocationResult {
subdivision: None,
country: Some(settings.fallback_country.clone()),
dma: None,
provider: Provider::Fallback,
}
}
}
Expand All @@ -68,11 +81,13 @@ impl LocationResult {

/// Read a [HeaderValue] to see if there's anything we can use to derive the location
fn from_headervalue(header: &HeaderValue, settings: &Settings, metrics: &Metrics) -> Self {
let provider = Provider::LoadBalancer;
let mut tags = Tags::default();
tags.add_tag("provider", "lb");
tags.add_tag("provider", &provider.to_string());

let loc_string = header.to_str().unwrap_or("");
let mut loc = Self::from(settings);
loc.provider = provider;
let mut parts = loc_string.split(',');
if let Some(country) = parts.next().map(|country| country.trim().to_owned()) {
if !country.is_empty() {
Expand Down Expand Up @@ -123,7 +138,7 @@ impl LocationResult {
pub struct Location {
iploc: Option<Arc<maxminddb::Reader<Vec<u8>>>>,
test_header: Option<String>,
default_loc: LocationResult,
fallback_loc: LocationResult,
}

/// Process and convert the MaxMindDB errors into native [crate::error::HandlerError]s
Expand Down Expand Up @@ -163,7 +178,7 @@ impl From<&Settings> for Location {
Self {
iploc: settings.into(),
test_header: settings.location_test_header.clone(),
default_loc: LocationResult::from(settings),
fallback_loc: LocationResult::from(settings),
}
}
}
Expand Down Expand Up @@ -271,8 +286,9 @@ impl Location {
if self.iploc.is_none() {
return Ok(None);
}
let provider = Provider::MaxMind;
let mut tags = Tags::default();
tags.add_tag("provider", "maxmind");
tags.add_tag("provider", &provider.to_string());

/*
The structure of the returned maxminddb free record is:
Expand Down Expand Up @@ -313,7 +329,8 @@ impl Location {
traits: None }
}
*/
let mut result = self.default_loc.clone();
let mut result = self.fallback_loc.clone();
result.provider = provider;
match self.iploc.clone().unwrap().lookup::<City<'_>>(ip_addr) {
Ok(location) => {
if let Some(names) = location.city.and_then(|c| c.names) {
Expand Down Expand Up @@ -465,7 +482,7 @@ pub mod test {
}

#[actix_rt::test]
async fn test_default_loc() -> HandlerResult<()> {
async fn test_fallback_loc() -> HandlerResult<()> {
// From a bad setting
let mut settings = Settings {
fallback_country: "USA".to_owned(),
Expand Down
31 changes: 26 additions & 5 deletions src/web/dockerflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
use std::collections::HashMap;

use actix_web::{web, HttpResponse};
use actix_web::{dev::Payload, web, FromRequest, HttpRequest, HttpResponse};
use serde::Deserialize;
use serde_json::Value;

use crate::error::HandlerError;
use crate::server::ServerState;
use crate::{
error::HandlerError,
server::{location::LocationResult, ServerState},
};

/// Well Known DockerFlow commands for Ops callbacks
pub const DOCKER_FLOW_ENDPOINTS: [&str; 4] = [
Expand Down Expand Up @@ -57,11 +60,29 @@ fn heartbeat() -> HttpResponse {
HttpResponse::Ok().json(checklist)
}

#[derive(Debug, Deserialize)]
pub struct ErrorParams {
pub with_location: Option<bool>,
}

/// Returning an API error to test error handling
async fn test_error() -> Result<HttpResponse, HandlerError> {
///
/// Optionally including location lookup information.
async fn test_error(
req: HttpRequest,
params: web::Query<ErrorParams>,
) -> Result<HttpResponse, HandlerError> {
// generate an error for sentry.
error!("Test Error");
Err(HandlerError::internal("Oh Noes!"))
let mut err = HandlerError::internal("Oh Noes!");
if matches!(params.with_location, Some(true)) {
let location_info = match LocationResult::from_request(&req, &mut Payload::None).await {
Ok(location) => format!("{:#?}", location),
Err(loce) => loce.to_string(),
};
err.tags.add_extra("location", &location_info);
}
Err(err)
}

async fn document_boot(state: web::Data<ServerState>) -> Result<HttpResponse, HandlerError> {
Expand Down

0 comments on commit 97aa2f3

Please sign in to comment.