feat(ops): add endpoint/model/request_type fields to error log structs + safeUpstreamURL
This commit is contained in:
@@ -62,6 +62,12 @@ type OpsErrorLog struct {
|
|||||||
ClientIP *string `json:"client_ip"`
|
ClientIP *string `json:"client_ip"`
|
||||||
RequestPath string `json:"request_path"`
|
RequestPath string `json:"request_path"`
|
||||||
Stream bool `json:"stream"`
|
Stream bool `json:"stream"`
|
||||||
|
|
||||||
|
InboundEndpoint string `json:"inbound_endpoint"`
|
||||||
|
UpstreamEndpoint string `json:"upstream_endpoint"`
|
||||||
|
RequestedModel string `json:"requested_model"`
|
||||||
|
UpstreamModel string `json:"upstream_model"`
|
||||||
|
RequestType *int16 `json:"request_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpsErrorLogDetail struct {
|
type OpsErrorLogDetail struct {
|
||||||
|
|||||||
@@ -79,6 +79,17 @@ type OpsInsertErrorLogInput struct {
|
|||||||
Model string
|
Model string
|
||||||
RequestPath string
|
RequestPath string
|
||||||
Stream bool
|
Stream bool
|
||||||
|
// InboundEndpoint is the normalized client-facing API endpoint path, e.g. /v1/chat/completions.
|
||||||
|
InboundEndpoint string
|
||||||
|
// UpstreamEndpoint is the normalized upstream endpoint path, e.g. /v1/responses.
|
||||||
|
UpstreamEndpoint string
|
||||||
|
// RequestedModel is the client-requested model name before mapping.
|
||||||
|
RequestedModel string
|
||||||
|
// UpstreamModel is the actual model sent to upstream after mapping. Empty means no mapping.
|
||||||
|
UpstreamModel string
|
||||||
|
// RequestType is the granular request type: 0=unknown, 1=sync, 2=stream, 3=ws_v2.
|
||||||
|
// Matches service.RequestType enum semantics from usage_log.go.
|
||||||
|
RequestType *int16
|
||||||
UserAgent string
|
UserAgent string
|
||||||
|
|
||||||
ErrorPhase string
|
ErrorPhase string
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ type OpsUpstreamErrorEvent struct {
|
|||||||
UpstreamStatusCode int `json:"upstream_status_code,omitempty"`
|
UpstreamStatusCode int `json:"upstream_status_code,omitempty"`
|
||||||
UpstreamRequestID string `json:"upstream_request_id,omitempty"`
|
UpstreamRequestID string `json:"upstream_request_id,omitempty"`
|
||||||
|
|
||||||
|
// UpstreamURL is the actual upstream URL that was called (host + path, query/fragment stripped).
|
||||||
|
// Helps debug 404/routing errors by showing which endpoint was targeted.
|
||||||
|
UpstreamURL string `json:"upstream_url,omitempty"`
|
||||||
|
|
||||||
// Best-effort upstream request capture (sanitized+trimmed).
|
// Best-effort upstream request capture (sanitized+trimmed).
|
||||||
// Required for retrying a specific upstream attempt.
|
// Required for retrying a specific upstream attempt.
|
||||||
UpstreamRequestBody string `json:"upstream_request_body,omitempty"`
|
UpstreamRequestBody string `json:"upstream_request_body,omitempty"`
|
||||||
@@ -119,6 +123,7 @@ func appendOpsUpstreamError(c *gin.Context, ev OpsUpstreamErrorEvent) {
|
|||||||
ev.UpstreamRequestBody = strings.TrimSpace(ev.UpstreamRequestBody)
|
ev.UpstreamRequestBody = strings.TrimSpace(ev.UpstreamRequestBody)
|
||||||
ev.UpstreamResponseBody = strings.TrimSpace(ev.UpstreamResponseBody)
|
ev.UpstreamResponseBody = strings.TrimSpace(ev.UpstreamResponseBody)
|
||||||
ev.Kind = strings.TrimSpace(ev.Kind)
|
ev.Kind = strings.TrimSpace(ev.Kind)
|
||||||
|
ev.UpstreamURL = strings.TrimSpace(ev.UpstreamURL)
|
||||||
ev.Message = strings.TrimSpace(ev.Message)
|
ev.Message = strings.TrimSpace(ev.Message)
|
||||||
ev.Detail = strings.TrimSpace(ev.Detail)
|
ev.Detail = strings.TrimSpace(ev.Detail)
|
||||||
if ev.Message != "" {
|
if ev.Message != "" {
|
||||||
@@ -205,3 +210,19 @@ func ParseOpsUpstreamErrors(raw string) ([]*OpsUpstreamErrorEvent, error) {
|
|||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeUpstreamURL returns scheme + host + path from a URL, stripping query/fragment
|
||||||
|
// to avoid leaking sensitive query parameters (e.g. OAuth tokens).
|
||||||
|
func safeUpstreamURL(rawURL string) string {
|
||||||
|
rawURL = strings.TrimSpace(rawURL)
|
||||||
|
if rawURL == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if idx := strings.IndexByte(rawURL, '?'); idx >= 0 {
|
||||||
|
rawURL = rawURL[:idx]
|
||||||
|
}
|
||||||
|
if idx := strings.IndexByte(rawURL, '#'); idx >= 0 {
|
||||||
|
rawURL = rawURL[:idx]
|
||||||
|
}
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-- Ops error logs: add endpoint, model mapping, and request_type fields
|
||||||
|
-- to match usage_logs observability coverage.
|
||||||
|
--
|
||||||
|
-- All columns are nullable with no default to preserve backward compatibility
|
||||||
|
-- with existing rows.
|
||||||
|
|
||||||
|
SET LOCAL lock_timeout = '5s';
|
||||||
|
SET LOCAL statement_timeout = '10min';
|
||||||
|
|
||||||
|
-- 1) Standardized endpoint paths (analogous to usage_logs.inbound_endpoint / upstream_endpoint)
|
||||||
|
ALTER TABLE ops_error_logs
|
||||||
|
ADD COLUMN IF NOT EXISTS inbound_endpoint VARCHAR(256),
|
||||||
|
ADD COLUMN IF NOT EXISTS upstream_endpoint VARCHAR(256);
|
||||||
|
|
||||||
|
-- 2) Model mapping fields (analogous to usage_logs.requested_model / upstream_model)
|
||||||
|
ALTER TABLE ops_error_logs
|
||||||
|
ADD COLUMN IF NOT EXISTS requested_model VARCHAR(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS upstream_model VARCHAR(100);
|
||||||
|
|
||||||
|
-- 3) Granular request type enum (analogous to usage_logs.request_type: 0=unknown, 1=sync, 2=stream, 3=ws_v2)
|
||||||
|
ALTER TABLE ops_error_logs
|
||||||
|
ADD COLUMN IF NOT EXISTS request_type SMALLINT;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN ops_error_logs.inbound_endpoint IS 'Normalized client-facing API endpoint path, e.g. /v1/chat/completions. Populated from InboundEndpointMiddleware.';
|
||||||
|
COMMENT ON COLUMN ops_error_logs.upstream_endpoint IS 'Normalized upstream endpoint path derived from platform, e.g. /v1/responses.';
|
||||||
|
COMMENT ON COLUMN ops_error_logs.requested_model IS 'Client-requested model name before mapping (raw from request body).';
|
||||||
|
COMMENT ON COLUMN ops_error_logs.upstream_model IS 'Actual model sent to upstream provider after mapping. NULL means no mapping applied.';
|
||||||
|
COMMENT ON COLUMN ops_error_logs.request_type IS 'Request type enum: 0=unknown, 1=sync, 2=stream, 3=ws_v2. Matches usage_logs.request_type semantics.';
|
||||||
Reference in New Issue
Block a user