refactor(api): enhance tts segment error handling and logging patterns
Update TTS segment ensure route to distinguish aborted and failed segment generation, providing more granular logging and context. Replace ambiguous message variables with clearer detail fields, and improve log event naming for abort scenarios. Adjust ESLint logger call selectors for maintainability and consistency, consolidating selector logic and enforcing stricter server logger usage patterns. These changes improve observability and error traceability in TTS segment operations.
This commit is contained in:
parent
034c1f03a4
commit
f78d066434
2 changed files with 52 additions and 39 deletions
|
|
@ -9,6 +9,15 @@ const compat = new FlatCompat({
|
|||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const LOGGER_LEVEL_METHOD = "trace|debug|info|warn|error|fatal";
|
||||
const STATIC_LOGGER_CALL_SELECTOR =
|
||||
`CallExpression[callee.type='MemberExpression'][callee.computed=false][callee.property.name=/^(${LOGGER_LEVEL_METHOD})$/]`;
|
||||
const DYNAMIC_LOGGER_CALL_SELECTOR =
|
||||
"CallExpression[callee.type='MemberExpression'][callee.computed=true]";
|
||||
const LOGGER_RECEIVER_SELECTOR =
|
||||
":matches([callee.object.name=/^(logger|serverLogger)$/],[callee.object.property.name='logger'])";
|
||||
const SERVER_LOGGER_CALL_SELECTOR = `:matches(${STATIC_LOGGER_CALL_SELECTOR}${LOGGER_RECEIVER_SELECTOR},${DYNAMIC_LOGGER_CALL_SELECTOR}${LOGGER_RECEIVER_SELECTOR})`;
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
{
|
||||
|
|
@ -39,32 +48,27 @@ const eslintConfig = [
|
|||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector:
|
||||
":matches(CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.name=/^(logger|serverLogger)$/],CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.property.name='logger'])[arguments.length<2]",
|
||||
selector: `${SERVER_LOGGER_CALL_SELECTOR}[arguments.length<2]`,
|
||||
message:
|
||||
"Server logger calls must pass context + message: logger.<level>({ event, ...ctx }, 'message').",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
":matches(CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.name=/^(logger|serverLogger)$/],CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.property.name='logger'])[arguments.0.type='Literal']",
|
||||
selector: `${SERVER_LOGGER_CALL_SELECTOR}[arguments.0.type='Literal']`,
|
||||
message:
|
||||
"Server logger first argument must be an object with an event field, not a string literal.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
":matches(CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.name=/^(logger|serverLogger)$/],CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.property.name='logger'])[arguments.0.type='TemplateLiteral']",
|
||||
selector: `${SERVER_LOGGER_CALL_SELECTOR}[arguments.0.type='TemplateLiteral']`,
|
||||
message:
|
||||
"Server logger first argument must be an object with an event field, not a template string.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
":matches(CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.name=/^(logger|serverLogger)$/],CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.property.name='logger'])[arguments.0.type='ObjectExpression']:not(:has(Property[key.name='event']))",
|
||||
selector: `${SERVER_LOGGER_CALL_SELECTOR}[arguments.0.type='ObjectExpression']:not(:has(Property[key.name='event']))`,
|
||||
message:
|
||||
"Server logger context object must include an event field.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
":matches(CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.name=/^(logger|serverLogger)$/],CallExpression[callee.property.name=/^(trace|debug|info|warn|error|fatal)$/][callee.object.property.name='logger']) > ObjectExpression:first-child > Property[key.name='err']",
|
||||
selector: `${SERVER_LOGGER_CALL_SELECTOR} > ObjectExpression:first-child > Property[key.name='err']`,
|
||||
message:
|
||||
"Use `error` (typically from errorToLog(...)) instead of `err` in server logs.",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ export async function POST(request: NextRequest) {
|
|||
segmentId: segment.segmentId,
|
||||
aborted,
|
||||
error: alignError instanceof Error ? alignError.message : String(alignError),
|
||||
});
|
||||
}, 'Self-heal alignment unavailable');
|
||||
alignment = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -666,7 +666,7 @@ export async function POST(request: NextRequest) {
|
|||
segmentId: segment.segmentId,
|
||||
aborted,
|
||||
error: alignError instanceof Error ? alignError.message : String(alignError),
|
||||
});
|
||||
}, 'Alignment unavailable');
|
||||
alignment = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -708,7 +708,7 @@ export async function POST(request: NextRequest) {
|
|||
status: 'completed',
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to generate segment';
|
||||
const detail = error instanceof Error ? error.message : 'Failed to generate segment';
|
||||
const aborted = isAbortLikeError(error);
|
||||
const upstreamStatus = getUpstreamStatus(error);
|
||||
const retryAfterSeconds = upstreamStatus === 429
|
||||
|
|
@ -719,29 +719,46 @@ export async function POST(request: NextRequest) {
|
|||
: upstreamStatus && upstreamStatus >= 500
|
||||
? 'UPSTREAM_TTS_ERROR'
|
||||
: 'TTS_SEGMENT_GENERATION_FAILED';
|
||||
const level = aborted ? 'info' : 'error';
|
||||
logger[level]({
|
||||
event: 'tts.segments.ensure.segment_failed',
|
||||
requestId,
|
||||
documentId: parsed.documentId,
|
||||
segmentId: segment.segmentId,
|
||||
failedStage,
|
||||
elapsedMs: Date.now() - segmentStartedAt,
|
||||
stageTimings,
|
||||
aborted,
|
||||
upstreamStatus,
|
||||
retryAfterSeconds,
|
||||
errorCode,
|
||||
message,
|
||||
});
|
||||
if (aborted) {
|
||||
logger.info({
|
||||
event: 'tts.segments.ensure.segment_aborted',
|
||||
requestId,
|
||||
documentId: parsed.documentId,
|
||||
segmentId: segment.segmentId,
|
||||
failedStage,
|
||||
elapsedMs: Date.now() - segmentStartedAt,
|
||||
stageTimings,
|
||||
aborted: true,
|
||||
errorCode,
|
||||
detail,
|
||||
completedSoFar: manifest.length,
|
||||
totalRequested: normalized.length,
|
||||
}, 'Stopping segment ensure after abort');
|
||||
} else {
|
||||
logger.error({
|
||||
event: 'tts.segments.ensure.segment_failed',
|
||||
requestId,
|
||||
documentId: parsed.documentId,
|
||||
segmentId: segment.segmentId,
|
||||
failedStage,
|
||||
elapsedMs: Date.now() - segmentStartedAt,
|
||||
stageTimings,
|
||||
aborted: false,
|
||||
upstreamStatus,
|
||||
retryAfterSeconds,
|
||||
errorCode,
|
||||
detail,
|
||||
error: errorToLog(error),
|
||||
}, 'TTS segment generation failed');
|
||||
}
|
||||
await db
|
||||
.update(ttsSegmentVariants)
|
||||
.set({
|
||||
status: aborted ? 'pending' : 'error',
|
||||
error: aborted ? null : (
|
||||
upstreamStatus
|
||||
? `${errorCode}${retryAfterSeconds ? ` (retry after ${retryAfterSeconds}s)` : ''}: ${message}`
|
||||
: message
|
||||
? `${errorCode}${retryAfterSeconds ? ` (retry after ${retryAfterSeconds}s)` : ''}: ${detail}`
|
||||
: detail
|
||||
),
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
|
|
@ -764,21 +781,13 @@ export async function POST(request: NextRequest) {
|
|||
? null
|
||||
: {
|
||||
code: errorCode,
|
||||
detail: message,
|
||||
detail,
|
||||
...(typeof upstreamStatus === 'number' ? { upstreamStatus } : {}),
|
||||
...(typeof retryAfterSeconds === 'number' ? { retryAfterSeconds } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (aborted || request.signal.aborted) {
|
||||
logger.info({
|
||||
event: 'tts.segments.ensure.stop_after_abort',
|
||||
requestId,
|
||||
documentId: parsed.documentId,
|
||||
segmentId: segment.segmentId,
|
||||
completedSoFar: manifest.length,
|
||||
totalRequested: normalized.length,
|
||||
}, 'Stopping segment ensure after abort');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue