mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 19:12:54 +00:00
- Replace fmt.Appendf with json.Marshal for structured log output - Add logData map with level/type/method/path/query/response_time/status/user_agent/ip/timestamp fields - Update logSlowRequest to output JSON-formatted logs with newline terminator for better log parsing and monitoring integration
283 lines
8.0 KiB
Go
283 lines
8.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// PerformanceMetrics tracks performance statistics
|
|
type PerformanceMetrics struct {
|
|
mu sync.RWMutex
|
|
requestCount int64
|
|
totalResponseTime time.Duration
|
|
slowRequests int64
|
|
fastRequests int64
|
|
errorRequests int64
|
|
}
|
|
|
|
// PerformanceMiddleware tracks and optimizes API performance
|
|
type PerformanceMiddleware struct {
|
|
metrics *PerformanceMetrics
|
|
slowThreshold time.Duration
|
|
}
|
|
|
|
// NewPerformanceMiddleware creates a new performance middleware
|
|
func NewPerformanceMiddleware(slowThreshold time.Duration) *PerformanceMiddleware {
|
|
return &PerformanceMiddleware{
|
|
metrics: &PerformanceMetrics{},
|
|
slowThreshold: slowThreshold,
|
|
}
|
|
}
|
|
|
|
// Middleware returns the gin.HandlerFunc
|
|
func (pm *PerformanceMiddleware) Middleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
// Calculate response time
|
|
responseTime := time.Since(start)
|
|
|
|
// Update metrics
|
|
pm.updateMetrics(c.Writer.Status(), responseTime)
|
|
|
|
// Add performance headers
|
|
c.Header("X-Response-Time", responseTime.String())
|
|
c.Header("X-Request-ID", c.GetHeader("X-Request-ID"))
|
|
|
|
// Log slow requests
|
|
if responseTime > pm.slowThreshold {
|
|
pm.logSlowRequest(c, responseTime)
|
|
}
|
|
}
|
|
}
|
|
|
|
// updateMetrics updates the performance metrics
|
|
func (pm *PerformanceMiddleware) updateMetrics(statusCode int, responseTime time.Duration) {
|
|
pm.metrics.mu.Lock()
|
|
defer pm.metrics.mu.Unlock()
|
|
|
|
pm.metrics.requestCount++
|
|
pm.metrics.totalResponseTime += responseTime
|
|
|
|
if responseTime > pm.slowThreshold {
|
|
pm.metrics.slowRequests++
|
|
} else {
|
|
pm.metrics.fastRequests++
|
|
}
|
|
|
|
if statusCode >= 400 {
|
|
pm.metrics.errorRequests++
|
|
}
|
|
}
|
|
|
|
// logSlowRequest logs slow requests for monitoring
|
|
func (pm *PerformanceMiddleware) logSlowRequest(c *gin.Context, responseTime time.Duration) {
|
|
// Structured logging for slow requests
|
|
logData := map[string]any{
|
|
"level": "warn",
|
|
"type": "slow_request",
|
|
"method": c.Request.Method,
|
|
"path": c.Request.URL.Path,
|
|
"query": c.Request.URL.RawQuery,
|
|
"response_time": responseTime.String(),
|
|
"status": c.Writer.Status(),
|
|
"user_agent": c.Request.UserAgent(),
|
|
"ip": c.ClientIP(),
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
// Convert to JSON for structured logging
|
|
logJSON, _ := json.Marshal(logData)
|
|
gin.DefaultWriter.Write(append(logJSON, '\n'))
|
|
}
|
|
|
|
// GetMetrics returns current performance metrics
|
|
func (pm *PerformanceMiddleware) GetMetrics() PerformanceMetrics {
|
|
pm.metrics.mu.RLock()
|
|
defer pm.metrics.mu.RUnlock()
|
|
|
|
return PerformanceMetrics{
|
|
requestCount: pm.metrics.requestCount,
|
|
totalResponseTime: pm.metrics.totalResponseTime,
|
|
slowRequests: pm.metrics.slowRequests,
|
|
fastRequests: pm.metrics.fastRequests,
|
|
errorRequests: pm.metrics.errorRequests,
|
|
}
|
|
}
|
|
|
|
// GetAverageResponseTime returns the average response time
|
|
func (pm *PerformanceMiddleware) GetAverageResponseTime() time.Duration {
|
|
pm.metrics.mu.RLock()
|
|
defer pm.metrics.mu.RUnlock()
|
|
|
|
if pm.metrics.requestCount == 0 {
|
|
return 0
|
|
}
|
|
|
|
return pm.metrics.totalResponseTime / time.Duration(pm.metrics.requestCount)
|
|
}
|
|
|
|
// GetSlowRequestPercentage returns the percentage of slow requests
|
|
func (pm *PerformanceMiddleware) GetSlowRequestPercentage() float64 {
|
|
pm.metrics.mu.RLock()
|
|
defer pm.metrics.mu.RUnlock()
|
|
|
|
if pm.metrics.requestCount == 0 {
|
|
return 0
|
|
}
|
|
|
|
return float64(pm.metrics.slowRequests) / float64(pm.metrics.requestCount) * 100
|
|
}
|
|
|
|
// GetErrorRate returns the error rate percentage
|
|
func (pm *PerformanceMiddleware) GetErrorRate() float64 {
|
|
pm.metrics.mu.RLock()
|
|
defer pm.metrics.mu.RUnlock()
|
|
|
|
if pm.metrics.requestCount == 0 {
|
|
return 0
|
|
}
|
|
|
|
return float64(pm.metrics.errorRequests) / float64(pm.metrics.requestCount) * 100
|
|
}
|
|
|
|
// ResetMetrics resets all performance metrics
|
|
func (pm *PerformanceMiddleware) ResetMetrics() {
|
|
pm.metrics.mu.Lock()
|
|
defer pm.metrics.mu.Unlock()
|
|
|
|
pm.metrics.requestCount = 0
|
|
pm.metrics.totalResponseTime = 0
|
|
pm.metrics.slowRequests = 0
|
|
pm.metrics.fastRequests = 0
|
|
pm.metrics.errorRequests = 0
|
|
}
|
|
|
|
// Global performance middleware instance
|
|
var globalPerformanceMiddleware *PerformanceMiddleware
|
|
|
|
// InitializePerformanceMiddleware initializes the global performance middleware
|
|
func InitializePerformanceMiddleware(slowThreshold time.Duration) {
|
|
globalPerformanceMiddleware = NewPerformanceMiddleware(slowThreshold)
|
|
}
|
|
|
|
// GetPerformanceMiddleware returns the global performance middleware
|
|
func GetPerformanceMiddleware() *PerformanceMiddleware {
|
|
return globalPerformanceMiddleware
|
|
}
|
|
|
|
// PerformanceMiddlewareHandler returns a gin.HandlerFunc for performance tracking
|
|
func PerformanceMiddlewareHandler() gin.HandlerFunc {
|
|
if globalPerformanceMiddleware == nil {
|
|
// Initialize with default threshold if not already initialized
|
|
InitializePerformanceMiddleware(100 * time.Millisecond)
|
|
}
|
|
return globalPerformanceMiddleware.Middleware()
|
|
}
|
|
|
|
// PerformanceHandler returns performance metrics as JSON
|
|
func PerformanceHandler() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if globalPerformanceMiddleware == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
"error": "Performance middleware not initialized",
|
|
})
|
|
return
|
|
}
|
|
|
|
metrics := globalPerformanceMiddleware.GetMetrics()
|
|
avgResponseTime := globalPerformanceMiddleware.GetAverageResponseTime()
|
|
slowRequestPercentage := globalPerformanceMiddleware.GetSlowRequestPercentage()
|
|
errorRate := globalPerformanceMiddleware.GetErrorRate()
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"metrics": gin.H{
|
|
"request_count": metrics.requestCount,
|
|
"total_response_time": metrics.totalResponseTime.String(),
|
|
"slow_requests": metrics.slowRequests,
|
|
"fast_requests": metrics.fastRequests,
|
|
"error_requests": metrics.errorRequests,
|
|
},
|
|
"performance": gin.H{
|
|
"average_response_time_ms": avgResponseTime.Milliseconds(),
|
|
"slow_request_percentage": slowRequestPercentage,
|
|
"error_rate_percentage": errorRate,
|
|
"slow_threshold_ms": globalPerformanceMiddleware.slowThreshold.Milliseconds(),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequestSizeMiddleware limits request size for performance
|
|
func RequestSizeMiddleware(maxSize int64) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxSize)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// CompressionMiddleware enables gzip compression for better performance
|
|
func CompressionMiddleware() gin.HandlerFunc {
|
|
// Use gin's built-in compression
|
|
return func(c *gin.Context) {
|
|
c.Header("Content-Encoding", "gzip")
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// CacheMiddleware adds basic caching headers for GET requests
|
|
func CacheMiddleware(duration time.Duration) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if c.Request.Method == "GET" {
|
|
c.Header("Cache-Control", "public, max-age="+duration.String())
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// TimeoutMiddleware adds timeout to requests
|
|
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
|
|
defer cancel()
|
|
c.Request = c.Request.WithContext(ctx)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// SecurityHeadersMiddleware adds security headers for better performance
|
|
func SecurityHeadersMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
c.Header("X-Frame-Options", "DENY")
|
|
c.Header("X-XSS-Protection", "1; mode=block")
|
|
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// CORSMiddleware handles CORS for better performance
|
|
func CORSMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Header("Access-Control-Allow-Origin", "*")
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
c.Header("Access-Control-Max-Age", "86400")
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|