Commit 13b72f6b authored by Ethan0x0000's avatar Ethan0x0000
Browse files

Merge remote-tracking branch 'origin/feat/ops-error-observability-transfer'...

Merge remote-tracking branch 'origin/feat/ops-error-observability-transfer' into feat/anthropic-openai-endpoint-compat

# Conflicts:
#	frontend/src/api/admin/ops.ts
#	frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
#	frontend/src/views/admin/ops/components/OpsErrorLogTable.vue
parents c5aa96a3 ecad083f
...@@ -970,9 +970,12 @@ export interface OpsErrorLog { ...@@ -970,9 +970,12 @@ export interface OpsErrorLog {
request_path?: string request_path?: string
stream?: boolean stream?: boolean
// Model mapping context for ops error observability // Error observability context (endpoint + model mapping)
inbound_endpoint?: string
upstream_endpoint?: string
requested_model?: string requested_model?: string
upstream_model?: string upstream_model?: string
request_type?: number | null
} }
export interface OpsErrorDetail extends OpsErrorLog { export interface OpsErrorDetail extends OpsErrorLog {
......
...@@ -3486,7 +3486,12 @@ export default { ...@@ -3486,7 +3486,12 @@ export default {
typeRequest: 'Request', typeRequest: 'Request',
typeAuth: 'Auth', typeAuth: 'Auth',
typeRouting: 'Routing', typeRouting: 'Routing',
typeInternal: 'Internal' typeInternal: 'Internal',
endpoint: 'Endpoint',
requestType: 'Type',
requestTypeSync: 'Sync',
requestTypeStream: 'Stream',
requestTypeWs: 'WS'
}, },
// Error Details Modal // Error Details Modal
errorDetails: { errorDetails: {
...@@ -3572,6 +3577,16 @@ export default { ...@@ -3572,6 +3577,16 @@ export default {
latency: 'Request Duration', latency: 'Request Duration',
businessLimited: 'Business Limited', businessLimited: 'Business Limited',
requestPath: 'Request Path', requestPath: 'Request Path',
inboundEndpoint: 'Inbound Endpoint',
upstreamEndpoint: 'Upstream Endpoint',
requestedModel: 'Requested Model',
upstreamModel: 'Upstream Model',
requestType: 'Request Type',
requestTypeUnknown: 'Unknown',
requestTypeSync: 'Sync',
requestTypeStream: 'Stream',
requestTypeWs: 'WebSocket',
modelMapping: 'Model Mapping',
timings: 'Timings', timings: 'Timings',
auth: 'Auth', auth: 'Auth',
routing: 'Routing', routing: 'Routing',
......
...@@ -3651,7 +3651,12 @@ export default { ...@@ -3651,7 +3651,12 @@ export default {
typeRequest: '请求', typeRequest: '请求',
typeAuth: '认证', typeAuth: '认证',
typeRouting: '路由', typeRouting: '路由',
typeInternal: '内部' typeInternal: '内部',
endpoint: '端点',
requestType: '类型',
requestTypeSync: '同步',
requestTypeStream: '流式',
requestTypeWs: 'WS'
}, },
// Error Details Modal // Error Details Modal
errorDetails: { errorDetails: {
...@@ -3737,6 +3742,16 @@ export default { ...@@ -3737,6 +3742,16 @@ export default {
latency: '请求时长', latency: '请求时长',
businessLimited: '业务限制', businessLimited: '业务限制',
requestPath: '请求路径', requestPath: '请求路径',
inboundEndpoint: '入站端点',
upstreamEndpoint: '上游端点',
requestedModel: '请求模型',
upstreamModel: '上游模型',
requestType: '请求类型',
requestTypeUnknown: '未知',
requestTypeSync: '同步',
requestTypeStream: '流式',
requestTypeWs: 'WebSocket',
modelMapping: '模型映射',
timings: '时序信息', timings: '时序信息',
auth: '认证', auth: '认证',
routing: '路由', routing: '路由',
......
...@@ -70,6 +70,20 @@ ...@@ -70,6 +70,20 @@
</div> </div>
</div> </div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.inboundEndpoint') }}</div>
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
{{ detail.inbound_endpoint || '—' }}
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.upstreamEndpoint') }}</div>
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
{{ detail.upstream_endpoint || '—' }}
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900"> <div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.status') }}</div> <div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.status') }}</div>
<div class="mt-1"> <div class="mt-1">
...@@ -79,6 +93,13 @@ ...@@ -79,6 +93,13 @@
</div> </div>
</div> </div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.requestType') }}</div>
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{{ formatRequestTypeLabel(detail.request_type) }}
</div>
</div>
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900"> <div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div> <div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div>
<div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message"> <div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message">
...@@ -220,6 +241,15 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean { ...@@ -220,6 +241,15 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
return phase === 'upstream' && owner === 'provider' return phase === 'upstream' && owner === 'provider'
} }
function formatRequestTypeLabel(type: number | null | undefined): string {
switch (type) {
case 1: return t('admin.ops.errorDetail.requestTypeSync')
case 2: return t('admin.ops.errorDetail.requestTypeStream')
case 3: return t('admin.ops.errorDetail.requestTypeWs')
default: return t('admin.ops.errorDetail.requestTypeUnknown')
}
}
function hasModelMapping(d: OpsErrorDetail | null): boolean { function hasModelMapping(d: OpsErrorDetail | null): boolean {
if (!d) return false if (!d) return false
const requested = String(d.requested_model || '').trim() const requested = String(d.requested_model || '').trim()
......
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400"> <th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.type') }} {{ t('admin.ops.errorLog.type') }}
</th> </th>
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.endpoint') }}
</th>
<th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400"> <th class="border-b border-gray-200 px-4 py-2.5 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:border-dark-700 dark:text-dark-400">
{{ t('admin.ops.errorLog.platform') }} {{ t('admin.ops.errorLog.platform') }}
</th> </th>
...@@ -42,7 +45,7 @@ ...@@ -42,7 +45,7 @@
</thead> </thead>
<tbody class="divide-y divide-gray-100 dark:divide-dark-700"> <tbody class="divide-y divide-gray-100 dark:divide-dark-700">
<tr v-if="rows.length === 0"> <tr v-if="rows.length === 0">
<td colspan="9" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500"> <td colspan="10" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
{{ t('admin.ops.errorLog.noErrors') }} {{ t('admin.ops.errorLog.noErrors') }}
</td> </td>
</tr> </tr>
...@@ -74,6 +77,18 @@ ...@@ -74,6 +77,18 @@
</span> </span>
</td> </td>
<!-- Endpoint -->
<td class="px-4 py-2">
<div class="max-w-[160px]">
<el-tooltip v-if="log.inbound_endpoint" :content="formatEndpointTooltip(log)" placement="top" :show-after="500">
<span class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
{{ log.inbound_endpoint }}
</span>
</el-tooltip>
<span v-else class="text-xs text-gray-400">-</span>
</div>
</td>
<!-- Platform --> <!-- Platform -->
<td class="whitespace-nowrap px-4 py-2"> <td class="whitespace-nowrap px-4 py-2">
<span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold uppercase text-gray-600 dark:bg-dark-700 dark:text-gray-300"> <span class="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold uppercase text-gray-600 dark:bg-dark-700 dark:text-gray-300">
...@@ -83,7 +98,7 @@ ...@@ -83,7 +98,7 @@
<!-- Model --> <!-- Model -->
<td class="px-4 py-2"> <td class="px-4 py-2">
<div class="max-w-[180px]"> <div class="max-w-[160px]">
<template v-if="hasModelMapping(log)"> <template v-if="hasModelMapping(log)">
<el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500"> <el-tooltip :content="modelMappingTooltip(log)" placement="top" :show-after="500">
<span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300"> <span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
...@@ -149,6 +164,12 @@ ...@@ -149,6 +164,12 @@
> >
{{ log.severity }} {{ log.severity }}
</span> </span>
<span
v-if="log.request_type != null && log.request_type > 0"
class="rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-bold text-gray-600 dark:bg-dark-700 dark:text-gray-300"
>
{{ formatRequestType(log.request_type) }}
</span>
</div> </div>
</td> </td>
...@@ -204,6 +225,13 @@ function isUpstreamRow(log: OpsErrorLog): boolean { ...@@ -204,6 +225,13 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
return phase === 'upstream' && owner === 'provider' return phase === 'upstream' && owner === 'provider'
} }
function formatEndpointTooltip(log: OpsErrorLog): string {
const parts: string[] = []
if (log.inbound_endpoint) parts.push(`Inbound: ${log.inbound_endpoint}`)
if (log.upstream_endpoint) parts.push(`Upstream: ${log.upstream_endpoint}`)
return parts.join('\n') || ''
}
function hasModelMapping(log: OpsErrorLog): boolean { function hasModelMapping(log: OpsErrorLog): boolean {
const requested = String(log.requested_model || '').trim() const requested = String(log.requested_model || '').trim()
const upstream = String(log.upstream_model || '').trim() const upstream = String(log.upstream_model || '').trim()
...@@ -226,6 +254,15 @@ function displayModel(log: OpsErrorLog): string { ...@@ -226,6 +254,15 @@ function displayModel(log: OpsErrorLog): string {
return String(log.model || '').trim() return String(log.model || '').trim()
} }
function formatRequestType(type: number | null | undefined): string {
switch (type) {
case 1: return t('admin.ops.errorLog.requestTypeSync')
case 2: return t('admin.ops.errorLog.requestTypeStream')
case 3: return t('admin.ops.errorLog.requestTypeWs')
default: return ''
}
}
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } { function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
const phase = String(log.phase || '').toLowerCase() const phase = String(log.phase || '').toLowerCase()
const owner = String(log.error_owner || '').toLowerCase() const owner = String(log.error_owner || '').toLowerCase()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment