feat: add bounded process metrics and private monitoring endpoint

This commit is contained in:
2026-09-18 13:17:33 +08:00
parent 1f1d833f69
commit a7be32f661
24 changed files with 1467 additions and 19 deletions

View File

@@ -4,9 +4,11 @@ package httpserver
import (
"context"
"encoding/json"
"git.yuntuo.hk/k2safe/orbit/pulse/service"
"net/http"
"time"
"git.yuntuo.hk/k2safe/orbit/internal/observability"
"git.yuntuo.hk/k2safe/orbit/pulse/service"
)
type Checker func(context.Context) error
@@ -36,3 +38,23 @@ func New(addr string, ready Checker, tasks ...*service.Service) *http.Server {
}
return &http.Server{Addr: addr, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 16 << 10}
}
// WrapMetrics must run after all Register* functions because those functions
// intentionally register routes on the underlying ServeMux.
func WrapMetrics(server *http.Server, metrics *observability.Registry) {
if server == nil || metrics == nil {
return
}
server.Handler = metrics.Middleware(server.Handler)
}
// NewMetricsServer uses a distinct listener so public API routing cannot expose
// operational aggregates. It does not provide business or database endpoints.
func NewMetricsServer(addr string, metrics *observability.Registry) *http.Server {
mux := http.NewServeMux()
if metrics != nil {
mux.Handle("GET /metrics", metrics)
}
return &http.Server{Addr: addr, Handler: mux, ReadHeaderTimeout: 2 * time.Second,
ReadTimeout: 3 * time.Second, WriteTimeout: 5 * time.Second, IdleTimeout: 15 * time.Second, MaxHeaderBytes: 8 << 10}
}

View File

@@ -7,6 +7,8 @@ import (
"net/http/httptest"
"strings"
"testing"
"git.yuntuo.hk/k2safe/orbit/internal/observability"
)
func TestHealthSeparatesLivenessAndReadiness(t *testing.T) {
@@ -19,3 +21,28 @@ func TestHealthSeparatesLivenessAndReadiness(t *testing.T) {
}
}
}
func TestMetricsListenerIsSeparateAndOptional(t *testing.T) {
metrics := observability.NewRegistry()
business := New("", nil)
WrapMetrics(business, metrics)
private := NewMetricsServer("", metrics)
for _, tc := range []struct {
handler http.Handler
path string
want int
}{
{business.Handler, "/metrics", 404}, {private.Handler, "/v1/tasks", 404},
{private.Handler, "/metrics", 200}, {NewMetricsServer("", nil).Handler, "/metrics", 404},
{private.Handler, "/metrics?secret=value", 400},
} {
rr := httptest.NewRecorder()
tc.handler.ServeHTTP(rr, httptest.NewRequest("GET", tc.path, nil))
if rr.Code != tc.want {
t.Fatalf("%s: %d", tc.path, rr.Code)
}
if strings.Contains(rr.Body.String(), "secret") {
t.Fatal("query leaked")
}
}
}