Log 500-return paths in PATCH /image/faces/{id}
The four 500-return paths in update_face_handler returned e.to_string()
in the body but never logged. When a face PATCH failed with a 16-byte
body and no log entry, the cause (SQLITE_BUSY from cross-DAO writer
contention exhausting the 5s busy_timeout) was invisible. Surface the
full anyhow chain via {:#} on each path so the diesel cause is in the
log even when the response body only shows the top-level context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
26
src/faces.rs
26
src/faces.rs
@@ -2018,12 +2018,19 @@ async fn update_face_handler<D: FaceDao>(
|
|||||||
match dao.get_face(&span_context, id) {
|
match dao.get_face(&span_context, id) {
|
||||||
Ok(Some(r)) => r,
|
Ok(Some(r)) => r,
|
||||||
Ok(None) => return HttpResponse::NotFound().finish(),
|
Ok(None) => return HttpResponse::NotFound().finish(),
|
||||||
Err(e) => return HttpResponse::InternalServerError().body(e.to_string()),
|
Err(e) => {
|
||||||
|
warn!("PATCH /image/faces/{}: 500 — get_face failed: {:#}", id, e);
|
||||||
|
return HttpResponse::InternalServerError().body(e.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let library = match app_state.library_by_id(current.library_id) {
|
let library = match app_state.library_by_id(current.library_id) {
|
||||||
Some(l) => l.clone(),
|
Some(l) => l.clone(),
|
||||||
None => {
|
None => {
|
||||||
|
warn!(
|
||||||
|
"PATCH /image/faces/{}: 500 — face row references unknown library_id {}",
|
||||||
|
id, current.library_id
|
||||||
|
);
|
||||||
return HttpResponse::InternalServerError().body(format!(
|
return HttpResponse::InternalServerError().body(format!(
|
||||||
"face row references unknown library_id {}",
|
"face row references unknown library_id {}",
|
||||||
current.library_id
|
current.library_id
|
||||||
@@ -2106,7 +2113,14 @@ async fn update_face_handler<D: FaceDao>(
|
|||||||
let mut dao = face_dao.lock().expect("face dao lock");
|
let mut dao = face_dao.lock().expect("face dao lock");
|
||||||
let row = match dao.update_face(&span_context, id, person_patch, bbox_patch, new_embedding) {
|
let row = match dao.update_face(&span_context, id, person_patch, bbox_patch, new_embedding) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => return HttpResponse::InternalServerError().body(e.to_string()),
|
Err(e) => {
|
||||||
|
// The full anyhow chain (`{:#}`) shows the diesel cause behind
|
||||||
|
// the short context string we surface in the response body —
|
||||||
|
// SQLITE_BUSY here usually means another DAO's writer held the
|
||||||
|
// lock past `busy_timeout` (5s), which is invisible in `{}`.
|
||||||
|
warn!("PATCH /image/faces/{}: 500 — update_face failed: {:#}", id, e);
|
||||||
|
return HttpResponse::InternalServerError().body(e.to_string());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Hydrate person_name so the response shape matches GET /image/faces
|
// Hydrate person_name so the response shape matches GET /image/faces
|
||||||
// — the carousel overlay does an optimistic replace on this row, and
|
// — the carousel overlay does an optimistic replace on this row, and
|
||||||
@@ -2114,7 +2128,13 @@ async fn update_face_handler<D: FaceDao>(
|
|||||||
// VFD label off the bbox even though the assignment didn't change.
|
// VFD label off the bbox even though the assignment didn't change.
|
||||||
match hydrate_face_with_person(&mut *dao, &span_context, row) {
|
match hydrate_face_with_person(&mut *dao, &span_context, row) {
|
||||||
Ok(joined) => HttpResponse::Ok().json(joined),
|
Ok(joined) => HttpResponse::Ok().json(joined),
|
||||||
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
"PATCH /image/faces/{}: 500 — hydrate_face_with_person failed: {:#}",
|
||||||
|
id, e
|
||||||
|
);
|
||||||
|
HttpResponse::InternalServerError().body(e.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user