diff --git a/src/server/lib/dataforseo/core.ts b/src/server/lib/dataforseo/core.ts index fe62c68..d8fd74a 100644 --- a/src/server/lib/dataforseo/core.ts +++ b/src/server/lib/dataforseo/core.ts @@ -138,10 +138,11 @@ function createAuthenticatedFetch( ); error.name = "DataForSEOHttpError"; // UPSTREAM_UNAVAILABLE is non-reportable, and the error handlers only log - // what they capture, so log here to keep the provider's failure rate - // visible in Workers Observability. + // what they capture, so warn here to keep the provider's failure rate + // visible in Workers Observability. Warn, not error: there is nothing in + // the app to fix. if (code === "UPSTREAM_UNAVAILABLE") - console.error("dataforseo.upstream-http-failed", { + console.warn("dataforseo.upstream-http-failed", { path, status: response.status, }); diff --git a/src/server/lib/dataforseo/envelope.ts b/src/server/lib/dataforseo/envelope.ts index a3a8a25..6e74771 100644 --- a/src/server/lib/dataforseo/envelope.ts +++ b/src/server/lib/dataforseo/envelope.ts @@ -246,10 +246,11 @@ export function assertOk( const detailedMessage = describeInvalidField(message, task); const isUpstreamFailure = isUpstreamServerErrorTask(task); // UPSTREAM_UNAVAILABLE is non-reportable, and the error handlers only log - // what they capture, so log here to keep the provider's failure rate — and - // the only remaining record of the message — visible in Workers Observability. + // what they capture, so warn here to keep the provider's failure rate — and + // the only remaining record of the message — visible in Workers + // Observability. Warn, not error: there is nothing in the app to fix. if (isUpstreamFailure) - console.error("dataforseo.upstream-task-failed", { + console.warn("dataforseo.upstream-task-failed", { path, status: task.status_code, message: task.status_message, diff --git a/src/server/workflows/rankCheckPaths.ts b/src/server/workflows/rankCheckPaths.ts index 949ce39..2c211cd 100644 --- a/src/server/workflows/rankCheckPaths.ts +++ b/src/server/workflows/rankCheckPaths.ts @@ -10,6 +10,7 @@ import type { RankCheckResult, RankCheckTaskInput, } from "@/server/lib/dataforseo"; +import { AppError } from "@/server/lib/errors"; import type { RankTrackingConfig } from "@/types/schemas/rank-tracking"; import { KEYWORDS_PER_BATCH } from "@/shared/rank-tracking"; import { pgStep } from "@/server/workflows/pgStep"; @@ -100,16 +101,23 @@ async function checkBatchLive( ), ); const results: RankCheckResultWithDevice[] = []; - for (const outcome of settled) { + settled.forEach((outcome, index) => { if (outcome.status === "fulfilled") { results.push(outcome.value); - } else { - console.error( - `[rank-check] ${ctx.runId} live call failed:`, - outcome.reason, - ); + return; } - } + const reason = outcome.reason; + const code = reason instanceof AppError ? reason.code : "UNKNOWN"; + const message = reason instanceof Error ? reason.message : String(reason); + // DataForSEO erring on its own side is a provider flake, not our bug: the + // keyword just misses this run and finalize reports it to the user. Every + // other rejection (no credits, bad API key) is ours and stays at error. + const log = code === "UPSTREAM_UNAVAILABLE" ? console.warn : console.error; + const task = tasks[index]; + log( + `[rank-check] ${ctx.runId} live call failed (${code}) keyword="${task.keyword}" device=${task.device}: ${message}`, + ); + }); if (results.length > 0) { await RankTrackingRepository.insertSnapshots( mapResultsToSnapshotRows(ctx.runId, results), diff --git a/src/shared/error-codes.ts b/src/shared/error-codes.ts index dd95158..5c3adc0 100644 --- a/src/shared/error-codes.ts +++ b/src/shared/error-codes.ts @@ -39,8 +39,8 @@ const NON_REPORTABLE_ERROR_CODES = new Set([ "AUDIT_ALREADY_RUNNING", // An external provider (DataForSEO) failing on its own side. Nothing in the // app to fix, and it drowned real exceptions. Note the error handlers only - // log what they capture, so every throw site logs its own line to keep the - // failure rate visible in Workers Observability. + // log what they capture, so every throw site warns on its own line to keep + // the failure rate visible in Workers Observability. "UPSTREAM_UNAVAILABLE", ]);