Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | package reverseproxy |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "runtime/debug" |
| 6 | "sync" |
| 7 | "time" |
| 8 | |
| 9 | "github.com/prometheus/client_golang/prometheus" |
| 10 | "go.uber.org/zap" |
| 11 | "go.uber.org/zap/zapcore" |
| 12 | |
| 13 | "github.com/caddyserver/caddy/v2" |
| 14 | ) |
| 15 | |
| 16 | var reverseProxyMetrics = struct { |
| 17 | once sync.Once |
| 18 | upstreamsHealthy *prometheus.GaugeVec |
| 19 | streamsActive *prometheus.GaugeVec |
| 20 | streamsTotal *prometheus.CounterVec |
| 21 | streamDuration *prometheus.HistogramVec |
| 22 | streamBytes *prometheus.CounterVec |
| 23 | logger *zap.Logger |
| 24 | }{} |
| 25 | |
| 26 | func initReverseProxyMetrics(handler *Handler, registry *prometheus.Registry) { |
| 27 | const ns, sub = "caddy", "reverse_proxy" |
| 28 | |
| 29 | upstreamsLabels := []string{"upstream"} |
| 30 | streamResultLabels := []string{"upstream", "result"} |
| 31 | streamBytesLabels := []string{"upstream", "direction"} |
| 32 | reverseProxyMetrics.once.Do(func() { |
| 33 | reverseProxyMetrics.upstreamsHealthy = prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| 34 | Namespace: ns, |
| 35 | Subsystem: sub, |
| 36 | Name: "upstreams_healthy", |
| 37 | Help: "Health status of reverse proxy upstreams.", |
| 38 | }, upstreamsLabels) |
| 39 | reverseProxyMetrics.streamsActive = prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| 40 | Namespace: ns, |
| 41 | Subsystem: sub, |
| 42 | Name: "streams_active", |
| 43 | Help: "Number of currently active upgraded reverse proxy streams.", |
| 44 | }, upstreamsLabels) |
| 45 | reverseProxyMetrics.streamsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ |
| 46 | Namespace: ns, |
| 47 | Subsystem: sub, |
| 48 | Name: "streams_total", |
| 49 | Help: "Total number of upgraded reverse proxy streams by close result.", |
| 50 | }, streamResultLabels) |
| 51 | reverseProxyMetrics.streamDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ |
| 52 | Namespace: ns, |
| 53 | Subsystem: sub, |
| 54 | Name: "stream_duration_seconds", |
| 55 | Help: "Duration of upgraded reverse proxy streams by close result.", |
| 56 | Buckets: prometheus.DefBuckets, |
| 57 | }, streamResultLabels) |
| 58 | reverseProxyMetrics.streamBytes = prometheus.NewCounterVec(prometheus.CounterOpts{ |
| 59 | Namespace: ns, |
| 60 | Subsystem: sub, |
| 61 | Name: "stream_bytes_total", |
| 62 | Help: "Total bytes proxied across upgraded reverse proxy streams.", |
| 63 | }, streamBytesLabels) |
| 64 | }) |
| 65 | |
| 66 | // duplicate registration could happen if multiple sites with reverse proxy are configured; so ignore the error because |
| 67 | // there's no good way to capture having multiple sites with reverse proxy. If this happens, the metrics will be |
| 68 | // registered twice, but the second registration will be ignored. |
| 69 | if err := registry.Register(reverseProxyMetrics.upstreamsHealthy); err != nil && |
| 70 | !errors.Is(err, prometheus.AlreadyRegisteredError{ |
| 71 | ExistingCollector: reverseProxyMetrics.upstreamsHealthy, |
| 72 | NewCollector: reverseProxyMetrics.upstreamsHealthy, |
| 73 | }) { |
| 74 | panic(err) |
| 75 | } |
| 76 | if err := registry.Register(reverseProxyMetrics.streamsActive); err != nil && |
| 77 | !errors.Is(err, prometheus.AlreadyRegisteredError{ |
| 78 | ExistingCollector: reverseProxyMetrics.streamsActive, |
| 79 | NewCollector: reverseProxyMetrics.streamsActive, |
| 80 | }) { |
| 81 | panic(err) |
| 82 | } |
| 83 | if err := registry.Register(reverseProxyMetrics.streamsTotal); err != nil && |
| 84 | !errors.Is(err, prometheus.AlreadyRegisteredError{ |
| 85 | ExistingCollector: reverseProxyMetrics.streamsTotal, |
| 86 | NewCollector: reverseProxyMetrics.streamsTotal, |
| 87 | }) { |
| 88 | panic(err) |
| 89 | } |
| 90 | if err := registry.Register(reverseProxyMetrics.streamDuration); err != nil && |
| 91 | !errors.Is(err, prometheus.AlreadyRegisteredError{ |
| 92 | ExistingCollector: reverseProxyMetrics.streamDuration, |
| 93 | NewCollector: reverseProxyMetrics.streamDuration, |
| 94 | }) { |
| 95 | panic(err) |
| 96 | } |
| 97 | if err := registry.Register(reverseProxyMetrics.streamBytes); err != nil && |
| 98 | !errors.Is(err, prometheus.AlreadyRegisteredError{ |
| 99 | ExistingCollector: reverseProxyMetrics.streamBytes, |
| 100 | NewCollector: reverseProxyMetrics.streamBytes, |
| 101 | }) { |
| 102 | panic(err) |
| 103 | } |
| 104 | |
| 105 | reverseProxyMetrics.logger = handler.logger.Named("reverse_proxy.metrics") |
| 106 | } |
| 107 | |
| 108 | func trackActiveStream(upstream string) func(result string, duration time.Duration, toBackend, fromBackend int64) { |
| 109 | labels := prometheus.Labels{"upstream": upstream} |
| 110 | reverseProxyMetrics.streamsActive.With(labels).Inc() |
| 111 | |
| 112 | var once sync.Once |
| 113 | return func(result string, duration time.Duration, toBackend, fromBackend int64) { |
| 114 | once.Do(func() { |
| 115 | reverseProxyMetrics.streamsActive.With(labels).Dec() |
| 116 | reverseProxyMetrics.streamsTotal.WithLabelValues(upstream, result).Inc() |
| 117 | reverseProxyMetrics.streamDuration.WithLabelValues(upstream, result).Observe(duration.Seconds()) |
| 118 | if toBackend > 0 { |
| 119 | reverseProxyMetrics.streamBytes.WithLabelValues(upstream, "to_upstream").Add(float64(toBackend)) |
| 120 | } |
| 121 | if fromBackend > 0 { |
| 122 | reverseProxyMetrics.streamBytes.WithLabelValues(upstream, "from_upstream").Add(float64(fromBackend)) |
| 123 | } |
| 124 | }) |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | type metricsUpstreamsHealthyUpdater struct { |
| 129 | handler *Handler |
| 130 | } |
| 131 | |
| 132 | func newMetricsUpstreamsHealthyUpdater(handler *Handler, ctx caddy.Context) *metricsUpstreamsHealthyUpdater { |
| 133 | initReverseProxyMetrics(handler, ctx.GetMetricsRegistry()) |
| 134 | reverseProxyMetrics.upstreamsHealthy.Reset() |
| 135 | |
| 136 | return &metricsUpstreamsHealthyUpdater{handler} |
| 137 | } |
| 138 | |
| 139 | func (m *metricsUpstreamsHealthyUpdater) init() { |
| 140 | go func() { |
| 141 | defer func() { |
| 142 | if err := recover(); err != nil { |
| 143 | if c := reverseProxyMetrics.logger.Check(zapcore.ErrorLevel, "upstreams healthy metrics updater panicked"); c != nil { |
| 144 | c.Write( |
| 145 | zap.Any("error", err), |
| 146 | zap.ByteString("stack", debug.Stack()), |
| 147 | ) |
| 148 | } |
| 149 | } |
| 150 | }() |
| 151 | |
| 152 | m.update() |
| 153 | |
| 154 | ticker := time.NewTicker(10 * time.Second) |
| 155 | for { |
| 156 | select { |
| 157 | case <-ticker.C: |
| 158 | m.update() |
| 159 | case <-m.handler.ctx.Done(): |
| 160 | ticker.Stop() |
| 161 | return |
| 162 | } |
| 163 | } |
| 164 | }() |
| 165 | } |
| 166 | |
| 167 | func (m *metricsUpstreamsHealthyUpdater) update() { |
| 168 | for _, upstream := range m.handler.Upstreams { |
| 169 | labels := prometheus.Labels{"upstream": upstream.Dial} |
| 170 | |
| 171 | gaugeValue := 0.0 |
| 172 | if upstream.Healthy() { |
| 173 | gaugeValue = 1.0 |
| 174 | } |
| 175 | |
| 176 | reverseProxyMetrics.upstreamsHealthy.With(labels).Set(gaugeValue) |
| 177 | } |
| 178 | } |