Repository navigation
feat(attachments): report unserved attachments and audit them hourly - #79
gmaclennan wants to merge 3 commits into
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
gmaclennan
left a comment
There was a problem hiding this comment.
Let's separate this into 2 PRs: one for the audit endpoint, and the other for reporting missing attachments to Sentry. I'm not sure about the automated audit being sent to Sentry: servers can sit without sync for several days, so this seems like a lot of unnecessary noise and processing. Maybe open a 3rd draft PR for sentry audits, but perhaps rather than hourly, send after a sync, sent at most once per hour, and only if there is something missing.
|
Split as asked, stacked in order:
Three things I had to decide in #90 that you may want to change: it only runs when This PR can be closed once you are happy with the three. |
Today the only sign that an attachment is partly synced to the cloud is a partner's connector failing downstream: the server logs to stdout and nothing else, so a 404, a 500 with
UNKNOWN_ERRORor a 502 from the attachments route goes unnoticed until someone complains.This adds optional Sentry reporting, on only when
SENTRY_DSNis set (@sentry/nodeis imported on demand, so a deployment without a DSN does not pay for it). The attachments route insrc/routes.jsnow sends oneattachment.nonokevent for every response it could not serve as a 200, tagged with the project, the first seven characters of the drive id, type, variant, the HTTP status, a coarse class (missingfor an upstream 404,incompletewhen core resets or truncates,errorotherwise) and core's own error code where it sent one; the fetch cause (UND_ERR_SOCKETand the like) goes in the event detail and the pino line. The server also runs an integrity audit:src/attachments-audit.jslists a project's observations, fetches every variant of every photo and audio attachment from core's blob server, reads each to the end and counts ok / missing / incomplete / error. It is exposed as an authenticatedGET /projects/:projectPublicId/attachments/auditand, when Sentry is on, runs once shortly after start and then everyAUDIT_INTERVAL_MS(default one hour,0disables), capped atAUDIT_MAX_ATTACHMENTSnewest attachments per project, sending oneattachment.auditevent per project with the counts in the event context. Each failure names the device that created the observation (createdBy), since that is the phone that has to sync again, and the result carries adevicesmap from those device ids to the names the project knows them by; both go to Sentry with the counts. The capture function is injected through the server options, so tests run without a DSN. Sentry's unhandled-rejection integration is set to strict so a DSN does not stop the process crashing the way it does today.Scope and limits. Sentry is initialised after fastify and core are imported and its ESM loader hook is turned off, so there is no tracing or auto-instrumentation: only crashes, unhandled rejections and the two attachment events above are reported, and a 5xx that fastify turns into a response on another route is not. The dependency is
@sentry/node10.x rather than 11.x because 11 requires node 20.19 and the Dockerfile pins 20.17.0. And the audit reads every variant in full, so the default cap of 200 attachments is a guess at what a small VPS can comfortably read once an hour; once core exposes a blob status API the audit can stop downloading bytes.Related changes. After digidem/comapeo-core#1350, a partial blob reaches this route as an upstream 404 with code BLOB_NOT_AVAILABLE_ERROR rather than a reset or truncation. The event already carries core's error code, and the audit's missing versus incomplete split should then key on that code rather than on how the body failed. The hourly audit stays useful either way, since it is the only server-side signal that media is missing.