Skip to content

Commit bd8eadb

Browse files
committed
feat(ops): enhance error observability with additional context fields and UI updates
1 parent 7cd3824 commit bd8eadb

5 files changed

Lines changed: 135 additions & 9 deletions

File tree

frontend/src/api/admin/ops.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,13 @@ export interface OpsErrorLog {
969969
client_ip?: string | null
970970
request_path?: string
971971
stream?: boolean
972+
973+
// Error observability context (endpoint + model mapping)
974+
inbound_endpoint?: string
975+
upstream_endpoint?: string
976+
requested_model?: string
977+
upstream_model?: string
978+
request_type?: number | null
972979
}
973980

974981
export interface OpsErrorDetail extends OpsErrorLog {

frontend/src/i18n/locales/en.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3486,7 +3486,12 @@ export default {
34863486
typeRequest: 'Request',
34873487
typeAuth: 'Auth',
34883488
typeRouting: 'Routing',
3489-
typeInternal: 'Internal'
3489+
typeInternal: 'Internal',
3490+
endpoint: 'Endpoint',
3491+
requestType: 'Type',
3492+
requestTypeSync: 'Sync',
3493+
requestTypeStream: 'Stream',
3494+
requestTypeWs: 'WS'
34903495
},
34913496
// Error Details Modal
34923497
errorDetails: {
@@ -3572,6 +3577,16 @@ export default {
35723577
latency: 'Request Duration',
35733578
businessLimited: 'Business Limited',
35743579
requestPath: 'Request Path',
3580+
inboundEndpoint: 'Inbound Endpoint',
3581+
upstreamEndpoint: 'Upstream Endpoint',
3582+
requestedModel: 'Requested Model',
3583+
upstreamModel: 'Upstream Model',
3584+
requestType: 'Request Type',
3585+
requestTypeUnknown: 'Unknown',
3586+
requestTypeSync: 'Sync',
3587+
requestTypeStream: 'Stream',
3588+
requestTypeWs: 'WebSocket',
3589+
modelMapping: 'Model Mapping',
35753590
timings: 'Timings',
35763591
auth: 'Auth',
35773592
routing: 'Routing',

frontend/src/i18n/locales/zh.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3651,7 +3651,12 @@ export default {
36513651
typeRequest: '请求',
36523652
typeAuth: '认证',
36533653
typeRouting: '路由',
3654-
typeInternal: '内部'
3654+
typeInternal: '内部',
3655+
endpoint: '端点',
3656+
requestType: '类型',
3657+
requestTypeSync: '同步',
3658+
requestTypeStream: '流式',
3659+
requestTypeWs: 'WS'
36553660
},
36563661
// Error Details Modal
36573662
errorDetails: {
@@ -3737,6 +3742,16 @@ export default {
37373742
latency: '请求时长',
37383743
businessLimited: '业务限制',
37393744
requestPath: '请求路径',
3745+
inboundEndpoint: '入站端点',
3746+
upstreamEndpoint: '上游端点',
3747+
requestedModel: '请求模型',
3748+
upstreamModel: '上游模型',
3749+
requestType: '请求类型',
3750+
requestTypeUnknown: '未知',
3751+
requestTypeSync: '同步',
3752+
requestTypeStream: '流式',
3753+
requestTypeWs: 'WebSocket',
3754+
modelMapping: '模型映射',
37403755
timings: '时序信息',
37413756
auth: '认证',
37423757
routing: '路由',

frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,28 @@
5959
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
6060
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.model') }}</div>
6161
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
62-
{{ detail.model || '—' }}
62+
<template v-if="detail.requested_model && detail.upstream_model && detail.requested_model !== detail.upstream_model">
63+
<span class="font-mono">{{ detail.requested_model }}</span>
64+
<span class="mx-1 text-gray-400">→</span>
65+
<span class="font-mono text-primary-600 dark:text-primary-400">{{ detail.upstream_model }}</span>
66+
</template>
67+
<template v-else>
68+
{{ detail.requested_model || detail.model || '—' }}
69+
</template>
70+
</div>
71+
</div>
72+
73+
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
74+
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.inboundEndpoint') }}</div>
75+
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
76+
{{ detail.inbound_endpoint || '—' }}
77+
</div>
78+
</div>
79+
80+
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
81+
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.upstreamEndpoint') }}</div>
82+
<div class="mt-1 break-all font-mono text-sm font-medium text-gray-900 dark:text-white">
83+
{{ detail.upstream_endpoint || '—' }}
6384
</div>
6485
</div>
6586

@@ -72,6 +93,13 @@
7293
</div>
7394
</div>
7495

96+
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
97+
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.requestType') }}</div>
98+
<div class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
99+
{{ formatRequestTypeLabel(detail.request_type) }}
100+
</div>
101+
</div>
102+
75103
<div class="rounded-xl bg-gray-50 p-4 dark:bg-dark-900">
76104
<div class="text-xs font-bold uppercase tracking-wider text-gray-400">{{ t('admin.ops.errorDetail.message') }}</div>
77105
<div class="mt-1 truncate text-sm font-medium text-gray-900 dark:text-white" :title="detail.message">
@@ -213,6 +241,15 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
213241
return phase === 'upstream' && owner === 'provider'
214242
}
215243
244+
function formatRequestTypeLabel(type: number | null | undefined): string {
245+
switch (type) {
246+
case 1: return t('admin.ops.errorDetail.requestTypeSync')
247+
case 2: return t('admin.ops.errorDetail.requestTypeStream')
248+
case 3: return t('admin.ops.errorDetail.requestTypeWs')
249+
default: return t('admin.ops.errorDetail.requestTypeUnknown')
250+
}
251+
}
252+
216253
const correlatedUpstream = ref<OpsErrorDetail[]>([])
217254
const correlatedUpstreamLoading = ref(false)
218255

frontend/src/views/admin/ops/components/OpsErrorLogTable.vue

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
<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">
1818
{{ t('admin.ops.errorLog.type') }}
1919
</th>
20+
<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">
21+
{{ t('admin.ops.errorLog.endpoint') }}
22+
</th>
2023
<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">
2124
{{ t('admin.ops.errorLog.platform') }}
2225
</th>
@@ -42,7 +45,7 @@
4245
</thead>
4346
<tbody class="divide-y divide-gray-100 dark:divide-dark-700">
4447
<tr v-if="rows.length === 0">
45-
<td colspan="9" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
48+
<td colspan="10" class="py-12 text-center text-sm text-gray-400 dark:text-dark-500">
4649
{{ t('admin.ops.errorLog.noErrors') }}
4750
</td>
4851
</tr>
@@ -74,6 +77,18 @@
7477
</span>
7578
</td>
7679

80+
<!-- Endpoint -->
81+
<td class="px-4 py-2">
82+
<div class="max-w-[160px]">
83+
<el-tooltip v-if="log.inbound_endpoint" :content="formatEndpointTooltip(log)" placement="top" :show-after="500">
84+
<span class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
85+
{{ log.inbound_endpoint }}
86+
</span>
87+
</el-tooltip>
88+
<span v-else class="text-xs text-gray-400">-</span>
89+
</div>
90+
</td>
91+
7792
<!-- Platform -->
7893
<td class="whitespace-nowrap px-4 py-2">
7994
<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,11 +98,22 @@
8398

8499
<!-- Model -->
85100
<td class="px-4 py-2">
86-
<div class="max-w-[120px] truncate" :title="log.model">
87-
<span v-if="log.model" class="font-mono text-[11px] text-gray-700 dark:text-gray-300">
88-
{{ log.model }}
89-
</span>
90-
<span v-else class="text-xs text-gray-400">-</span>
101+
<div class="max-w-[160px]">
102+
<template v-if="log.requested_model && log.upstream_model && log.requested_model !== log.upstream_model">
103+
<el-tooltip :content="`${log.requested_model} → ${log.upstream_model}`" placement="top" :show-after="500">
104+
<span class="flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300">
105+
<span class="truncate">{{ log.requested_model }}</span>
106+
<span class="flex-shrink-0 text-gray-400">→</span>
107+
<span class="truncate text-primary-600 dark:text-primary-400">{{ log.upstream_model }}</span>
108+
</span>
109+
</el-tooltip>
110+
</template>
111+
<template v-else>
112+
<span v-if="displayModel(log)" class="truncate font-mono text-[11px] text-gray-700 dark:text-gray-300" :title="displayModel(log)">
113+
{{ displayModel(log) }}
114+
</span>
115+
<span v-else class="text-xs text-gray-400">-</span>
116+
</template>
91117
</div>
92118
</td>
93119

@@ -138,6 +164,12 @@
138164
>
139165
{{ log.severity }}
140166
</span>
167+
<span
168+
v-if="log.request_type != null && log.request_type > 0"
169+
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"
170+
>
171+
{{ formatRequestType(log.request_type) }}
172+
</span>
141173
</div>
142174
</td>
143175

@@ -193,6 +225,26 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
193225
return phase === 'upstream' && owner === 'provider'
194226
}
195227
228+
function formatEndpointTooltip(log: OpsErrorLog): string {
229+
const parts: string[] = []
230+
if (log.inbound_endpoint) parts.push(`Inbound: ${log.inbound_endpoint}`)
231+
if (log.upstream_endpoint) parts.push(`Upstream: ${log.upstream_endpoint}`)
232+
return parts.join('\n') || ''
233+
}
234+
235+
function displayModel(log: OpsErrorLog): string {
236+
return log.requested_model || log.model || ''
237+
}
238+
239+
function formatRequestType(type: number | null | undefined): string {
240+
switch (type) {
241+
case 1: return t('admin.ops.errorLog.requestTypeSync')
242+
case 2: return t('admin.ops.errorLog.requestTypeStream')
243+
case 3: return t('admin.ops.errorLog.requestTypeWs')
244+
default: return ''
245+
}
246+
}
247+
196248
function getTypeBadge(log: OpsErrorLog): { label: string; className: string } {
197249
const phase = String(log.phase || '').toLowerCase()
198250
const owner = String(log.error_owner || '').toLowerCase()

0 commit comments

Comments
 (0)