- Add explicit viper.BindEnv() for all gateway.node_tls_proxy.* keys to fix viper's AutomaticEnv+Unmarshal nested struct bug where env vars are silently ignored when config.yaml lacks the corresponding section - Sync proxy.js CLI_VERSION 2.1.84→2.1.87 and BUILD_TIME to match constants.go, eliminating API/telemetry version mismatch
139 lines
4.2 KiB
Go
139 lines
4.2 KiB
Go
package admin
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// DebugLogHandler provides admin endpoints to control gateway debug logging.
|
|
type DebugLogHandler struct {
|
|
debugLogger *service.GatewayDebugLogger
|
|
}
|
|
|
|
func NewDebugLogHandler(debugLogger *service.GatewayDebugLogger) *DebugLogHandler {
|
|
return &DebugLogHandler{debugLogger: debugLogger}
|
|
}
|
|
|
|
// GetStatus returns whether debug logging is enabled.
|
|
func (h *DebugLogHandler) GetStatus(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"enabled": h.debugLogger.IsEnabled(),
|
|
})
|
|
}
|
|
|
|
// Enable turns on debug logging.
|
|
func (h *DebugLogHandler) Enable(c *gin.Context) {
|
|
h.debugLogger.Enable()
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"enabled": true,
|
|
"message": "gateway debug logging enabled",
|
|
})
|
|
}
|
|
|
|
// Disable turns off debug logging.
|
|
func (h *DebugLogHandler) Disable(c *gin.Context) {
|
|
h.debugLogger.Disable()
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"enabled": false,
|
|
"message": "gateway debug logging disabled",
|
|
})
|
|
}
|
|
|
|
// ListLogs returns recent debug logs with pagination.
|
|
func (h *DebugLogHandler) ListLogs(c *gin.Context) {
|
|
db := h.debugLogger.DB()
|
|
if db == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database not available"})
|
|
return
|
|
}
|
|
|
|
limit := 50
|
|
if v := c.Query("limit"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
|
|
limit = n
|
|
}
|
|
}
|
|
|
|
accountID := c.Query("account_id")
|
|
eventType := c.Query("event_type")
|
|
|
|
query := `SELECT id, upstream_request_id, account_id, account_email, account_platform,
|
|
event_type, method, full_url, request_headers, request_body, request_size,
|
|
response_status, response_headers, response_body_preview, response_size,
|
|
model_requested, model_upstream, is_stream, duration_ms, tls_profile,
|
|
error_message, created_at
|
|
FROM gateway_debug_logs WHERE 1=1`
|
|
args := []interface{}{}
|
|
argIdx := 1
|
|
|
|
if accountID != "" {
|
|
query += " AND account_id = $" + strconv.Itoa(argIdx)
|
|
args = append(args, accountID)
|
|
argIdx++
|
|
}
|
|
if eventType != "" {
|
|
query += " AND event_type = $" + strconv.Itoa(argIdx)
|
|
args = append(args, eventType)
|
|
argIdx++
|
|
}
|
|
|
|
query += " ORDER BY created_at DESC LIMIT $" + strconv.Itoa(argIdx)
|
|
args = append(args, limit)
|
|
|
|
rows, err := db.QueryContext(c.Request.Context(), query, args...)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
type logRow struct {
|
|
ID int64 `json:"id"`
|
|
UpstreamRequestID *string `json:"upstream_request_id"`
|
|
AccountID int64 `json:"account_id"`
|
|
AccountEmail *string `json:"account_email"`
|
|
AccountPlatform *string `json:"account_platform"`
|
|
EventType string `json:"event_type"`
|
|
Method *string `json:"method"`
|
|
FullURL *string `json:"full_url"`
|
|
RequestHeaders *string `json:"request_headers"`
|
|
RequestBody *string `json:"request_body"`
|
|
RequestSize *int `json:"request_size"`
|
|
ResponseStatus *int `json:"response_status"`
|
|
ResponseHeaders *string `json:"response_headers"`
|
|
ResponseBodyPreview *string `json:"response_body_preview"`
|
|
ResponseSize *int `json:"response_size"`
|
|
ModelRequested *string `json:"model_requested"`
|
|
ModelUpstream *string `json:"model_upstream"`
|
|
IsStream bool `json:"is_stream"`
|
|
DurationMs *int `json:"duration_ms"`
|
|
TLSProfile *string `json:"tls_profile"`
|
|
ErrorMessage *string `json:"error_message"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
var results []logRow
|
|
for rows.Next() {
|
|
var r logRow
|
|
if err := rows.Scan(
|
|
&r.ID, &r.UpstreamRequestID, &r.AccountID, &r.AccountEmail, &r.AccountPlatform,
|
|
&r.EventType, &r.Method, &r.FullURL, &r.RequestHeaders, &r.RequestBody, &r.RequestSize,
|
|
&r.ResponseStatus, &r.ResponseHeaders, &r.ResponseBodyPreview, &r.ResponseSize,
|
|
&r.ModelRequested, &r.ModelUpstream, &r.IsStream, &r.DurationMs, &r.TLSProfile,
|
|
&r.ErrorMessage, &r.CreatedAt,
|
|
); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
results = append(results, r)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"items": results,
|
|
"count": len(results),
|
|
})
|
|
}
|