- 修复 ops_repo_latency_histogram_buckets.go 中另一个函数的 WriteString 未检查错误 - 修复 ops_repo_request_details.go 和 ops_repo_trends.go 中的 Rows.Close 未检查错误 - 修复 ops_alert_models.go, ops_cleanup_service.go, ops_request_details.go 的格式化问题 - 移除 ops_retry.go 中未使用的 status 字段 - 修复 maxTime 函数重复声明(将测试文件中的函数重命名为 testMaxTime)
152 lines
3.3 KiB
Go
152 lines
3.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
type OpsRequestKind string
|
|
|
|
const (
|
|
OpsRequestKindSuccess OpsRequestKind = "success"
|
|
OpsRequestKindError OpsRequestKind = "error"
|
|
)
|
|
|
|
// OpsRequestDetail is a request-level view across success (usage_logs) and error (ops_error_logs).
|
|
// It powers "request drilldown" UIs without exposing full request bodies for successful requests.
|
|
type OpsRequestDetail struct {
|
|
Kind OpsRequestKind `json:"kind"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
RequestID string `json:"request_id"`
|
|
|
|
Platform string `json:"platform,omitempty"`
|
|
Model string `json:"model,omitempty"`
|
|
|
|
DurationMs *int `json:"duration_ms,omitempty"`
|
|
StatusCode *int `json:"status_code,omitempty"`
|
|
|
|
// When Kind == "error", ErrorID links to /admin/ops/errors/:id.
|
|
ErrorID *int64 `json:"error_id,omitempty"`
|
|
|
|
Phase string `json:"phase,omitempty"`
|
|
Severity string `json:"severity,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
|
|
UserID *int64 `json:"user_id,omitempty"`
|
|
APIKeyID *int64 `json:"api_key_id,omitempty"`
|
|
AccountID *int64 `json:"account_id,omitempty"`
|
|
GroupID *int64 `json:"group_id,omitempty"`
|
|
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type OpsRequestDetailFilter struct {
|
|
StartTime *time.Time
|
|
EndTime *time.Time
|
|
|
|
// kind: success|error|all
|
|
Kind string
|
|
|
|
Platform string
|
|
GroupID *int64
|
|
|
|
UserID *int64
|
|
APIKeyID *int64
|
|
AccountID *int64
|
|
|
|
Model string
|
|
RequestID string
|
|
Query string
|
|
|
|
MinDurationMs *int
|
|
MaxDurationMs *int
|
|
|
|
// Sort: created_at_desc (default) or duration_desc.
|
|
Sort string
|
|
|
|
Page int
|
|
PageSize int
|
|
}
|
|
|
|
func (f *OpsRequestDetailFilter) Normalize() (page, pageSize int, startTime, endTime time.Time) {
|
|
page = 1
|
|
pageSize = 50
|
|
endTime = time.Now()
|
|
startTime = endTime.Add(-1 * time.Hour)
|
|
|
|
if f == nil {
|
|
return page, pageSize, startTime, endTime
|
|
}
|
|
|
|
if f.Page > 0 {
|
|
page = f.Page
|
|
}
|
|
if f.PageSize > 0 {
|
|
pageSize = f.PageSize
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
|
|
if f.EndTime != nil {
|
|
endTime = *f.EndTime
|
|
}
|
|
if f.StartTime != nil {
|
|
startTime = *f.StartTime
|
|
} else if f.EndTime != nil {
|
|
startTime = endTime.Add(-1 * time.Hour)
|
|
}
|
|
|
|
if startTime.After(endTime) {
|
|
startTime, endTime = endTime, startTime
|
|
}
|
|
|
|
return page, pageSize, startTime, endTime
|
|
}
|
|
|
|
type OpsRequestDetailList struct {
|
|
Items []*OpsRequestDetail `json:"items"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
func (s *OpsService) ListRequestDetails(ctx context.Context, filter *OpsRequestDetailFilter) (*OpsRequestDetailList, error) {
|
|
if err := s.RequireMonitoringEnabled(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if s.opsRepo == nil {
|
|
return &OpsRequestDetailList{
|
|
Items: []*OpsRequestDetail{},
|
|
Total: 0,
|
|
Page: 1,
|
|
PageSize: 50,
|
|
}, nil
|
|
}
|
|
|
|
page, pageSize, startTime, endTime := filter.Normalize()
|
|
filterCopy := &OpsRequestDetailFilter{}
|
|
if filter != nil {
|
|
*filterCopy = *filter
|
|
}
|
|
filterCopy.Page = page
|
|
filterCopy.PageSize = pageSize
|
|
filterCopy.StartTime = &startTime
|
|
filterCopy.EndTime = &endTime
|
|
|
|
items, total, err := s.opsRepo.ListRequestDetails(ctx, filterCopy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if items == nil {
|
|
items = []*OpsRequestDetail{}
|
|
}
|
|
|
|
return &OpsRequestDetailList{
|
|
Items: items,
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}, nil
|
|
}
|