feat: 十轮网关优化 - 安全加固/可观测性/性能/可靠性
- 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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user