168 lines
4.2 KiB
Go
168 lines
4.2 KiB
Go
|
|
package middleware
|
||
|
|
|
||
|
|
import (
|
||
|
|
"log"
|
||
|
|
"sync"
|
||
|
|
"sync/atomic"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TrafficStats 全局流量统计
|
||
|
|
var TrafficStats = &trafficStats{
|
||
|
|
startTime: time.Now(),
|
||
|
|
}
|
||
|
|
|
||
|
|
type trafficStats struct {
|
||
|
|
startTime time.Time
|
||
|
|
totalRequests int64
|
||
|
|
totalBytesIn int64
|
||
|
|
totalBytesOut int64
|
||
|
|
activeConns int64
|
||
|
|
|
||
|
|
// 按状态码统计
|
||
|
|
status2xx int64
|
||
|
|
status3xx int64
|
||
|
|
status4xx int64
|
||
|
|
status5xx int64
|
||
|
|
|
||
|
|
// 最近请求记录(滑动窗口)
|
||
|
|
recentRequests []requestRecord
|
||
|
|
mu sync.Mutex
|
||
|
|
}
|
||
|
|
|
||
|
|
type requestRecord struct {
|
||
|
|
Timestamp time.Time
|
||
|
|
IP string
|
||
|
|
Path string
|
||
|
|
Status int
|
||
|
|
Bytes int
|
||
|
|
}
|
||
|
|
|
||
|
|
// TrafficInfo 流量信息
|
||
|
|
type TrafficInfo struct {
|
||
|
|
Uptime string `json:"uptime"`
|
||
|
|
TotalRequests int64 `json:"total_requests"`
|
||
|
|
TotalBytesIn int64 `json:"total_bytes_in"`
|
||
|
|
TotalBytesOut int64 `json:"total_bytes_out"`
|
||
|
|
ActiveConns int64 `json:"active_conns"`
|
||
|
|
QPS float64 `json:"qps"` // 每秒请求数
|
||
|
|
Status2xx int64 `json:"status_2xx"`
|
||
|
|
Status3xx int64 `json:"status_3xx"`
|
||
|
|
Status4xx int64 `json:"status_4xx"`
|
||
|
|
Status5xx int64 `json:"status_5xx"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetTrafficInfo 获取流量统计信息
|
||
|
|
func GetTrafficInfo() TrafficInfo {
|
||
|
|
uptime := time.Since(TrafficStats.startTime).Truncate(time.Second)
|
||
|
|
|
||
|
|
// 计算QPS(最近60秒)
|
||
|
|
TrafficStats.mu.Lock()
|
||
|
|
cutoff := time.Now().Add(-60 * time.Second)
|
||
|
|
recent := 0
|
||
|
|
for _, r := range TrafficStats.recentRequests {
|
||
|
|
if r.Timestamp.After(cutoff) {
|
||
|
|
recent++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
TrafficStats.mu.Unlock()
|
||
|
|
|
||
|
|
var qps float64
|
||
|
|
if uptime.Seconds() > 0 {
|
||
|
|
qps = float64(recent) / 60.0
|
||
|
|
}
|
||
|
|
|
||
|
|
return TrafficInfo{
|
||
|
|
Uptime: uptime.String(),
|
||
|
|
TotalRequests: atomic.LoadInt64(&TrafficStats.totalRequests),
|
||
|
|
TotalBytesIn: atomic.LoadInt64(&TrafficStats.totalBytesIn),
|
||
|
|
TotalBytesOut: atomic.LoadInt64(&TrafficStats.totalBytesOut),
|
||
|
|
ActiveConns: atomic.LoadInt64(&TrafficStats.activeConns),
|
||
|
|
QPS: qps,
|
||
|
|
Status2xx: atomic.LoadInt64(&TrafficStats.status2xx),
|
||
|
|
Status3xx: atomic.LoadInt64(&TrafficStats.status3xx),
|
||
|
|
Status4xx: atomic.LoadInt64(&TrafficStats.status4xx),
|
||
|
|
Status5xx: atomic.LoadInt64(&TrafficStats.status5xx),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TrafficMonitor 流量监控中间件
|
||
|
|
func TrafficMonitor() gin.HandlerFunc {
|
||
|
|
return func(c *gin.Context) {
|
||
|
|
start := time.Now()
|
||
|
|
|
||
|
|
// 增加活跃连接数
|
||
|
|
atomic.AddInt64(&TrafficStats.activeConns, 1)
|
||
|
|
defer atomic.AddInt64(&TrafficStats.activeConns, -1)
|
||
|
|
|
||
|
|
// 统计请求大小
|
||
|
|
bytesIn := int64(c.Request.ContentLength)
|
||
|
|
if bytesIn < 0 {
|
||
|
|
bytesIn = 0
|
||
|
|
}
|
||
|
|
atomic.AddInt64(&TrafficStats.totalBytesIn, bytesIn)
|
||
|
|
atomic.AddInt64(&TrafficStats.totalRequests, 1)
|
||
|
|
|
||
|
|
c.Next()
|
||
|
|
|
||
|
|
// 统计响应
|
||
|
|
status := c.Writer.Status()
|
||
|
|
bytesOut := int64(c.Writer.Size())
|
||
|
|
|
||
|
|
switch {
|
||
|
|
case status >= 200 && status < 300:
|
||
|
|
atomic.AddInt64(&TrafficStats.status2xx, 1)
|
||
|
|
case status >= 300 && status < 400:
|
||
|
|
atomic.AddInt64(&TrafficStats.status3xx, 1)
|
||
|
|
case status >= 400 && status < 500:
|
||
|
|
atomic.AddInt64(&TrafficStats.status4xx, 1)
|
||
|
|
case status >= 500:
|
||
|
|
atomic.AddInt64(&TrafficStats.status5xx, 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 记录最近请求
|
||
|
|
TrafficStats.mu.Lock()
|
||
|
|
TrafficStats.recentRequests = append(TrafficStats.recentRequests, requestRecord{
|
||
|
|
Timestamp: start,
|
||
|
|
IP: c.ClientIP(),
|
||
|
|
Path: c.Request.URL.Path,
|
||
|
|
Status: status,
|
||
|
|
Bytes: int(bytesOut),
|
||
|
|
})
|
||
|
|
// 只保留最近5分钟的记录
|
||
|
|
cutoff := time.Now().Add(-5 * time.Minute)
|
||
|
|
valid := TrafficStats.recentRequests[:0]
|
||
|
|
for _, r := range TrafficStats.recentRequests {
|
||
|
|
if r.Timestamp.After(cutoff) {
|
||
|
|
valid = append(valid, r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
TrafficStats.recentRequests = valid
|
||
|
|
TrafficStats.mu.Unlock()
|
||
|
|
|
||
|
|
// 慢请求日志
|
||
|
|
duration := time.Since(start)
|
||
|
|
if duration > 5*time.Second {
|
||
|
|
log.Printf("[SLOW] %s %s %d %v", c.ClientIP(), c.Request.URL.Path, status, duration)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetRecentRequests 获取最近的请求记录
|
||
|
|
func GetRecentRequests(limit int) []requestRecord {
|
||
|
|
TrafficStats.mu.Lock()
|
||
|
|
defer TrafficStats.mu.Unlock()
|
||
|
|
|
||
|
|
total := len(TrafficStats.recentRequests)
|
||
|
|
if limit <= 0 || limit > total {
|
||
|
|
limit = total
|
||
|
|
}
|
||
|
|
|
||
|
|
start := total - limit
|
||
|
|
result := make([]requestRecord, limit)
|
||
|
|
copy(result, TrafficStats.recentRequests[start:])
|
||
|
|
return result
|
||
|
|
}
|