Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
bd8eadb7
Commit
bd8eadb7
authored
Mar 22, 2026
by
Ethan0x0000
Browse files
feat(ops): enhance error observability with additional context fields and UI updates
parent
7cd38248
Changes
5
Hide whitespace changes
Inline
Side-by-side
frontend/src/api/admin/ops.ts
View file @
bd8eadb7
...
@@ -969,6 +969,13 @@ export interface OpsErrorLog {
...
@@ -969,6 +969,13 @@ export interface OpsErrorLog {
client_ip
?:
string
|
null
client_ip
?:
string
|
null
request_path
?:
string
request_path
?:
string
stream
?:
boolean
stream
?:
boolean
// Error observability context (endpoint + model mapping)
inbound_endpoint
?:
string
upstream_endpoint
?:
string
requested_model
?:
string
upstream_model
?:
string
request_type
?:
number
|
null
}
}
export
interface
OpsErrorDetail
extends
OpsErrorLog
{
export
interface
OpsErrorDetail
extends
OpsErrorLog
{
...
...
frontend/src/i18n/locales/en.ts
View file @
bd8eadb7
...
@@ -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
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
bd8eadb7
...
@@ -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
:
'
路由
'
,
...
...
frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue
View file @
bd8eadb7
...
@@ -59,7 +59,28 @@
...
@@ -59,7 +59,28 @@
<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.model') }}
</div>
<div
class=
"text-xs font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.errorDetail.model') }}
</div>
<div
class=
"mt-1 text-sm font-medium text-gray-900 dark:text-white"
>
<div
class=
"mt-1 text-sm font-medium text-gray-900 dark:text-white"
>
{{ detail.model || '—' }}
<
template
v-if=
"detail.requested_model && detail.upstream_model && detail.requested_model !== detail.upstream_model"
>
<span
class=
"font-mono"
>
{{
detail
.
requested_model
}}
</span>
<span
class=
"mx-1 text-gray-400"
>
→
</span>
<span
class=
"font-mono text-primary-600 dark:text-primary-400"
>
{{
detail
.
upstream_model
}}
</span>
</
template
>
<
template
v-else
>
{{
detail
.
requested_model
||
detail
.
model
||
'
—
'
}}
</
template
>
</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>
</div>
...
@@ -72,6 +93,13 @@
...
@@ -72,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"
>
...
@@ -213,6 +241,15 @@ function isUpstreamError(d: OpsErrorDetail | null): boolean {
...
@@ -213,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
'
)
}
}
const
correlatedUpstream
=
ref
<
OpsErrorDetail
[]
>
([])
const
correlatedUpstream
=
ref
<
OpsErrorDetail
[]
>
([])
const
correlatedUpstreamLoading
=
ref
(
false
)
const
correlatedUpstreamLoading
=
ref
(
false
)
...
...
frontend/src/views/admin/ops/components/OpsErrorLogTable.vue
View file @
bd8eadb7
...
@@ -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,11 +98,22 @@
...
@@ -83,11 +98,22 @@
<!-- Model -->
<!-- Model -->
<td
class=
"px-4 py-2"
>
<td
class=
"px-4 py-2"
>
<div
class=
"max-w-[120px] truncate"
:title=
"log.model"
>
<div
class=
"max-w-[160px]"
>
<span
v-if=
"log.model"
class=
"font-mono text-[11px] text-gray-700 dark:text-gray-300"
>
<template
v-if=
"log.requested_model && log.upstream_model && log.requested_model !== log.upstream_model"
>
{{
log
.
model
}}
<el-tooltip
:content=
"`$
{log.requested_model} → ${log.upstream_model}`" placement="top" :show-after="500">
</span>
<span
class=
"flex items-center gap-1 truncate font-mono text-[11px] text-gray-700 dark:text-gray-300"
>
<span
v-else
class=
"text-xs text-gray-400"
>
-
</span>
<span
class=
"truncate"
>
{{
log
.
requested_model
}}
</span>
<span
class=
"flex-shrink-0 text-gray-400"
>
→
</span>
<span
class=
"truncate text-primary-600 dark:text-primary-400"
>
{{
log
.
upstream_model
}}
</span>
</span>
</el-tooltip>
</
template
>
<
template
v-else
>
<span
v-if=
"displayModel(log)"
class=
"truncate font-mono text-[11px] text-gray-700 dark:text-gray-300"
:title=
"displayModel(log)"
>
{{
displayModel
(
log
)
}}
</span>
<span
v-else
class=
"text-xs text-gray-400"
>
-
</span>
</
template
>
</div>
</div>
</td>
</td>
...
@@ -138,6 +164,12 @@
...
@@ -138,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>
...
@@ -193,6 +225,26 @@ function isUpstreamRow(log: OpsErrorLog): boolean {
...
@@ -193,6 +225,26 @@ 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
displayModel
(
log
:
OpsErrorLog
):
string
{
return
log
.
requested_model
||
log
.
model
||
''
}
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
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment