fix: summarize truncated debug output
This commit is contained in:
@@ -271,6 +271,10 @@ function summarizeEnvelope(envelope: JsonEnvelope<unknown>): Record<string, unkn
|
||||
summary.topLevelKeys = Object.keys(source).slice(0, 40);
|
||||
const arrayCounts = summarizeArrayCounts(source);
|
||||
if (Object.keys(arrayCounts).length > 0) summary.arrayCounts = arrayCounts;
|
||||
const debugDispatch = summarizeDebugDispatch(source);
|
||||
if (debugDispatch !== null) summary.debugDispatch = debugDispatch;
|
||||
const providerTriage = summarizeProviderTriage(source);
|
||||
if (providerTriage !== null) summary.providerTriage = providerTriage;
|
||||
const issue = source.issue;
|
||||
if (typeof issue === "object" && issue !== null) {
|
||||
const issueRecord = issue as Record<string, unknown>;
|
||||
@@ -303,6 +307,189 @@ function summarizeArrayCounts(source: Record<string, unknown>): Record<string, n
|
||||
return result;
|
||||
}
|
||||
|
||||
function summarizeDebugDispatch(source: Record<string, unknown>): Record<string, unknown> | null {
|
||||
const dispatch = recordOrNull(source.dispatch);
|
||||
const wait = recordOrNull(source.wait);
|
||||
if (dispatch === null && wait === null) return null;
|
||||
const dispatchBody = recordOrNull(dispatch?.body);
|
||||
const waitTask = recordOrNull(wait?.task);
|
||||
const waitPayload = recordOrNull(waitTask?.payload);
|
||||
const waitResult = recordOrNull(waitTask?.result);
|
||||
const result: Record<string, unknown> = {
|
||||
dispatch: dispatch === null ? null : {
|
||||
...pickSummary(dispatch, ["ok", "status", "responseBytesRead", "responseContentLength", "responseTruncated"]),
|
||||
body: dispatchBody === null ? null : pickSummary(dispatchBody, ["ok", "providerOnline", "status", "taskId"]),
|
||||
},
|
||||
wait: wait === null ? null : pickSummary(wait, ["ok", "timeoutMs"]),
|
||||
task: waitTask === null ? null : {
|
||||
...pickSummary(waitTask, ["id", "status", "command", "providerId", "createdAt", "updatedAt"]),
|
||||
payload: waitPayload === null ? null : pickSummary(waitPayload, ["mode", "source"]),
|
||||
},
|
||||
};
|
||||
const providerUpgrade = summarizeProviderUpgradeResult(waitResult);
|
||||
if (providerUpgrade !== null) result.providerUpgrade = providerUpgrade;
|
||||
return result;
|
||||
}
|
||||
|
||||
function summarizeProviderUpgradeResult(result: Record<string, unknown> | null): Record<string, unknown> | null {
|
||||
if (result === null) return null;
|
||||
const schedulerResult = recordOrNull(result.schedulerResult);
|
||||
const plan = recordOrNull(result.plan) ?? recordOrNull(schedulerResult?.plan);
|
||||
const finalPromotedContainer = recordOrNull(result.finalPromotedContainer)
|
||||
?? recordOrNull(schedulerResult?.finalPromotedContainer)
|
||||
?? recordOrNull(plan?.finalPromotedContainer);
|
||||
const heartbeat = recordOrNull(result.heartbeat);
|
||||
const heartbeatConfirmation = recordOrNull(plan?.finalPromotedHeartbeatConfirmation)
|
||||
?? recordOrNull(schedulerResult?.finalPromotedHeartbeatConfirmation);
|
||||
const summary: Record<string, unknown> = {
|
||||
...pickSummary(result, ["ok", "reason", "taskId", "providerId", "targetProviderGatewayVersion", "candidateValidationWasTerminal"]),
|
||||
};
|
||||
const mode = result.mode ?? schedulerResult?.mode;
|
||||
if (mode !== undefined) summary.mode = mode;
|
||||
const message = stringOrNull(result.message) ?? stringOrNull(schedulerResult?.message);
|
||||
if (message !== null) summary.message = shortText(message, 240);
|
||||
const providerGatewayVersion = result.providerGatewayVersion ?? schedulerResult?.providerGatewayVersion ?? plan?.providerGatewayVersion;
|
||||
if (providerGatewayVersion !== undefined) summary.providerGatewayVersion = providerGatewayVersion;
|
||||
if (schedulerResult !== null) {
|
||||
summary.schedulerResult = {
|
||||
...pickSummary(schedulerResult, [
|
||||
"mode",
|
||||
"message",
|
||||
"providerId",
|
||||
"providerName",
|
||||
"providerGatewayVersion",
|
||||
"targetProviderGatewayVersion",
|
||||
"updaterContainerId",
|
||||
"candidateValidationIsNotTerminalSuccess",
|
||||
"terminalStatusManagedByBackendCore",
|
||||
]),
|
||||
};
|
||||
}
|
||||
if (plan !== null) {
|
||||
summary.plan = {
|
||||
...pickSummary(plan, [
|
||||
"taskId",
|
||||
"providerId",
|
||||
"providerName",
|
||||
"policy",
|
||||
"hostProjectRoot",
|
||||
"workspace",
|
||||
"workspaceStateMount",
|
||||
"providerGatewayName",
|
||||
"providerGatewayVersion",
|
||||
"targetProviderGatewayName",
|
||||
"targetProviderGatewayVersion",
|
||||
"runnerImage",
|
||||
"updaterName",
|
||||
"candidateName",
|
||||
]),
|
||||
compose: pickSummary(recordOrNull(plan.compose) ?? {}, ["composeFile", "envFile", "project", "service"]),
|
||||
heartbeatConfirmation: heartbeatConfirmation === null ? null : pickSummary(heartbeatConfirmation, [
|
||||
"providerId",
|
||||
"required",
|
||||
"heartbeatTimeoutMs",
|
||||
"targetProviderGatewayVersion",
|
||||
"finalContainerMustNotEqualCandidate",
|
||||
]),
|
||||
};
|
||||
}
|
||||
if (finalPromotedContainer !== null) {
|
||||
summary.finalPromotedContainer = pickSummary(finalPromotedContainer, [
|
||||
"containerId",
|
||||
"containerName",
|
||||
"version",
|
||||
"restartPolicy",
|
||||
"pidMode",
|
||||
"heartbeatTimestamp",
|
||||
"containerIdUnavailableReason",
|
||||
"versionUnavailableReason",
|
||||
"restartPolicyUnavailableReason",
|
||||
"pidModeUnavailableReason",
|
||||
"heartbeatTimestampUnavailableReason",
|
||||
]);
|
||||
}
|
||||
if (heartbeat !== null) {
|
||||
const heartbeatSummary = pickSummary(heartbeat, ["providerId", "version", "containerId", "containerName", "restartPolicy", "pidMode", "timestamp"]);
|
||||
if (Object.keys(heartbeatSummary).length > 0) summary.heartbeat = heartbeatSummary;
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
function summarizeProviderTriage(source: Record<string, unknown>): Record<string, unknown> | null {
|
||||
if (!("decision" in source) || !("providerId" in source) || !Array.isArray(source.signals)) return null;
|
||||
const signals = source.signals.map(recordOrNull).filter((item): item is Record<string, unknown> => item !== null);
|
||||
const issueSignals = signals
|
||||
.filter((item) => item.status === "failed" || item.status === "degraded" || item.status === "unknown")
|
||||
.sort((left, right) => signalStatusRank(left.status) - signalStatusRank(right.status));
|
||||
const previewSignals = (issueSignals.length > 0 ? issueSignals : signals).slice(0, 8);
|
||||
return {
|
||||
...pickSummary(source, ["ok", "providerId", "decision", "scope", "retryable", "blockingDisposition", "observedAt"]),
|
||||
failedScopes: boundedStringArray(source.failedScopes, 12),
|
||||
degradedScopes: boundedStringArray(source.degradedScopes, 12),
|
||||
healthyScopes: boundedStringArray(source.healthyScopes, 12),
|
||||
failedIndependentScopes: boundedStringArray(source.failedIndependentScopes, 12),
|
||||
healthyIndependentScopes: boundedStringArray(source.healthyIndependentScopes, 12),
|
||||
signalCounts: providerSignalCounts(signals, previewSignals.length),
|
||||
recommendedCrossChecks: boundedStringArray(source.recommendedCrossChecks, 8),
|
||||
rationale: boundedStringArray(source.rationale, 8),
|
||||
signalsPreview: previewSignals.map((item) => ({
|
||||
...pickSummary(item, ["id", "scope", "status", "independentPath", "observedAt"]),
|
||||
summary: shortText(stringOrNull(item.summary) ?? "", 220),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function providerSignalCounts(signals: Record<string, unknown>[], returned: number): Record<string, unknown> {
|
||||
const counts = {
|
||||
total: signals.length,
|
||||
returned,
|
||||
ok: 0,
|
||||
degraded: 0,
|
||||
failed: 0,
|
||||
unknown: 0,
|
||||
};
|
||||
for (const signal of signals) {
|
||||
if (signal.status === "ok") counts.ok += 1;
|
||||
else if (signal.status === "degraded") counts.degraded += 1;
|
||||
else if (signal.status === "failed") counts.failed += 1;
|
||||
else counts.unknown += 1;
|
||||
}
|
||||
return {
|
||||
...counts,
|
||||
omittedSignals: Math.max(0, signals.length - returned),
|
||||
};
|
||||
}
|
||||
|
||||
function signalStatusRank(status: unknown): number {
|
||||
if (status === "failed") return 0;
|
||||
if (status === "degraded") return 1;
|
||||
if (status === "unknown") return 2;
|
||||
if (status === "ok") return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
function boundedStringArray(value: unknown, limit: number): Record<string, unknown> {
|
||||
const items = Array.from(new Set((Array.isArray(value) ? value : []).map((item) => String(item ?? "")).filter(Boolean)));
|
||||
return {
|
||||
items: items.slice(0, limit),
|
||||
count: items.length,
|
||||
truncated: items.length > limit,
|
||||
omitted: Math.max(0, items.length - limit),
|
||||
};
|
||||
}
|
||||
|
||||
function recordOrNull(value: unknown): Record<string, unknown> | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function stringOrNull(value: unknown): string | null {
|
||||
return typeof value === "string" ? value : null;
|
||||
}
|
||||
|
||||
function shortText(value: string, maxChars: number): string {
|
||||
return value.length <= maxChars ? value : `${value.slice(0, Math.max(0, maxChars - 3))}...`;
|
||||
}
|
||||
|
||||
function pickSummary(source: Record<string, unknown>, keys: string[]): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key of keys) {
|
||||
|
||||
Reference in New Issue
Block a user