feat(server/coverart): batch summary log with category breakdown
The existing processed/succeeded/failed tally written to scan_runs
collapses too much to debug "0 successes" symptoms — failed includes
"settled to none (correct)" alongside "left NULL (transient/IO error,
will retry)" alongside "skipped (no MBID)". An operator looking at
{"failed": 144, "succeeded": 0} can't tell whether the enricher is
broken, the upstream is broken, the data is missing MBIDs, or the
artists genuinely aren't in TheAudioDB.
Adds a single end-of-batch Info log per stage with the breakdown:
- eligible: rows returned by the SQL filter
- processed: rows the loop actually touched
- succeeded: art attached (provider success)
- settled_none: tried, no provider had art (legit miss)
- no_mbid: skipped inside the enricher because MBID was NULL
(artist stage only)
- left_null: tried, transient/IO failure, will retry next pass
- errored: internal error during the entry (logged separately)
The JSON written to scan_runs keeps its existing shape so the admin
UI's stage badges aren't disturbed.
This commit is contained in:
@@ -135,6 +135,12 @@ func (e *Enricher) recordArtistArt(ctx context.Context, artistID pgtype.UUID, th
|
|||||||
// EnrichArtistBatch drains up to limit artists whose eligibility loop
|
// EnrichArtistBatch drains up to limit artists whose eligibility loop
|
||||||
// determines they need processing. Iterates serially. Returns
|
// determines they need processing. Iterates serially. Returns
|
||||||
// per-batch tallies for the orchestrator's scan-run record.
|
// per-batch tallies for the orchestrator's scan-run record.
|
||||||
|
//
|
||||||
|
// Emits a single Info log line at end-of-batch with a category
|
||||||
|
// breakdown (succeeded / settled-none / no-MBID / left-NULL /
|
||||||
|
// errored). The JSON tally written to scan_runs keeps the existing
|
||||||
|
// processed/succeeded/failed shape for backwards compat with the
|
||||||
|
// admin overview UI; the log line is the operator's diagnostic.
|
||||||
func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir string,
|
func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir string,
|
||||||
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
|
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
@@ -148,8 +154,10 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str
|
|||||||
if qerr != nil {
|
if qerr != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("list missing artist art: %w", qerr)
|
return 0, 0, 0, fmt.Errorf("list missing artist art: %w", qerr)
|
||||||
}
|
}
|
||||||
|
var settledNone, noMBID, leftNull, errored int
|
||||||
for _, id := range rows {
|
for _, id := range rows {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
|
e.logArtistBatchSummary(len(rows), processed, succeeded, settledNone, noMBID, leftNull, errored)
|
||||||
return processed, succeeded, failed, ctx.Err()
|
return processed, succeeded, failed, ctx.Err()
|
||||||
}
|
}
|
||||||
processed++
|
processed++
|
||||||
@@ -157,6 +165,7 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str
|
|||||||
e.logger.Warn("coverart: artist batch entry failed",
|
e.logger.Warn("coverart: artist batch entry failed",
|
||||||
"artist_id", uuidString(id), "err", eerr)
|
"artist_id", uuidString(id), "err", eerr)
|
||||||
failed++
|
failed++
|
||||||
|
errored++
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
@@ -166,25 +175,49 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str
|
|||||||
row, rerr := q.GetArtistByID(ctx, id)
|
row, rerr := q.GetArtistByID(ctx, id)
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
failed++
|
failed++
|
||||||
|
errored++
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if row.ArtistArtSource != nil &&
|
switch {
|
||||||
|
case row.ArtistArtSource != nil &&
|
||||||
*row.ArtistArtSource != "" &&
|
*row.ArtistArtSource != "" &&
|
||||||
*row.ArtistArtSource != "none" {
|
*row.ArtistArtSource != "none":
|
||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
case row.ArtistArtSource != nil && *row.ArtistArtSource == "none":
|
||||||
|
settledNone++
|
||||||
|
failed++
|
||||||
|
case row.Mbid == nil || *row.Mbid == "":
|
||||||
|
noMBID++
|
||||||
|
failed++
|
||||||
|
default:
|
||||||
|
leftNull++
|
||||||
failed++
|
failed++
|
||||||
}
|
}
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.logArtistBatchSummary(len(rows), processed, succeeded, settledNone, noMBID, leftNull, errored)
|
||||||
return processed, succeeded, failed, nil
|
return processed, succeeded, failed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logArtistBatchSummary emits a single Info line at end-of-batch with
|
||||||
|
// the full category breakdown including MBID-skipped (artists eligible
|
||||||
|
// by the SQL filter but skipped inside EnrichArtist due to NULL MBID).
|
||||||
|
func (e *Enricher) logArtistBatchSummary(eligible, processed, succeeded, settledNone, noMBID, leftNull, errored int) {
|
||||||
|
e.logger.Info("coverart: artist enrichment batch complete",
|
||||||
|
"eligible", eligible,
|
||||||
|
"processed", processed,
|
||||||
|
"succeeded", succeeded,
|
||||||
|
"settled_none", settledNone,
|
||||||
|
"no_mbid", noMBID,
|
||||||
|
"left_null", leftNull,
|
||||||
|
"errored", errored)
|
||||||
|
}
|
||||||
|
|
||||||
// CleanupArtistArt removes the artist-art directory for a deleted
|
// CleanupArtistArt removes the artist-art directory for a deleted
|
||||||
// artist. Idempotent — no-op if the directory doesn't exist. Called
|
// artist. Idempotent — no-op if the directory doesn't exist. Called
|
||||||
// from the artist-delete cascade in tracks/service.go after the
|
// from the artist-delete cascade in tracks/service.go after the
|
||||||
|
|||||||
@@ -168,6 +168,12 @@ func (e *Enricher) recordAlbumCover(ctx context.Context, albumID pgtype.UUID, pa
|
|||||||
// EnrichBatch drains up to limit albums whose eligibility loop
|
// EnrichBatch drains up to limit albums whose eligibility loop
|
||||||
// determines they need processing. Iterates serially. Returns
|
// determines they need processing. Iterates serially. Returns
|
||||||
// per-batch tallies for the orchestrator's scan-run record.
|
// per-batch tallies for the orchestrator's scan-run record.
|
||||||
|
//
|
||||||
|
// Emits a single Info log line at end-of-batch with a category
|
||||||
|
// breakdown (succeeded / settled-none / left-NULL / errored). The
|
||||||
|
// JSON tally written to scan_runs keeps the existing
|
||||||
|
// processed/succeeded/failed shape for backwards compat with the
|
||||||
|
// admin overview UI; the log line is the operator's diagnostic.
|
||||||
func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
|
func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
|
||||||
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
|
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
@@ -181,8 +187,10 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
|
|||||||
if qerr != nil {
|
if qerr != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("list missing: %w", qerr)
|
return 0, 0, 0, fmt.Errorf("list missing: %w", qerr)
|
||||||
}
|
}
|
||||||
|
var settledNone, leftNull, errored int
|
||||||
for _, id := range rows {
|
for _, id := range rows {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
|
e.logBatchSummary("album", len(rows), processed, succeeded, settledNone, leftNull, errored)
|
||||||
return processed, succeeded, failed, ctx.Err()
|
return processed, succeeded, failed, ctx.Err()
|
||||||
}
|
}
|
||||||
processed++
|
processed++
|
||||||
@@ -190,6 +198,7 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
|
|||||||
e.logger.Warn("coverart: batch entry failed",
|
e.logger.Warn("coverart: batch entry failed",
|
||||||
"album_id", uuidString(id), "err", eerr)
|
"album_id", uuidString(id), "err", eerr)
|
||||||
failed++
|
failed++
|
||||||
|
errored++
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
@@ -199,27 +208,49 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
|
|||||||
row, rerr := q.GetAlbumWithFirstTrackPath(ctx, id)
|
row, rerr := q.GetAlbumWithFirstTrackPath(ctx, id)
|
||||||
if rerr != nil {
|
if rerr != nil {
|
||||||
failed++
|
failed++
|
||||||
|
errored++
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if row.CoverArtSource != nil &&
|
switch {
|
||||||
|
case row.CoverArtSource != nil &&
|
||||||
(*row.CoverArtSource == "sidecar" ||
|
(*row.CoverArtSource == "sidecar" ||
|
||||||
*row.CoverArtSource == "mbcaa" ||
|
*row.CoverArtSource == "mbcaa" ||
|
||||||
*row.CoverArtSource == "embedded" ||
|
*row.CoverArtSource == "embedded" ||
|
||||||
*row.CoverArtSource == "theaudiodb") {
|
*row.CoverArtSource == "theaudiodb"):
|
||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
case row.CoverArtSource != nil && *row.CoverArtSource == "none":
|
||||||
|
settledNone++
|
||||||
|
failed++
|
||||||
|
default:
|
||||||
|
leftNull++
|
||||||
failed++
|
failed++
|
||||||
}
|
}
|
||||||
if progressCb != nil {
|
if progressCb != nil {
|
||||||
progressCb(processed, succeeded, failed)
|
progressCb(processed, succeeded, failed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.logBatchSummary("album", len(rows), processed, succeeded, settledNone, leftNull, errored)
|
||||||
return processed, succeeded, failed, nil
|
return processed, succeeded, failed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logBatchSummary emits a single Info line at end-of-batch with the
|
||||||
|
// full category breakdown. Operator-side diagnostic; the
|
||||||
|
// processed/succeeded/failed tally collapses too much to debug
|
||||||
|
// "0 successes" symptoms by itself.
|
||||||
|
func (e *Enricher) logBatchSummary(stage string, eligible, processed, succeeded, settledNone, leftNull, errored int) {
|
||||||
|
e.logger.Info("coverart: enrichment batch complete",
|
||||||
|
"stage", stage,
|
||||||
|
"eligible", eligible,
|
||||||
|
"processed", processed,
|
||||||
|
"succeeded", succeeded,
|
||||||
|
"settled_none", settledNone,
|
||||||
|
"left_null", leftNull,
|
||||||
|
"errored", errored)
|
||||||
|
}
|
||||||
|
|
||||||
// EnrichRetryMissing drains both NULL and 'none' rows. Used by the
|
// EnrichRetryMissing drains both NULL and 'none' rows. Used by the
|
||||||
// admin bulk-retry endpoint. Clears 'none' to NULL first so
|
// admin bulk-retry endpoint. Clears 'none' to NULL first so
|
||||||
// EnrichAlbum picks them up.
|
// EnrichAlbum picks them up.
|
||||||
|
|||||||
Reference in New Issue
Block a user