Files
wx_service/internal/observability/http.go
T
2026-02-28 16:37:37 +08:00

93 lines
1.9 KiB
Go

package observability
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"sync/atomic"
"time"
"github.com/gin-gonic/gin"
"wx_service/internal/middleware"
)
var requestSeq uint64
func RequestLogMiddleware(metrics *Collector) gin.HandlerFunc {
return func(c *gin.Context) {
startedAt := time.Now()
requestID := strings.TrimSpace(c.GetHeader("X-Request-ID"))
if requestID == "" {
seq := atomic.AddUint64(&requestSeq, 1)
requestID = fmt.Sprintf("req-%d-%d", time.Now().UnixNano(), seq)
}
c.Writer.Header().Set("X-Request-ID", requestID)
c.Next()
status := c.Writer.Status()
latency := time.Since(startedAt)
if metrics != nil {
metrics.Observe(status, latency)
}
path := c.FullPath()
if path == "" {
path = c.Request.URL.Path
}
level := "info"
if status >= 500 {
level = "error"
} else if status >= 400 {
level = "warn"
}
entry := map[string]any{
"ts": time.Now().UTC().Format(time.RFC3339),
"level": level,
"request_id": requestID,
"method": c.Request.Method,
"path": path,
"status": status,
"latency_ms": float64(latency.Microseconds()) / 1000.0,
"client_ip": c.ClientIP(),
}
if user, ok := middleware.CurrentUser(c); ok && user != nil {
entry["uid"] = user.ID
}
if len(c.Errors) > 0 {
entry["errors"] = c.Errors.String()
}
payload, err := json.Marshal(entry)
if err != nil {
log.Printf("observability marshal log failed: %v", err)
return
}
log.Println(string(payload))
}
}
func BasicMetricsHandler(metrics *Collector) gin.HandlerFunc {
return func(c *gin.Context) {
if metrics == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"code": http.StatusServiceUnavailable,
"message": "metrics not initialized",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"message": "success",
"data": metrics.Snapshot(),
})
}
}