mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-18 08:31:55 +08:00
feat(metrics): enhance metrics collection and UI with top referers tracking
- Added support for tracking top referers in the metrics collector, allowing for better insights into request sources. - Updated MetricsHandler to include top referers in the metrics response. - Enhanced the metrics UI to display a new section for the top 10 referers, improving visibility into traffic sources. - Refactored request recording to capture referer information in the collector for comprehensive statistics.
This commit is contained in:
parent
9be13ce0ef
commit
d4af4c4d31
@ -32,6 +32,7 @@ type Metrics struct {
|
||||
LatencyPercentiles map[string]float64 `json:"latency_percentiles"`
|
||||
TopPaths []metrics.PathMetrics `json:"top_paths"`
|
||||
RecentRequests []metrics.RequestLog `json:"recent_requests"`
|
||||
TopReferers []metrics.PathMetrics `json:"top_referers"`
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -59,6 +60,7 @@ func (h *ProxyHandler) MetricsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
StatusCodeStats: stats["status_code_stats"].(map[string]int64),
|
||||
TopPaths: stats["top_paths"].([]metrics.PathMetrics),
|
||||
RecentRequests: stats["recent_requests"].([]metrics.RequestLog),
|
||||
TopReferers: stats["top_referers"].([]metrics.PathMetrics),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@ -259,11 +261,21 @@ var metricsTemplate = `
|
||||
.status-3xx { background: #17a2b8; }
|
||||
.status-4xx { background: #ffc107; }
|
||||
.status-5xx { background: #dc3545; }
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.grid-container .card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Proxy-Go Metrics</h1>
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="card">
|
||||
<h2>基础指标</h2>
|
||||
<div class="metric">
|
||||
@ -323,6 +335,7 @@ var metricsTemplate = `
|
||||
<span class="metric-value" id="bytesPerSecond"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>状态码统计</h2>
|
||||
@ -362,6 +375,19 @@ var metricsTemplate = `
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>热门引用来源 (Top 10)</h2>
|
||||
<table id="topReferers">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>来源</th>
|
||||
<th>请求数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<span id="lastUpdate"></span>
|
||||
<button class="refresh" onclick="refreshMetrics()">刷新</button>
|
||||
|
||||
@ -452,6 +478,15 @@ var metricsTemplate = `
|
||||
).join('');
|
||||
document.querySelector('#recentRequests tbody').innerHTML = recentRequestsHtml;
|
||||
|
||||
// 更新热门引用来源
|
||||
const topReferersHtml = data.top_referers.map(referer =>
|
||||
'<tr>' +
|
||||
'<td>' + referer.path + '</td>' +
|
||||
'<td>' + referer.request_count + '</td>' +
|
||||
'</tr>'
|
||||
).join('');
|
||||
document.querySelector('#topReferers tbody').innerHTML = topReferersHtml;
|
||||
|
||||
document.getElementById('lastUpdate').textContent = '最后更新: ' + new Date().toLocaleTimeString();
|
||||
}
|
||||
|
||||
|
@ -138,5 +138,5 @@ func (h *MirrorProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
utils.GetRequestSource(r), actualURL)
|
||||
|
||||
// 记录统计信息
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r))
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r), r)
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 确定<EFBFBD><EFBFBD><EFBFBD>标基础URL
|
||||
// 确定标基础URL
|
||||
targetBase := pathConfig.DefaultTarget
|
||||
|
||||
// 检查文件扩展名
|
||||
@ -228,7 +228,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
written, _ := w.Write(body)
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), int64(written), utils.GetClientIP(r))
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), int64(written), utils.GetClientIP(r), r)
|
||||
} else {
|
||||
// 大响应使用流式传输
|
||||
var bytesCopied int64
|
||||
@ -274,7 +274,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
targetURL, // 目标URL
|
||||
)
|
||||
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), bytesCopied, utils.GetClientIP(r))
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(start), bytesCopied, utils.GetClientIP(r), r)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
@ -17,6 +18,7 @@ type Collector struct {
|
||||
totalBytes atomic.Int64
|
||||
latencySum atomic.Int64
|
||||
pathStats sync.Map
|
||||
refererStats sync.Map
|
||||
statusStats [6]atomic.Int64
|
||||
latencyBuckets [10]atomic.Int64
|
||||
recentRequests struct {
|
||||
@ -45,7 +47,7 @@ func (c *Collector) EndRequest() {
|
||||
atomic.AddInt64(&c.activeRequests, -1)
|
||||
}
|
||||
|
||||
func (c *Collector) RecordRequest(path string, status int, latency time.Duration, bytes int64, clientIP string) {
|
||||
func (c *Collector) RecordRequest(path string, status int, latency time.Duration, bytes int64, clientIP string, r *http.Request) {
|
||||
// 更新总请求数
|
||||
atomic.AddInt64(&c.totalRequests, 1)
|
||||
|
||||
@ -88,6 +90,17 @@ func (c *Collector) RecordRequest(path string, status int, latency time.Duration
|
||||
c.pathStats.Store(path, newStats)
|
||||
}
|
||||
|
||||
// 更新引用来源统计
|
||||
if referer := r.Header.Get("Referer"); referer != "" {
|
||||
if stats, ok := c.refererStats.Load(referer); ok {
|
||||
stats.(*PathStats).requests.Add(1)
|
||||
} else {
|
||||
newStats := &PathStats{}
|
||||
newStats.requests.Add(1)
|
||||
c.refererStats.Store(referer, newStats)
|
||||
}
|
||||
}
|
||||
|
||||
// 记录最近的请求
|
||||
log := &RequestLog{
|
||||
Time: time.Now(),
|
||||
@ -151,6 +164,33 @@ func (c *Collector) GetStats() map[string]interface{} {
|
||||
pathMetrics = allPaths
|
||||
}
|
||||
|
||||
// 获取Top 10引用来源
|
||||
var refererMetrics []PathMetrics
|
||||
var allReferers []PathMetrics
|
||||
c.refererStats.Range(func(key, value interface{}) bool {
|
||||
stats := value.(*PathStats)
|
||||
if stats.requests.Load() == 0 {
|
||||
return true
|
||||
}
|
||||
allReferers = append(allReferers, PathMetrics{
|
||||
Path: key.(string),
|
||||
RequestCount: stats.requests.Load(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
// 按请求数排序
|
||||
sort.Slice(allReferers, func(i, j int) bool {
|
||||
return allReferers[i].RequestCount > allReferers[j].RequestCount
|
||||
})
|
||||
|
||||
// 取前10个
|
||||
if len(allReferers) > 10 {
|
||||
refererMetrics = allReferers[:10]
|
||||
} else {
|
||||
refererMetrics = allReferers
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"uptime": uptime.String(),
|
||||
"active_requests": atomic.LoadInt64(&c.activeRequests),
|
||||
@ -170,6 +210,7 @@ func (c *Collector) GetStats() map[string]interface{} {
|
||||
"status_code_stats": statusStats,
|
||||
"top_paths": pathMetrics,
|
||||
"recent_requests": c.getRecentRequests(),
|
||||
"top_referers": refererMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ func FixedPathProxyMiddleware(configs []config.FixedPathConfig) func(http.Handle
|
||||
}
|
||||
|
||||
// 记录统计信息
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r))
|
||||
collector.RecordRequest(r.URL.Path, resp.StatusCode, time.Since(startTime), bytesCopied, utils.GetClientIP(r), r)
|
||||
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user