File size: 3,056 Bytes
2986042
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package app

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"strconv"
	"unicode/utf8"

	"ccLoad/internal/model"

	"github.com/gin-gonic/gin"
)

// maskSensitiveHeaderJSON 对 JSON string 格式的 headers 做脱敏
func maskSensitiveHeaderJSON(jsonStr string) string {
	if jsonStr == "" {
		return jsonStr
	}
	var headers map[string]any
	if err := json.Unmarshal([]byte(jsonStr), &headers); err != nil {
		return jsonStr
	}
	for k, v := range headers {
		if !isSensitiveHeader(k) {
			continue
		}
		switch val := v.(type) {
		case string:
			headers[k] = maskHeaderValue(val)
		case []any:
			for i, item := range val {
				if s, ok := item.(string); ok {
					val[i] = maskHeaderValue(s)
				}
			}
		}
	}
	out, err := json.Marshal(headers)
	if err != nil {
		return jsonStr
	}
	return string(out)
}

type debugLogUnavailableInfo struct {
	Reason                   string               `json:"reason"`
	DebugLogEnabled          *model.SystemSetting `json:"debug_log_enabled,omitempty"`
	DebugLogRetentionMinutes *model.SystemSetting `json:"debug_log_retention_minutes,omitempty"`
}

func (s *Server) buildDebugLogUnavailableInfo(ctx context.Context) debugLogUnavailableInfo {
	info := debugLogUnavailableInfo{
		Reason: "debug_log_not_found",
	}

	if setting, err := s.configService.GetSettingFresh(ctx, "debug_log_enabled"); err == nil {
		info.DebugLogEnabled = setting
	}
	if setting, err := s.configService.GetSettingFresh(ctx, "debug_log_retention_minutes"); err == nil {
		info.DebugLogRetentionMinutes = setting
	}

	return info
}

func debugLogResponse(entry *model.DebugLogEntry) gin.H {
	resp := gin.H{
		"log_id":       entry.LogID,
		"created_at":   entry.CreatedAt,
		"req_method":   entry.ReqMethod,
		"req_url":      entry.ReqURL,
		"req_headers":  maskSensitiveHeaderJSON(entry.ReqHeaders),
		"resp_status":  entry.RespStatus,
		"resp_headers": maskSensitiveHeaderJSON(entry.RespHeaders),
	}

	if utf8.Valid(entry.ReqBody) {
		resp["req_body"] = string(entry.ReqBody)
	} else {
		resp["req_body"] = base64.StdEncoding.EncodeToString(entry.ReqBody)
		resp["req_body_encoding"] = "base64"
	}

	if utf8.Valid(entry.RespBody) {
		resp["resp_body"] = string(entry.RespBody)
	} else {
		resp["resp_body"] = base64.StdEncoding.EncodeToString(entry.RespBody)
		resp["resp_body_encoding"] = "base64"
	}

	return resp
}

// HandleGetDebugLog 获取指定 log_id 对应的调试日志
// GET /admin/debug-logs/:log_id
func (s *Server) HandleGetDebugLog(c *gin.Context) {
	logIDStr := c.Param("log_id")
	logID, err := strconv.ParseInt(logIDStr, 10, 64)
	if err != nil || logID <= 0 {
		RespondErrorMsg(c, http.StatusBadRequest, "invalid log_id")
		return
	}

	entry, err := s.store.GetDebugLogByLogID(c.Request.Context(), logID)
	if err != nil {
		RespondError(c, http.StatusInternalServerError, err)
		return
	}
	if entry == nil {
		RespondErrorWithData(c, http.StatusNotFound, "debug log unavailable", s.buildDebugLogUnavailableInfo(c.Request.Context()))
		return
	}

	RespondJSON(c, http.StatusOK, debugLogResponse(entry))
}