Add structured error logging to OCI handlers

Every 500 response in the OCI package silently discarded the actual
error, making production debugging impossible. Add slog.Error before
each 500 response with the error and relevant context (repo, digest,
tag, uuid). Add slog.Info for state-mutating successes (manifest push,
blob upload complete, deletions).

Logger is injected into the OCI Handler via constructor, falling back
to slog.Default() if nil.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 12:47:44 -07:00
parent 61b8c2fcef
commit ef39152f4e
15 changed files with 86 additions and 39 deletions

View File

@@ -64,18 +64,21 @@ func (h *Handler) handleUploadInitiate(w http.ResponseWriter, r *http.Request, r
// Create repository if it doesn't exist (implicit creation).
repoID, err := h.db.GetOrCreateRepository(repo)
if err != nil {
h.log.Error("upload initiate: get or create repository", "error", err, "repo", repo)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
uuid, err := generateUUID()
if err != nil {
h.log.Error("upload initiate: generate uuid", "error", err, "repo", repo)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
// Insert upload row in DB.
if err := h.db.CreateUpload(uuid, repoID); err != nil {
h.log.Error("upload initiate: create upload in database", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
@@ -85,12 +88,15 @@ func (h *Handler) handleUploadInitiate(w http.ResponseWriter, r *http.Request, r
if err != nil {
// Clean up DB row on storage failure.
_ = h.db.DeleteUpload(uuid)
h.log.Error("upload initiate: start upload in storage", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
h.uploads.set(uuid, bw)
h.log.Info("upload initiated", "repo", repo, "uuid", uuid)
w.Header().Set("Location", fmt.Sprintf("/v2/%s/blobs/uploads/%s", repo, uuid))
w.Header().Set("Docker-Upload-UUID", uuid)
w.Header().Set("Range", "0-0")
@@ -112,6 +118,7 @@ func (h *Handler) handleUploadChunk(w http.ResponseWriter, r *http.Request, repo
// Append request body to upload file.
n, err := io.Copy(bw, r.Body)
if err != nil {
h.log.Error("upload chunk: write to storage", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "write failed")
return
}
@@ -119,6 +126,7 @@ func (h *Handler) handleUploadChunk(w http.ResponseWriter, r *http.Request, repo
// Update offset in DB.
newOffset := bw.BytesWritten()
if err := h.db.UpdateUploadOffset(uuid, newOffset); err != nil {
h.log.Error("upload chunk: update offset in database", "error", err, "repo", repo, "uuid", uuid, "offset", newOffset)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
@@ -152,6 +160,7 @@ func (h *Handler) handleUploadComplete(w http.ResponseWriter, r *http.Request, r
// If request body is non-empty, append it first (monolithic upload).
if r.ContentLength != 0 {
if _, err := io.Copy(bw, r.Body); err != nil {
h.log.Error("upload complete: write final chunk to storage", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "write failed")
return
}
@@ -172,6 +181,7 @@ func (h *Handler) handleUploadComplete(w http.ResponseWriter, r *http.Request, r
writeOCIError(w, "DIGEST_INVALID", http.StatusBadRequest, "invalid digest format")
return
}
h.log.Error("upload complete: commit blob", "error", err, "repo", repo, "uuid", uuid, "digest", digest)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "commit failed")
return
}
@@ -180,6 +190,7 @@ func (h *Handler) handleUploadComplete(w http.ResponseWriter, r *http.Request, r
// Insert blob row (no-op if already exists — content-addressed dedup).
if err := h.db.InsertBlob(digest, size); err != nil {
h.log.Error("upload complete: insert blob in database", "error", err, "repo", repo, "digest", digest, "size", size)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
@@ -189,6 +200,8 @@ func (h *Handler) handleUploadComplete(w http.ResponseWriter, r *http.Request, r
h.audit(r, "blob_uploaded", repo, digest)
h.log.Info("blob upload complete", "repo", repo, "digest", digest, "size", size)
w.Header().Set("Location", fmt.Sprintf("/v2/%s/blobs/%s", repo, digest))
w.Header().Set("Docker-Content-Digest", digest)
w.WriteHeader(http.StatusCreated)
@@ -206,6 +219,7 @@ func (h *Handler) handleUploadStatus(w http.ResponseWriter, r *http.Request, rep
writeOCIError(w, "BLOB_UPLOAD_UNKNOWN", http.StatusNotFound, "upload not found")
return
}
h.log.Error("upload status: lookup upload", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}
@@ -233,6 +247,7 @@ func (h *Handler) handleUploadCancel(w http.ResponseWriter, r *http.Request, rep
writeOCIError(w, "BLOB_UPLOAD_UNKNOWN", http.StatusNotFound, "upload not found")
return
}
h.log.Error("upload cancel: delete upload from database", "error", err, "repo", repo, "uuid", uuid)
writeOCIError(w, "UNKNOWN", http.StatusInternalServerError, "internal error")
return
}