Skip to content

Commit 66d9d11

Browse files
chenbapjenvey
andauthored
feat: log when Sentry is not configured (#2073)
* feat: log when Sentry is not configured * Update syncserver-common/src/middleware/sentry.rs Co-authored-by: Philip Jenvey <pjenvey@underboss.org> * warn and optionally print backtrace * Use error! * panic! * Cleanup and comment * Cargo.lock --------- Co-authored-by: Philip Jenvey <pjenvey@underboss.org>
1 parent 35b0a9d commit 66d9d11

7 files changed

Lines changed: 157 additions & 28 deletions

File tree

Cargo.lock

Lines changed: 22 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ slog-mozlog-json = "0.1"
8989
slog-scope = "4.4"
9090
slog-stdlog = "4.1"
9191
slog-term = "2.9"
92+
temp-env = { version = "0.3", features = ["async_closure"] }
9293
tokio = "1"
9394
thiserror = "1.0.69"
9495
time = "0.3.47"

syncserver-common/src/middleware/sentry.rs

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{cell::RefCell, collections::BTreeMap, marker::PhantomData, rc::Rc, sync::Arc};
1+
use std::{cell::RefCell, collections::BTreeMap, env, marker::PhantomData, rc::Rc, sync::Arc};
22

33
use actix_web::{
44
Error,
@@ -15,13 +15,20 @@ use crate::{ReportableError, Taggable};
1515
pub struct SentryWrapper<E> {
1616
metrics: Arc<StatsdClient>,
1717
phantom: PhantomData<E>,
18+
sentry_enabled: bool,
19+
backtrace_enabled: bool,
1820
}
1921

2022
impl<E> SentryWrapper<E> {
2123
pub fn new(metrics: Arc<StatsdClient>) -> Self {
24+
let sentry_enabled = env::var("SENTRY_DSN").is_ok_and(|dsn| !dsn.is_empty());
25+
let backtrace_enabled = env::var("RUST_BACKTRACE").is_ok_and(|v| v == "1" || v == "full");
26+
2227
Self {
2328
metrics,
2429
phantom: PhantomData,
30+
sentry_enabled,
31+
backtrace_enabled,
2532
}
2633
}
2734
}
@@ -43,6 +50,8 @@ where
4350
service: Rc::new(RefCell::new(service)),
4451
metrics: self.metrics.clone(),
4552
phantom: PhantomData,
53+
sentry_enabled: self.sentry_enabled,
54+
backtrace_enabled: self.backtrace_enabled,
4655
})
4756
}
4857
}
@@ -52,6 +61,8 @@ pub struct SentryWrapperMiddleware<S, E> {
5261
service: Rc<RefCell<S>>,
5362
metrics: Arc<StatsdClient>,
5463
phantom: PhantomData<E>,
64+
sentry_enabled: bool,
65+
backtrace_enabled: bool,
5566
}
5667

5768
impl<S, B, E> Service<ServiceRequest> for SentryWrapperMiddleware<S, E>
@@ -77,6 +88,8 @@ where
7788

7889
// get the tag information
7990
let metrics = self.metrics.clone();
91+
let sentry_enabled = self.sentry_enabled;
92+
let backtrace_enabled = self.backtrace_enabled;
8093
let tags = sreq.get_tags();
8194
let extras = sreq.get_extras();
8295

@@ -99,12 +112,19 @@ where
99112
}
100113
};
101114
debug!("Reporting error to Sentry (service error): {}", error);
115+
102116
let mut event = event_from_actix_error::<E>(&error);
103117
// Add in the tags from the request
104118
event.tags.extend(tags);
105119
event.extra.extend(extras);
106-
let event_id = hub.capture_event(event);
107-
trace!("event_id = {}", event_id);
120+
121+
if sentry_enabled {
122+
let event_id = hub.capture_event(event);
123+
trace!("event_id = {}", event_id);
124+
} else {
125+
// log if Sentry is not configured
126+
log_event::<E>(&event, &error, backtrace_enabled);
127+
}
108128
return Err(error);
109129
}
110130
};
@@ -118,9 +138,16 @@ where
118138
return Ok(response);
119139
}
120140
debug!("Reporting error to Sentry (response error): {}", error);
141+
121142
let event = event_from_actix_error::<E>(error);
122-
let event_id = hub.capture_event(event);
123-
trace!("event_id = {}", event_id);
143+
144+
if sentry_enabled {
145+
let event_id = hub.capture_event(event);
146+
trace!("event_id = {}", event_id);
147+
} else {
148+
// log if Sentry is not configured
149+
log_event::<E>(&event, error, backtrace_enabled);
150+
}
124151
}
125152
Ok(response)
126153
}
@@ -266,3 +293,42 @@ pub fn exception_from_reportable_error(err: &dyn ReportableError) -> sentry::pro
266293
..Default::default()
267294
}
268295
}
296+
297+
/// Log the error event instead when Sentry is not configured.
298+
fn log_event<E>(
299+
event: &sentry::protocol::Event<'static>,
300+
error: &actix_web::Error,
301+
backtrace_enabled: bool,
302+
) where
303+
E: ReportableError + actix_web::ResponseError + 'static,
304+
{
305+
let first_exception = event.exception.last();
306+
let error_type = first_exception.map_or("UnknownError", |e| e.ty.as_str());
307+
let error_value = first_exception
308+
.and_then(|e| e.value.as_deref())
309+
.unwrap_or("No error message");
310+
311+
let backtrace = if backtrace_enabled {
312+
error
313+
.as_error::<E>()
314+
.and_then(|e| e.backtrace())
315+
.map(|bt| format!("\n{bt:?}"))
316+
.unwrap_or_default()
317+
} else {
318+
String::new()
319+
};
320+
321+
error!(
322+
"{}{}", error_value, backtrace;
323+
"error_type" => error_type,
324+
"tags" => ?event.tags,
325+
"extra" => ?event.extra,
326+
"url" => event.request.as_ref()
327+
.and_then(|r| r.url.as_ref())
328+
.map(|u| u.to_string())
329+
.unwrap_or_default(),
330+
"method" => event.request.as_ref()
331+
.and_then(|r| r.method.as_deref())
332+
.unwrap_or(""),
333+
);
334+
}

