Skip to content

Commit 59f6926

Browse files
jan-auerlcian
authored andcommitted
fix(log): Capture service errors only once (#558)
For every service error, we received two issues in Sentry: 1. Captured within the service by the spawned task itself 2. Captured by the web middleware when unwinding the error Since tasks can outlive their requests, we need to keep capturing inside the service. Consequently, we now skip error tracking for service error kinds in the API layer. There are a few exceptions where a service error is constructed outside of the spawned task, which is now tracked explicitly.
1 parent 0e58c5b commit 59f6926

3 files changed

Lines changed: 30 additions & 12 deletions

File tree

objectstore-server/src/endpoints/batch.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ async fn got_to_part(
232232
.meter_stream(stream, context)
233233
.try_collect::<BytesMut>()
234234
.await
235-
.map_err(|e| ApiError::Service(e.into()))?
235+
.map_err(|e| {
236+
objectstore_log::error!(!!&e, "failed to collect payload stream");
237+
ApiError::Service(e.into())
238+
})?
236239
.freeze();
237240

238241
let mut metadata_headers = metadata.to_headers("").map_err(|err| {
@@ -317,6 +320,9 @@ fn create_success_part(
317320
}
318321

319322
fn create_error_part(idx: usize, error: &ApiError) -> Part {
323+
// Capture the error explicitly, as it is not converted via `IntoResponse` here.
324+
error.capture();
325+
320326
let mut headers = HeaderMap::new();
321327
insert_index_header(&mut headers, idx);
322328
insert_status_header(&mut headers, error.status());

objectstore-server/src/endpoints/common.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ impl ApiError {
8080
ApiError::Batch(BatchError::LimitExceeded(_)) => StatusCode::PAYLOAD_TOO_LARGE,
8181
ApiError::Batch(BatchError::RateLimited) => StatusCode::TOO_MANY_REQUESTS,
8282
ApiError::Batch(BatchError::ResponseSerialization { .. }) => {
83-
objectstore_log::error!(!!self, "error serializing batch response");
8483
StatusCode::INTERNAL_SERVER_ERROR
8584
}
8685

@@ -90,10 +89,7 @@ impl ApiError {
9089
ApiError::Auth(AuthError::UnknownKey) => StatusCode::UNAUTHORIZED,
9190
ApiError::Auth(AuthError::UnsupportedPresignedMethod) => StatusCode::FORBIDDEN,
9291
ApiError::Auth(AuthError::NotPermitted) => StatusCode::FORBIDDEN,
93-
ApiError::Auth(AuthError::InternalError(_)) => {
94-
objectstore_log::error!(!!self, "auth system error");
95-
StatusCode::INTERNAL_SERVER_ERROR
96-
}
92+
ApiError::Auth(AuthError::InternalError(_)) => StatusCode::INTERNAL_SERVER_ERROR,
9793

9894
ApiError::Service(error) => match error.kind() {
9995
ServiceErrorKind::ClientStream | ServiceErrorKind::InvalidInput => {
@@ -106,21 +102,31 @@ impl ApiError {
106102
| ServiceErrorKind::BackendUnavailable => StatusCode::SERVICE_UNAVAILABLE,
107103
ServiceErrorKind::NotImplemented => StatusCode::NOT_IMPLEMENTED,
108104
ServiceErrorKind::TaskPanic | ServiceErrorKind::Internal => {
109-
objectstore_log::error!(!!self, "error handling request");
110105
StatusCode::INTERNAL_SERVER_ERROR
111106
}
112107
},
108+
}
109+
}
113110

114-
ApiError::Internal(_) => {
115-
objectstore_log::error!(!!self, "internal error");
116-
StatusCode::INTERNAL_SERVER_ERROR
117-
}
111+
/// Reports this error to error tracking if it indicates a server fault (5xx status).
112+
///
113+
/// Call this exactly once wherever an `ApiError` is serialized into a client-visible
114+
/// response: standalone responses ([`IntoResponse`]) and batch response parts.
115+
pub fn capture(&self) {
116+
// Captured at the source in the service layer to prevent double-logging.
117+
if matches!(self, ApiError::Service(_)) {
118+
return;
119+
}
120+
121+
if self.status().is_server_error() {
122+
objectstore_log::error!(!!self, "error handling request");
118123
}
119124
}
120125
}
121126

122127
impl IntoResponse for ApiError {
123128
fn into_response(self) -> Response {
129+
self.capture();
124130
let body = ApiErrorResponse::from_error(&self);
125131
(self.status(), Json(body)).into_response()
126132
}

objectstore-service/src/concurrency.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,13 @@ where
194194
.bind_hub(new_hub),
195195
);
196196
rx.await
197-
.map_err(|_| Error::new(ErrorKind::Internal).context("task dropped"))?
197+
.map_err(|_|
198+
199+
rx.await.map_err(|_| {
200+
let err = Error::new(ErrorKind::Internal).context("task dropped");
201+
objectstore_log::error!(!!err, operation, "Task failed");
202+
err
203+
})?
198204
}
199205

200206
#[cfg(test)]

0 commit comments

Comments
 (0)