feat: 十轮网关优化 - 安全加固/可观测性/性能/可靠性
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / security-scan (push) Has been cancelled

- SSE Keepalive Ping (15s心跳防止代理断连)
- Timing HTTP 头 (X-Timing-Queue/Inference/Total-Ms)
- Adapter Request-ID 传播到后端
- Session 清理日志回调
- Server 安全加固 (ReadHeaderTimeout/MaxHeaderBytes 防 slowloris)
- Usage Tracker 数据保留清理 (retentionDays + 定期清理)
- Config Reload 后 Adapter Registry 更新 (RegisterIfAbsent + RWMutex)
- Rate Limiter 空闲 Bucket 清理 (30分钟过期)
- Shutdown Drain 超时可配置 (ShutdownDrainSeconds)
- Config 模型字段校验增强 (provider/endpoint/actual_model)
- Auth 过期 Key 自动清理 (5分钟扫描)
- Admin API Rate Limiting
- Adapter Health Check 独立超时 (每个 adapter 3s)
- TCP 连接阶段超时 (DialContext 5s + KeepAlive 30s)
- 幂等键缓存、审计日志、Gzip 中间件、CORS Expose Headers
- Backpressure 响应头、熔断器 Prometheus 指标
- 连接池优化、Trace-ID 全链路传播
This commit is contained in:
selfrelease
2026-08-03 15:43:11 +08:00
parent e7e98271d4
commit da9c8334d8
27 changed files with 3002 additions and 309 deletions
+100
View File
@@ -0,0 +1,100 @@
package observability
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"sync"
"time"
)
// AuditEntry 审计日志条目,记录关键管理操作的详细信息。
type AuditEntry struct {
Timestamp string `json:"timestamp"`
Actor string `json:"actor"` // 操作者 app_id
Action string `json:"action"` // 操作类型
Resource string `json:"resource"` // 操作资源
ResourceID string `json:"resource_id"` // 资源 ID
Method string `json:"method"` // HTTP 方法
Path string `json:"path"` // 请求路径
IP string `json:"ip"` // 客户端 IP
Status int `json:"status"` // HTTP 响应码
Details map[string]interface{} `json:"details,omitempty"`
}
// AuditLogger 审计日志记录器,将关键操作写入独立日志文件。
type AuditLogger struct {
mu sync.Mutex
file *os.File
logger *Logger
}
// NewAuditLogger 创建审计日志记录器,日志写入指定目录下的 audit.log 文件。
func NewAuditLogger(dir string, logger *Logger) *AuditLogger {
auditPath := filepath.Join(dir, "audit.log")
file, err := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
logger.Warn("failed to open audit log file, audit logs will go to stderr",
F().Event("audit_log_init_failed").Reason(err.Error()))
return &AuditLogger{file: nil, logger: logger}
}
return &AuditLogger{file: file, logger: logger}
}
// Record 记录一条审计日志。
func (a *AuditLogger) Record(entry AuditEntry) {
if entry.Timestamp == "" {
entry.Timestamp = time.Now().UTC().Format(time.RFC3339Nano)
}
data, err := json.Marshal(entry)
if err != nil {
a.logger.Error("audit log marshal error", F().Event("audit_marshal_error").Reason(err.Error()))
return
}
a.mu.Lock()
defer a.mu.Unlock()
if a.file != nil {
fmt.Fprintln(a.file, string(data))
} else {
fmt.Fprintln(os.Stderr, string(data))
}
}
// RecordFromRequest 从 HTTP 请求中提取信息并记录审计日志。
func (a *AuditLogger) RecordFromRequest(r *http.Request, actor, action, resource, resourceID string, status int, details map[string]interface{}) {
entry := AuditEntry{
Actor: actor,
Action: action,
Resource: resource,
ResourceID: resourceID,
Method: r.Method,
Path: r.URL.Path,
IP: extractIP(r),
Status: status,
Details: details,
}
a.Record(entry)
}
// Close 关闭审计日志文件。
func (a *AuditLogger) Close() {
if a.file != nil {
a.file.Close()
}
}
// extractIP 从请求中提取客户端 IP 地址。
func extractIP(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
return xff
}
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return xri
}
return r.RemoteAddr
}
+49
View File
@@ -27,6 +27,7 @@ type Metrics struct {
runningTasks int64
activeSessions int64
backpressureLevel int64
breakerState int64 // 0=closed, 1=open, 2=half_open
// Histograms (simplified as buckets)
gatewayLatencyBuckets map[string]int64
@@ -110,6 +111,11 @@ func (m *Metrics) SetBackpressureLevel(level int) {
atomic.StoreInt64(&m.backpressureLevel, int64(level))
}
// SetBreakerState 设置熔断器状态 gauge0=closed, 1=open, 2=half_open)。
func (m *Metrics) SetBreakerState(state int) {
atomic.StoreInt64(&m.breakerState, int64(state))
}
// ObserveGatewayLatency records gateway latency in a histogram bucket.
func (m *Metrics) ObserveGatewayLatency(ms int64) {
bucket := latencyBucket(ms)
@@ -142,34 +148,77 @@ func (m *Metrics) Handler() http.HandlerFunc {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
// Counters
fmt.Fprintf(w, "# HELP edgeai_requests_total Total requests by status\n")
fmt.Fprintf(w, "# TYPE edgeai_requests_total counter\n")
m.mu.RLock()
for key, val := range m.requestsTotal {
fmt.Fprintf(w, "edgeai_requests_total{%s} %d\n", key, val)
}
fmt.Fprintf(w, "# HELP edgeai_tasks_total Total tasks by final state\n")
fmt.Fprintf(w, "# TYPE edgeai_tasks_total counter\n")
for key, val := range m.tasksTotal {
fmt.Fprintf(w, "edgeai_tasks_total{%s} %d\n", key, val)
}
m.mu.RUnlock()
fmt.Fprintf(w, "# HELP edgeai_tokens_input_total Total input tokens processed\n")
fmt.Fprintf(w, "# TYPE edgeai_tokens_input_total counter\n")
fmt.Fprintf(w, "edgeai_tokens_input_total %d\n", atomic.LoadInt64(&m.tokensInputTotal))
fmt.Fprintf(w, "# HELP edgeai_tokens_output_total Total output tokens generated\n")
fmt.Fprintf(w, "# TYPE edgeai_tokens_output_total counter\n")
fmt.Fprintf(w, "edgeai_tokens_output_total %d\n", atomic.LoadInt64(&m.tokensOutputTotal))
fmt.Fprintf(w, "# HELP edgeai_cancellations_total Total cancelled requests\n")
fmt.Fprintf(w, "# TYPE edgeai_cancellations_total counter\n")
fmt.Fprintf(w, "edgeai_cancellations_total %d\n", atomic.LoadInt64(&m.cancellationsTotal))
fmt.Fprintf(w, "# HELP edgeai_queue_timeouts_total Total queue timeouts\n")
fmt.Fprintf(w, "# TYPE edgeai_queue_timeouts_total counter\n")
fmt.Fprintf(w, "edgeai_queue_timeouts_total %d\n", atomic.LoadInt64(&m.queueTimeoutsTotal))
fmt.Fprintf(w, "# HELP edgeai_first_token_timeouts_total Total first token timeouts\n")
fmt.Fprintf(w, "# TYPE edgeai_first_token_timeouts_total counter\n")
fmt.Fprintf(w, "edgeai_first_token_timeouts_total %d\n", atomic.LoadInt64(&m.firstTokenTimeoutsTotal))
fmt.Fprintf(w, "# HELP edgeai_inference_timeouts_total Total inference timeouts\n")
fmt.Fprintf(w, "# TYPE edgeai_inference_timeouts_total counter\n")
fmt.Fprintf(w, "edgeai_inference_timeouts_total %d\n", atomic.LoadInt64(&m.inferenceTimeoutsTotal))
fmt.Fprintf(w, "# HELP edgeai_degraded_requests_total Total requests served with degradation\n")
fmt.Fprintf(w, "# TYPE edgeai_degraded_requests_total counter\n")
fmt.Fprintf(w, "edgeai_degraded_requests_total %d\n", atomic.LoadInt64(&m.degradedRequestsTotal))
// Gauges
fmt.Fprintf(w, "# HELP edgeai_queue_length Current queue length\n")
fmt.Fprintf(w, "# TYPE edgeai_queue_length gauge\n")
fmt.Fprintf(w, "edgeai_queue_length %d\n", atomic.LoadInt64(&m.queueLength))
fmt.Fprintf(w, "# HELP edgeai_running_tasks Current running tasks\n")
fmt.Fprintf(w, "# TYPE edgeai_running_tasks gauge\n")
fmt.Fprintf(w, "edgeai_running_tasks %d\n", atomic.LoadInt64(&m.runningTasks))
fmt.Fprintf(w, "# HELP edgeai_active_sessions Current active sessions\n")
fmt.Fprintf(w, "# TYPE edgeai_active_sessions gauge\n")
fmt.Fprintf(w, "edgeai_active_sessions %d\n", atomic.LoadInt64(&m.activeSessions))
fmt.Fprintf(w, "# HELP edgeai_backpressure_level Current backpressure level (0-3)\n")
fmt.Fprintf(w, "# TYPE edgeai_backpressure_level gauge\n")
fmt.Fprintf(w, "edgeai_backpressure_level %d\n", atomic.LoadInt64(&m.backpressureLevel))
fmt.Fprintf(w, "# HELP edgeai_circuit_breaker_state Circuit breaker state (0=closed, 1=open, 2=half_open)\n")
fmt.Fprintf(w, "# TYPE edgeai_circuit_breaker_state gauge\n")
fmt.Fprintf(w, "edgeai_circuit_breaker_state %d\n", atomic.LoadInt64(&m.breakerState))
// Histograms
fmt.Fprintf(w, "# HELP edgeai_gateway_latency_bucket Gateway latency distribution in milliseconds\n")
fmt.Fprintf(w, "# TYPE edgeai_gateway_latency_bucket histogram\n")
m.mu.RLock()
for bucket, count := range m.gatewayLatencyBuckets {
fmt.Fprintf(w, "edgeai_gateway_latency_bucket{%s} %d\n", bucket, count)
}
fmt.Fprintf(w, "# HELP edgeai_first_token_latency_bucket First token latency distribution in milliseconds\n")
fmt.Fprintf(w, "# TYPE edgeai_first_token_latency_bucket histogram\n")
for bucket, count := range m.firstTokenLatencyBuckets {
fmt.Fprintf(w, "edgeai_first_token_latency_bucket{%s} %d\n", bucket, count)
}