syncserver-settings/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ tokenserver-settings = { path = "../tokenserver-settings" }
1717
url = "2.5"
1818

1919
[dev-dependencies]
20-
temp-env = "0.3"
20+
temp-env.workspace = true

syncserver/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ validator = "0.20"
5959
validator_derive = "0.20"
6060
woothee = "0.13"
6161

62+
[dev-dependencies]
63+
temp-env.workspace = true
64+
6265
[target.'cfg(target_os = "linux")'.dependencies]
6366
slog-journald = "2.2.0"
6467

syncserver/src/server/test.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use std::collections::HashMap;
2-
use std::str::FromStr;
1+
use std::{collections::HashMap, env, str::FromStr};
32

43
use actix_web::{
54
dev::Service,
@@ -841,3 +840,56 @@ async fn lbheartbeat_ttl_check() {
841840
let sresp = app.call(lb_req).await.unwrap();
842841
assert_eq!(sresp.status(), StatusCode::INTERNAL_SERVER_ERROR);
843842
}
843+
844+
#[actix_rt::test]
845+
async fn error_endpoint_logging_check() {
846+
use slog::Drain as _;
847+
use std::io::Write;
848+
use std::sync::{Arc, Mutex};
849+
850+
temp_env::async_with_vars(
851+
[("SENTRY_DSN", None::<&str>), ("RUST_BACKTRACE", Some("1"))],
852+
async {
853+
let app = init_app!().await;
854+
855+
struct CaptureDrain(Arc<Mutex<Vec<u8>>>);
856+
impl slog::Drain for CaptureDrain {
857+
type Ok = ();
858+
type Err = std::io::Error;
859+
860+
fn log(
861+
&self,
862+
record: &slog::Record<'_>,
863+
_values: &slog::OwnedKVList,
864+
) -> Result<Self::Ok, Self::Err> {
865+
let mut buf = self.0.lock().unwrap();
866+
writeln!(buf, "{}", record.msg())?;
867+
Ok(())
868+
}
869+
}
870+
871+
let buffer = Arc::new(Mutex::new(Vec::new()));
872+
let drain = CaptureDrain(buffer.clone()).fuse();
873+
let logger = slog::Logger::root(drain, slog::o!());
874+
let _guard = slog_scope::set_global_logger(logger);
875+
876+
let req = test::TestRequest::get().uri("/__error__").to_request();
877+
let resp = test::call_service(&app, req).await;
878+
879+
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
880+
881+
let log_msg = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
882+
883+
assert!(
884+
log_msg.contains("Oh Noes!"),
885+
"Unexpected error message: {log_msg}",
886+
);
887+
// Look for the fn name in the backtrace.
888+
assert!(
889+
log_msg.contains("test_error"),
890+
"Unexpected error message: {log_msg}",
891+
);
892+
},
893+
)
894+
.await;
895+
}

syncserver/src/web/handlers.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! API Handlers
22
use std::collections::HashMap;
33
use std::convert::Into;
4+
use std::thread;
45
use std::time::{Duration, Instant};
56

67
use crate::server::user_agent::{DeviceInfo, get_device_info};
@@ -908,5 +909,9 @@ pub async fn test_error(
908909
error!("Test Error");
909910
let err = ApiError::from(ApiErrorKind::Internal("Oh Noes!".to_owned()));
910911

912+
thread::spawn(|| {
913+
panic!("TestError");
914+
});
915+
911916
Err(err)
912917
}

0 commit comments

Comments
 (0)