sub2api/backend/internal/handler/admin/debug_log_handler.go
win d3d885cf75
Some checks failed
CI / test (push) Failing after 6s
CI / golangci-lint (push) Failing after 5s
Security Scan / backend-security (push) Failing after 5s
Security Scan / frontend-security (push) Failing after 7s
fix: node-tls-proxy not receiving traffic due to viper BindEnv bug
- 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
2026-03-31 13:16:02 +08:00

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),
})
}