diff --git a/internal/net/http/middleware/metrics_logger/metrics_logger.go b/internal/net/http/middleware/metrics_logger/metrics_logger.go new file mode 100644 index 0000000..23a22f4 --- /dev/null +++ b/internal/net/http/middleware/metrics_logger/metrics_logger.go @@ -0,0 +1,44 @@ +package metricslogger + +import ( + "net" + "net/http" + + "github.com/yusing/go-proxy/internal/metrics" +) + +type MetricsLogger struct { + ServiceName string `json:"service_name"` +} + +func NewMetricsLogger(serviceName string) *MetricsLogger { + return &MetricsLogger{serviceName} +} + +func (m *MetricsLogger) GetHandler(next http.Handler) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + m.ServeHTTP(rw, req, next.ServeHTTP) + } +} + +func (m *MetricsLogger) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) { + visitorIP, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + visitorIP = req.RemoteAddr + } + + // req.RemoteAddr had been modified by middleware (if any) + lbls := &metrics.HTTPRouteMetricLabels{ + Service: m.ServiceName, + Method: req.Method, + Host: req.Host, + Visitor: visitorIP, + Path: req.URL.Path, + } + + next.ServeHTTP(newHTTPMetricLogger(rw, lbls), req) +} + +func (m *MetricsLogger) ResetMetrics() { + metrics.GetRouteMetrics().UnregisterService(m.ServiceName) +} diff --git a/internal/net/http/middleware/metrics_logger/metrics_response_writer.go b/internal/net/http/middleware/metrics_logger/metrics_response_writer.go new file mode 100644 index 0000000..3f9a689 --- /dev/null +++ b/internal/net/http/middleware/metrics_logger/metrics_response_writer.go @@ -0,0 +1,47 @@ +package metricslogger + +import ( + "net/http" + "time" + + "github.com/yusing/go-proxy/internal/metrics" +) + +type httpMetricLogger struct { + http.ResponseWriter + timestamp time.Time + labels *metrics.HTTPRouteMetricLabels +} + +// WriteHeader implements http.ResponseWriter. +func (l *httpMetricLogger) WriteHeader(status int) { + l.ResponseWriter.WriteHeader(status) + duration := time.Since(l.timestamp) + go func() { + m := metrics.GetRouteMetrics() + m.HTTPReqTotal.Inc() + m.HTTPReqElapsed.With(l.labels).Set(float64(duration.Milliseconds())) + + // ignore 1xx + switch { + case status >= 500: + m.HTTP5xx.With(l.labels).Inc() + case status >= 400: + m.HTTP4xx.With(l.labels).Inc() + case status >= 200: + m.HTTP2xx3xx.With(l.labels).Inc() + } + }() +} + +func (l *httpMetricLogger) Unwrap() http.ResponseWriter { + return l.ResponseWriter +} + +func newHTTPMetricLogger(w http.ResponseWriter, labels *metrics.HTTPRouteMetricLabels) *httpMetricLogger { + return &httpMetricLogger{ + ResponseWriter: w, + timestamp: time.Now(), + labels: labels, + } +} diff --git a/internal/net/http/reverseproxy/reverse_proxy_mod.go b/internal/net/http/reverseproxy/reverse_proxy_mod.go index 61532af..aeeb31d 100644 --- a/internal/net/http/reverseproxy/reverse_proxy_mod.go +++ b/internal/net/http/reverseproxy/reverse_proxy_mod.go @@ -23,12 +23,9 @@ import ( "net/url" "strings" "sync" - "time" "github.com/rs/zerolog" - "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/logging" - "github.com/yusing/go-proxy/internal/metrics" gphttp "github.com/yusing/go-proxy/internal/net/http" "github.com/yusing/go-proxy/internal/net/http/accesslog" "github.com/yusing/go-proxy/internal/net/types" @@ -99,37 +96,6 @@ type ReverseProxy struct { TargetURL *types.URL } -type httpMetricLogger struct { - http.ResponseWriter - timestamp time.Time - labels *metrics.HTTPRouteMetricLabels -} - -// WriteHeader implements http.ResponseWriter. -func (l *httpMetricLogger) WriteHeader(status int) { - l.ResponseWriter.WriteHeader(status) - duration := time.Since(l.timestamp) - go func() { - m := metrics.GetRouteMetrics() - m.HTTPReqTotal.Inc() - m.HTTPReqElapsed.With(l.labels).Set(float64(duration.Milliseconds())) - - // ignore 1xx - switch { - case status >= 500: - m.HTTP5xx.With(l.labels).Inc() - case status >= 400: - m.HTTP4xx.With(l.labels).Inc() - case status >= 200: - m.HTTP2xx3xx.With(l.labels).Inc() - } - }() -} - -func (l *httpMetricLogger) Unwrap() http.ResponseWriter { - return l.ResponseWriter -} - func singleJoiningSlash(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") @@ -181,10 +147,6 @@ func NewReverseProxy(name string, target *types.URL, transport http.RoundTripper return rp } -func (p *ReverseProxy) UnregisterMetrics() { - metrics.GetRouteMetrics().UnregisterService(p.TargetName) -} - func (p *ReverseProxy) rewriteRequestURL(req *http.Request) { targetQuery := p.TargetURL.RawQuery req.URL.Scheme = p.TargetURL.Scheme @@ -255,28 +217,6 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { - visitorIP, _, err := net.SplitHostPort(req.RemoteAddr) - if err != nil { - visitorIP = req.RemoteAddr - } - - if common.PrometheusEnabled { - t := time.Now() - // req.RemoteAddr had been modified by middleware (if any) - lbls := &metrics.HTTPRouteMetricLabels{ - Service: p.TargetName, - Method: req.Method, - Host: req.Host, - Visitor: visitorIP, - Path: req.URL.Path, - } - rw = &httpMetricLogger{ - ResponseWriter: rw, - timestamp: t, - labels: lbls, - } - } - transport := p.Transport ctx := req.Context() @@ -360,7 +300,11 @@ func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) { // separated list and fold multiple headers into one. prior, ok := outreq.Header[gphttp.HeaderXForwardedFor] omit := ok && prior == nil // Issue 38079: nil now means don't populate the header - xff := visitorIP + + xff, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + xff = req.RemoteAddr + } if len(prior) > 0 { xff = strings.Join(prior, ", ") + ", " + xff } diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index 0d11c63..493cc79 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -15,6 +15,7 @@ import ( "github.com/yusing/go-proxy/internal/net/http/loadbalancer" loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types" "github.com/yusing/go-proxy/internal/net/http/middleware" + metricslogger "github.com/yusing/go-proxy/internal/net/http/middleware/metrics_logger" "github.com/yusing/go-proxy/internal/net/http/reverseproxy" "github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/task" @@ -158,7 +159,9 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) E.Error { } if common.PrometheusEnabled { - r.task.OnCancel("metrics_cleanup", r.rp.UnregisterMetrics) + metricsLogger := metricslogger.NewMetricsLogger(r.TargetName()) + r.handler = metricsLogger.GetHandler(r.handler) + r.task.OnCancel("reset_metrics", metricsLogger.ResetMetrics) } r.task.OnCancel("reset_favicon", func() { favicon.PruneRouteIconCache(r) })