Files
urldb/middleware/logging.go
2025-11-20 16:05:41 +08:00

146 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"bytes"
"io"
"net/http"
"strings"
"time"
"github.com/ctwj/urldb/utils"
)
// responseWriter 包装http.ResponseWriter以捕获响应状态码和内容
type responseWriter struct {
http.ResponseWriter
statusCode int
body bytes.Buffer
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
rw.body.Write(b)
return rw.ResponseWriter.Write(b)
}
// LoggingMiddleware HTTP请求日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装ResponseWriter
rw := &responseWriter{
ResponseWriter: w,
statusCode: 200, // 默认状态码
}
// 读取请求体
var requestBody []byte
if r.Body != nil {
requestBody, _ = io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(requestBody))
}
// 处理请求
next.ServeHTTP(rw, r)
// 计算处理时间
duration := time.Since(start)
// 记录请求日志
logRequest(r, rw, duration, requestBody)
})
}
// logRequest 记录请求日志 - 恢复正常请求日志记录
func logRequest(r *http.Request, rw *responseWriter, duration time.Duration, requestBody []byte) {
// 获取客户端IP
clientIP := getClientIP(r)
// 判断是否需要详细记录日志的条件
shouldDetailLog := rw.statusCode >= 400 || // 错误状态码
duration > 5*time.Second || // 耗时过长
shouldLogPath(r.URL.Path) || // 关键路径
isAdminPath(r.URL.Path) // 管理员路径
// 所有API请求都记录基本信息但详细日志只记录重要请求
if rw.statusCode >= 400 {
// 错误请求记录详细信息
utils.Error("HTTP异常 - %s %s - IP: %s - 状态码: %d - 耗时: %v",
r.Method, r.URL.Path, clientIP, rw.statusCode, duration)
// 仅在错误状态下记录简要的请求信息
if len(requestBody) > 0 && len(requestBody) <= 500 {
utils.Error("请求详情: %s", string(requestBody))
}
} else if duration > 5*time.Second {
// 慢请求警告
utils.Warn("HTTP慢请求 - %s %s - IP: %s - 耗时: %v",
r.Method, r.URL.Path, clientIP, duration)
} else if shouldDetailLog {
// 关键路径的正常请求
utils.Info("HTTP关键请求 - %s %s - IP: %s - 状态码: %d - 耗时: %v",
r.Method, r.URL.Path, clientIP, rw.statusCode, duration)
} else {
// 普通API请求记录简化日志 - 使用Info级别确保能被看到
// utils.Info("HTTP请求 - %s %s - 状态码: %d - 耗时: %v",
// r.Method, r.URL.Path, rw.statusCode, duration)
}
}
// shouldLogPath 判断路径是否需要记录日志
func shouldLogPath(path string) bool {
// 定义需要记录日志的关键路径
keyPaths := []string{
"/api/public/resources",
"/api/admin/config",
"/api/admin/users",
"/telegram/webhook",
"/api/resources",
"/api/version",
"/api/cks",
"/api/pans",
"/api/categories",
"/api/tags",
"/api/tasks",
}
for _, keyPath := range keyPaths {
if strings.HasPrefix(path, keyPath) {
return true
}
}
return false
}
// isAdminPath 判断是否为管理员路径
func isAdminPath(path string) bool {
return strings.HasPrefix(path, "/api/admin/") ||
strings.HasPrefix(path, "/admin/")
}
// getClientIP 获取客户端真实IP地址
func getClientIP(r *http.Request) string {
// 检查X-Forwarded-For头
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
return ip
}
// 检查X-Real-IP头
if ip := r.Header.Get("X-Real-IP"); ip != "" {
return ip
}
// 检查X-Client-IP头
if ip := r.Header.Get("X-Client-IP"); ip != "" {
return ip
}
// 返回远程地址
return r.RemoteAddr
}