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.
97 lines
2.2 KiB
Go
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)
|
|
}
|