mattermost-push-proxy-router/proxy.go
Felipe M. 8a4b11281d
feat: add Prometheus metrics endpoint
Expose /metrics with counters for routed requests, routing errors,
upstream outcomes, and upstream latency histograms. Instrument the
router and proxy forwarding path and document the new metrics.
2026-07-27 08:38:28 +02:00

97 lines
2.2 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type Proxy struct {
client *http.Client
cfg Config
metrics *Metrics
}
func NewProxy(cfg Config, metrics *Metrics) *Proxy {
return &Proxy{
client: &http.Client{Timeout: cfg.RequestTimeout},
cfg: cfg,
metrics: metrics,
}
}
type pushResponse struct {
Status string `json:"status"`
}
func (p *Proxy) Forward(w http.ResponseWriter, r *http.Request, backendURL, path string, body []byte, platform string) {
start := time.Now()
target := backendURL + path
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, target, bytes.NewReader(body))
if err != nil {
p.metrics.observeUpstream(platform, "error", time.Since(start))
writePushError(w, fmt.Sprintf("failed to create upstream request: %v", err))
return
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
p.metrics.observeUpstream(platform, "error", time.Since(start))
writePushError(w, fmt.Sprintf("upstream request failed: %v", err))
return
}
defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
p.metrics.observeUpstream(platform, "error", time.Since(start))
writePushError(w, fmt.Sprintf("failed to read upstream response: %v", err))
return
}
p.metrics.observeUpstream(platform, upstreamOutcome(resp.StatusCode, respBody), time.Since(start))
for k, vals := range resp.Header {
for _, v := range vals {
w.Header().Add(k, v)
}
}
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/json")
}
w.WriteHeader(resp.StatusCode)
_, _ = w.Write(respBody)
}
func upstreamOutcome(statusCode int, body []byte) string {
var resp pushResponse
if err := json.Unmarshal(body, &resp); err == nil {
switch resp.Status {
case "OK":
return "success"
case "FAIL":
return "fail"
}
}
if statusCode >= 400 {
return "fail"
}
return "success"
}
func writePushError(w http.ResponseWriter, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
payload, _ := json.Marshal(map[string]string{
"status": "FAIL",
"error": message,
})
_, _ = w.Write(payload)
}