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(), }) } }