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:
Cameron Cordes
2026-05-12 14:37:26 -04:00
parent 6a3e37b7dc
commit 3427c2916c

View File

@@ -2018,12 +2018,19 @@ async fn update_face_handler<D: FaceDao>(
match dao.get_face(&span_context, id) {
Ok(Some(r)) => r,
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) {
Some(l) => l.clone(),
None => {
warn!(
"PATCH /image/faces/{}: 500 — face row references unknown library_id {}",
id, current.library_id
);
return HttpResponse::InternalServerError().body(format!(
"face row references unknown 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 row = match dao.update_face(&span_context, id, person_patch, bbox_patch, new_embedding) {
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
// — 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.
match hydrate_face_with_person(&mut *dao, &span_context, row) {
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())
}
}
}