Skip to content

Commit 8c4a217

Browse files
committed
feat(ops): add endpoint/model/request_type fields to error log structs + safeUpstreamURL
1 parent bda7c39 commit 8c4a217

4 files changed

Lines changed: 66 additions & 0 deletions

File tree

backend/internal/service/ops_models.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ type OpsErrorLog struct {
6262
ClientIP *string `json:"client_ip"`
6363
RequestPath string `json:"request_path"`
6464
Stream bool `json:"stream"`
65+
66+
InboundEndpoint string `json:"inbound_endpoint"`
67+
UpstreamEndpoint string `json:"upstream_endpoint"`
68+
RequestedModel string `json:"requested_model"`
69+
UpstreamModel string `json:"upstream_model"`
70+
RequestType *int16 `json:"request_type"`
6571
}
6672

6773
type OpsErrorLogDetail struct {

backend/internal/service/ops_port.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ type OpsInsertErrorLogInput struct {
7979
Model string
8080
RequestPath string
8181
Stream bool
82+
// InboundEndpoint is the normalized client-facing API endpoint path, e.g. /v1/chat/completions.
83+
InboundEndpoint string
84+
// UpstreamEndpoint is the normalized upstream endpoint path, e.g. /v1/responses.
85+
UpstreamEndpoint string
86+
// RequestedModel is the client-requested model name before mapping.
87+
RequestedModel string
88+
// UpstreamModel is the actual model sent to upstream after mapping. Empty means no mapping.
89+
UpstreamModel string
90+
// RequestType is the granular request type: 0=unknown, 1=sync, 2=stream, 3=ws_v2.
91+
// Matches service.RequestType enum semantics from usage_log.go.
92+
RequestType *int16
8293
UserAgent string
8394

8495
ErrorPhase string

backend/internal/service/ops_upstream_context.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ type OpsUpstreamErrorEvent struct {
9393
UpstreamStatusCode int `json:"upstream_status_code,omitempty"`
9494
UpstreamRequestID string `json:"upstream_request_id,omitempty"`
9595

96+
// UpstreamURL is the actual upstream URL that was called (host + path, query/fragment stripped).
97+
// Helps debug 404/routing errors by showing which endpoint was targeted.
98+
UpstreamURL string `json:"upstream_url,omitempty"`
99+
96100
// Best-effort upstream request capture (sanitized+trimmed).
97101
// Required for retrying a specific upstream attempt.
98102
UpstreamRequestBody string `json:"upstream_request_body,omitempty"`
@@ -119,6 +123,7 @@ func appendOpsUpstreamError(c *gin.Context, ev OpsUpstreamErrorEvent) {
119123
ev.UpstreamRequestBody = strings.TrimSpace(ev.UpstreamRequestBody)
120124
ev.UpstreamResponseBody = strings.TrimSpace(ev.UpstreamResponseBody)
121125
ev.Kind = strings.TrimSpace(ev.Kind)
126+
ev.UpstreamURL = strings.TrimSpace(ev.UpstreamURL)
122127
ev.Message = strings.TrimSpace(ev.Message)
123128
ev.Detail = strings.TrimSpace(ev.Detail)
124129
if ev.Message != "" {
@@ -205,3 +210,19 @@ func ParseOpsUpstreamErrors(raw string) ([]*OpsUpstreamErrorEvent, error) {
205210
}
206211
return out, nil
207212
}
213+
214+
// safeUpstreamURL returns scheme + host + path from a URL, stripping query/fragment
215+
// to avoid leaking sensitive query parameters (e.g. OAuth tokens).
216+
func safeUpstreamURL(rawURL string) string {
217+
rawURL = strings.TrimSpace(rawURL)
218+
if rawURL == "" {
219+
return ""
220+
}
221+
if idx := strings.IndexByte(rawURL, '?'); idx >= 0 {
222+
rawURL = rawURL[:idx]
223+
}
224+
if idx := strings.IndexByte(rawURL, '#'); idx >= 0 {
225+
rawURL = rawURL[:idx]
226+
}
227+
return rawURL
228+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-- Ops error logs: add endpoint, model mapping, and request_type fields
2+
-- to match usage_logs observability coverage.
3+
--
4+
-- All columns are nullable with no default to preserve backward compatibility
5+
-- with existing rows.
6+
7+
SET LOCAL lock_timeout = '5s';
8+
SET LOCAL statement_timeout = '10min';
9+
10+
-- 1) Standardized endpoint paths (analogous to usage_logs.inbound_endpoint / upstream_endpoint)
11+
ALTER TABLE ops_error_logs
12+
ADD COLUMN IF NOT EXISTS inbound_endpoint VARCHAR(256),
13+
ADD COLUMN IF NOT EXISTS upstream_endpoint VARCHAR(256);
14+
15+
-- 2) Model mapping fields (analogous to usage_logs.requested_model / upstream_model)
16+
ALTER TABLE ops_error_logs
17+
ADD COLUMN IF NOT EXISTS requested_model VARCHAR(100),
18+
ADD COLUMN IF NOT EXISTS upstream_model VARCHAR(100);
19+
20+
-- 3) Granular request type enum (analogous to usage_logs.request_type: 0=unknown, 1=sync, 2=stream, 3=ws_v2)
21+
ALTER TABLE ops_error_logs
22+
ADD COLUMN IF NOT EXISTS request_type SMALLINT;
23+
24+
COMMENT ON COLUMN ops_error_logs.inbound_endpoint IS 'Normalized client-facing API endpoint path, e.g. /v1/chat/completions. Populated from InboundEndpointMiddleware.';
25+
COMMENT ON COLUMN ops_error_logs.upstream_endpoint IS 'Normalized upstream endpoint path derived from platform, e.g. /v1/responses.';
26+
COMMENT ON COLUMN ops_error_logs.requested_model IS 'Client-requested model name before mapping (raw from request body).';
27+
COMMENT ON COLUMN ops_error_logs.upstream_model IS 'Actual model sent to upstream provider after mapping. NULL means no mapping applied.';
28+
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.';

0 commit comments

Comments
 (0)