mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
fix(observability): preserve admin upstream error diagnostics
This commit is contained in:
@@ -477,6 +477,18 @@
|
||||
HTTP {{ currentAttemptRequestError.statusCode }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentAttemptRequestError.upstreamStatusCode != null && currentAttemptRequestError.upstreamStatusCode !== currentAttemptRequestError.statusCode"
|
||||
class="error-upstream-status text-xs text-muted-foreground mb-1"
|
||||
>
|
||||
上游响应状态:HTTP {{ currentAttemptRequestError.upstreamStatusCode }}(本次尝试状态见上方)
|
||||
</div>
|
||||
<div
|
||||
v-if="currentAttemptRequestError.finalStatusCode != null"
|
||||
class="error-final-status text-xs text-muted-foreground mb-1"
|
||||
>
|
||||
请求最终状态:HTTP {{ currentAttemptRequestError.finalStatusCode }}(与本次尝试状态不同)
|
||||
</div>
|
||||
<div
|
||||
v-if="currentAttemptRequestError.message"
|
||||
class="error-msg"
|
||||
@@ -1630,21 +1642,19 @@ const shouldShowAttemptMessageWithUpstreamResponse = (
|
||||
rawMessage: string,
|
||||
upstreamResponse: Record<string, unknown> | null,
|
||||
): boolean => {
|
||||
if (!upstreamResponse) return true
|
||||
if (!upstreamResponse || !hasRenderableValue(upstreamResponse.body)) return true
|
||||
const normalized = rawMessage.trim()
|
||||
if (!normalized) return false
|
||||
if (isLocalSyncFinalizeDiagnostic(normalized)) return true
|
||||
if (isConversionDiagnosticMessage(normalized)) return true
|
||||
if (isGenericExecutionRuntimeStatusMessage(normalized)) return false
|
||||
|
||||
const hasBody = hasRenderableValue(upstreamResponse.body)
|
||||
const bodyState = (readStringField(upstreamResponse, 'body_state') ?? '').toLowerCase()
|
||||
return !hasBody && bodyState === 'disabled'
|
||||
return false
|
||||
}
|
||||
|
||||
const currentAttemptRequestError = computed<{
|
||||
message: string
|
||||
statusCode?: number
|
||||
upstreamStatusCode?: number
|
||||
finalStatusCode?: number
|
||||
upstreamResponse: Record<string, unknown> | null
|
||||
diagnostic: Record<string, unknown> | null
|
||||
} | null>(() => {
|
||||
@@ -1654,11 +1664,16 @@ const currentAttemptRequestError = computed<{
|
||||
const extra = extractObject(attempt.extra_data)
|
||||
const upstreamResponse = extractObject(extra?.upstream_response)
|
||||
const errorFlow = extractObject(extra?.error_flow)
|
||||
const statusCode = readNumberField(upstreamResponse ?? {}, 'status_code')
|
||||
const upstreamStatusCode = readNumberField(upstreamResponse ?? {}, 'status_code')
|
||||
?? readNumberField(upstreamResponse ?? {}, 'statusCode')
|
||||
const statusCode = attempt.status_code
|
||||
?? upstreamStatusCode
|
||||
?? readNumberField(errorFlow ?? {}, 'status_code')
|
||||
?? readNumberField(errorFlow ?? {}, 'statusCode')
|
||||
?? attempt.status_code
|
||||
const finalStatusCode = !['pending', 'streaming'].includes(computedFinalStatus.value)
|
||||
&& props.overrideStatusCode != null && props.overrideStatusCode !== statusCode
|
||||
? props.overrideStatusCode
|
||||
: undefined
|
||||
const flowMessage = errorFlow
|
||||
? readStringField(errorFlow, 'message')
|
||||
: ''
|
||||
@@ -1671,6 +1686,7 @@ const currentAttemptRequestError = computed<{
|
||||
const diagnosticMessage = extractVisibleDiagnosticMessage(extra)
|
||||
const rawMessage = chooseAttemptRawErrorMessage(flowMessage || '', fallbackMessage, diagnosticMessage)
|
||||
const message = formatAttemptErrorMessage(rawMessage, statusCode) || fallbackType
|
||||
|| '本次尝试失败,链路追踪未包含详细错误内容。'
|
||||
const upstreamResponseDisplay = normalizeUpstreamResponseDisplay(extra?.upstream_response)
|
||||
const visibleDiagnosticObjects = extractVisibleDiagnosticObjects(extra)
|
||||
const shouldAttachDiagnostic = Boolean(
|
||||
@@ -1695,20 +1711,16 @@ const currentAttemptRequestError = computed<{
|
||||
const response = Object.keys(upstreamResponseData).length > 0
|
||||
? upstreamResponseData
|
||||
: null
|
||||
if (
|
||||
!message
|
||||
&& statusCode == null
|
||||
&& !response
|
||||
&& !diagnostic
|
||||
) return null
|
||||
const showMessage = shouldShowAttemptMessageWithUpstreamResponse(
|
||||
rawMessage || fallbackType,
|
||||
upstreamResponseDisplay,
|
||||
)
|
||||
|
||||
return {
|
||||
message: showMessage ? (message || '未知错误') : '',
|
||||
message: showMessage ? message : '',
|
||||
statusCode,
|
||||
upstreamStatusCode,
|
||||
finalStatusCode,
|
||||
upstreamResponse: response,
|
||||
diagnostic,
|
||||
}
|
||||
|
||||
@@ -659,6 +659,98 @@ describe('HorizontalRequestTimeline', () => {
|
||||
expect(root.textContent).not.toContain('该错误被标记为敏感上游错误')
|
||||
})
|
||||
|
||||
it.each(['inline', 'reference', 'disabled', 'unavailable', 'none', undefined])(
|
||||
'shows a fallback for redacted errors with body state %s',
|
||||
async (bodyState) => {
|
||||
const trace = buildTrace([
|
||||
buildCandidate({
|
||||
status_code: 400,
|
||||
extra_data: {
|
||||
upstream_response: {
|
||||
status_code: 400,
|
||||
body_state: bodyState,
|
||||
},
|
||||
error_flow: {
|
||||
source: 'upstream_response',
|
||||
status_code: 400,
|
||||
decision: 'retry_next_candidate',
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
trace.final_status = 'failed'
|
||||
|
||||
const root = mountTimeline(trace, {
|
||||
overrideStatusCode: 503,
|
||||
requestStatus: 'failed',
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('.status-tag')?.textContent?.trim()).toBe('400')
|
||||
expect(root.querySelector('.error-status-badge')?.textContent?.trim()).toBe('HTTP 400')
|
||||
expect(root.querySelector('.error-msg')?.textContent).toContain('链路追踪未包含详细错误内容')
|
||||
expect(root.querySelector('.error-final-status')?.textContent).toContain('请求最终状态:HTTP 503')
|
||||
expect(root.querySelector('.error-upstream-response-json')).toBeNull()
|
||||
},
|
||||
)
|
||||
|
||||
it('keeps the attempt status separate from the upstream transport status', async () => {
|
||||
const root = mountTimeline(buildTrace([
|
||||
buildCandidate({
|
||||
status_code: 502,
|
||||
error_type: 'stream_error',
|
||||
extra_data: {
|
||||
upstream_response: { status_code: 200, body_state: 'disabled' },
|
||||
},
|
||||
}),
|
||||
]))
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('.status-tag')?.textContent?.trim()).toBe('502')
|
||||
expect(root.querySelector('.error-status-badge')?.textContent?.trim()).toBe('HTTP 502')
|
||||
expect(root.querySelector('.error-upstream-status')?.textContent).toContain('上游响应状态:HTTP 200')
|
||||
expect(root.querySelector('.error-final-status')).toBeNull()
|
||||
})
|
||||
|
||||
it('does not label an active request status as final', async () => {
|
||||
const root = mountTimeline(buildTrace([
|
||||
buildCandidate({ status_code: 400 }),
|
||||
]), {
|
||||
overrideStatusCode: 200,
|
||||
requestStatus: 'streaming',
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('.error-final-status')).toBeNull()
|
||||
})
|
||||
|
||||
it('shows failure details even when no status or error message was retained', async () => {
|
||||
const root = mountTimeline(buildTrace([buildCandidate()]))
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('.error-msg')?.textContent).toContain('链路追踪未包含详细错误内容')
|
||||
})
|
||||
|
||||
it('keeps a generic error visible when only upstream headers are available', async () => {
|
||||
const root = mountTimeline(buildTrace([
|
||||
buildCandidate({
|
||||
status_code: 400,
|
||||
error_message: 'execution runtime stream returned non-success status 400',
|
||||
extra_data: {
|
||||
upstream_response: {
|
||||
status_code: 400,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body_state: 'reference',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]))
|
||||
await nextTick()
|
||||
|
||||
expect(root.querySelector('.error-msg')?.textContent).toContain('上游返回非成功状态 400')
|
||||
expect(root.querySelector('.error-upstream-response-json')?.textContent).toContain('application/json')
|
||||
})
|
||||
|
||||
it('keeps local sync diagnostics visible when upstream response body capture is disabled', async () => {
|
||||
const trace = buildTrace([
|
||||
buildCandidate({
|
||||
|
||||
Reference in New Issue
Block a user