Documentation
¶
Index ¶
- Constants
- Variables
- func APIKeyGate(expectedKey, mcpPath string, next http.Handler) http.Handler
- func DBHealthMiddleware(h *DBHealth) func(http.Handler) http.Handler
- func IsProtectedPath(path, mcpPath string) bool
- func LoadTenantKeys(path string) (map[string]string, error)
- func MetricsMiddleware(metrics *telemetry.Metrics, next http.Handler) http.Handler
- func RecoverMiddleware(metrics *telemetry.Metrics, next http.Handler) http.Handler
- func RequireAPIKey(expectedKey string, next http.Handler) http.Handler
- func TenantMiddleware(cfg *config.Config) func(http.Handler) http.Handler
- type DBHealth
- type DBPinger
- type GraphEdge
- type GraphNode
- type NodeMetrics
- type RateLimiter
- type Server
- func (s *Server) BroadcastLog(l storage.Log)
- func (s *Server) RegisterRoutes(mux *http.ServeMux)
- func (s *Server) SetDLQSaturationProbe(fn func() float64)
- func (s *Server) SetGraph(g *graph.Graph)
- func (s *Server) SetGraphRAG(g *graphrag.GraphRAG)
- func (s *Server) SetPipelineSaturationProbe(fn func() float64)
- func (s *Server) SetVectorIndex(idx *vectordb.Index)
- type SystemGraphResponse
- type SystemSummary
- type TenantKeyAuth
Constants ¶
const TenantHeader = "X-Tenant-ID"
TenantHeader is the canonical HTTP header carrying the tenant ID on read-side (query) requests. Ingest paths resolve tenant separately via gRPC metadata / OTLP resource attributes and do not go through this middleware.
Variables ¶
var AuthFailureHook func(reason string)
AuthFailureHook is an optional callback invoked whenever API-key auth rejects a request. Set by main.go to increment the APIAuthFailuresTotal metric. Left as a package-level function pointer (rather than a DI parameter) to avoid circular imports between api and telemetry. Safe to leave nil.
Reasons: "missing_header", "bad_scheme", "bad_key".
var OtelContextStartTime = time.Now()
Functions ¶
func APIKeyGate ¶
APIKeyGate wraps a handler so only requests matching IsProtectedPath require the key. Public paths flow through untouched, which is what keeps the UI bundle and health probes accessible without credentials.
func DBHealthMiddleware ¶
DBHealthMiddleware returns 503 immediately when the DB poller reports unhealthy, for DB-dependent paths. Health/metrics/UI paths bypass the gate.
func IsProtectedPath ¶
IsProtectedPath reports whether a request path requires API-key authentication. Protected: /api/*, /v1/* (OTLP HTTP), and the MCP path. Unprotected: /live, /ready, /health*, /metrics* (Prometheus), /ws* (WebSocket), and the UI static bundle ("/" + assets).
func LoadTenantKeys ¶
LoadTenantKeys parses a tenant-keys file where each non-empty, non-comment line is a `key=tenant` pair. The returned map is keyed by bearer token (value=tenant ID) so an authenticated request can be scoped to the key's bound tenant regardless of any client-asserted X-Tenant-ID header.
File format (YAML/INI-friendly, also accepts plain `key=tenant`):
# one mapping per line, comments start with '#' 5f4dcc3b5aa765d61d8327deb882cf99=acme c20ad4d76fe97759aa27a0c99bff6710=beta
Whitespace around tokens is ignored; duplicate keys return an error so misconfiguration fails loud at startup.
func MetricsMiddleware ¶
MetricsMiddleware records OtelContext_http_requests_total and OtelContext_http_request_duration_seconds for every HTTP request.
func RecoverMiddleware ¶
RecoverMiddleware catches panics from downstream handlers/middleware, logs the stack trace, increments the panics-recovered metric, and responds with a generic 500. It must be installed as the OUTERMOST middleware (after MetricsMiddleware is wrapped around the stack) so panics anywhere below are caught. http.ErrAbortHandler is re-panicked to preserve net/http's sentinel-abort contract.
func RequireAPIKey ¶
RequireAPIKey returns middleware that requires an `Authorization: Bearer <key>` header matching the configured API key. When expectedKey is empty the middleware is a pass-through (auth disabled) — the caller is expected to log a warning at startup in that case.
The comparison is constant-time via subtle.ConstantTimeCompare to avoid timing side channels. On mismatch or missing header a 401 is returned with a JSON body `{"error":"unauthorized"}`.
func TenantMiddleware ¶
TenantMiddleware extracts the tenant ID from the X-Tenant-ID header (falling back to cfg.DefaultTenant — or "default" when cfg is nil or empty) and stashes it on the request context via storage.WithTenantContext so repository reads can scope their WHERE clause with storage.TenantFromContext.
The middleware is path-aware: only requests whose path begins with "/api/" are tenant-scoped. OTLP write endpoints ("/v1/..."), health probes ("/live", "/ready"), Prometheus scrape ("/metrics/..."), MCP, WebSocket and UI assets pass through untouched — these either resolve tenant separately (OTLP) or are tenant-agnostic/privileged.
Types ¶
type DBHealth ¶
type DBHealth struct {
// contains filtered or unexported fields
}
DBHealth periodically pings the database and exposes the result via an atomic boolean. The HTTP middleware below short-circuits /api/* traffic with a 503 when the flag is false, preventing goroutine pile-up on pool acquisition when the DB is unreachable.
To avoid false-positive 503 windows under load — e.g. SQLite with MaxOpen=1 where a busy write transiently blocks the 2s health ping — the poller flips to unhealthy only after failureThreshold consecutive failed pings. A single successful ping clears the counter and restores healthy immediately.
func NewDBHealth ¶
NewDBHealth constructs a health poller. Default poll interval is 5s; ping timeout is 2s per attempt; failureThreshold defaults to 3 consecutive failed pings before the gate flips. Start() must be called to begin polling.
func (*DBHealth) SetFailureThreshold ¶
SetFailureThreshold overrides the number of consecutive failed pings before the middleware flips to 503. n <= 0 normalises to 1 (legacy behaviour: any single failure trips the gate).
type DBPinger ¶
DBPinger is the minimum DB interface DBHealth needs. *sql.DB satisfies it; tests can pass a stub.
type GraphEdge ¶
type GraphEdge struct {
Source string `json:"source"`
Target string `json:"target"`
CallCount int64 `json:"call_count"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
ErrorRate float64 `json:"error_rate"`
Status string `json:"status"`
}
GraphEdge represents a call relationship between two services.
type GraphNode ¶
type GraphNode struct {
ID string `json:"id"`
Type string `json:"type"`
HealthScore float64 `json:"health_score"`
Status string `json:"status"`
Metrics NodeMetrics `json:"metrics"`
Alerts []string `json:"alerts"`
}
GraphNode represents a service in the system graph.
type NodeMetrics ¶
type NodeMetrics struct {
RequestRateRPS float64 `json:"request_rate_rps"`
ErrorRate float64 `json:"error_rate"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
P99LatencyMs float64 `json:"p99_latency_ms"`
SpanCount1H int64 `json:"span_count_1h"`
}
NodeMetrics holds per-service observability metrics.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter is a per-IP token bucket rate limiter middleware.
func NewRateLimiter ¶
func NewRateLimiter(rps float64) *RateLimiter
NewRateLimiter creates a RateLimiter with the given requests-per-second limit.
func (*RateLimiter) Middleware ¶
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler
Middleware returns an http.Handler that enforces rate limiting.
func (*RateLimiter) MiddlewareExcept ¶
func (rl *RateLimiter) MiddlewareExcept(skip func(path string) bool) func(http.Handler) http.Handler
MiddlewareExcept returns a handler chain that applies the rate limit only when skip(path) returns false. Intended to exempt OTLP ingestion paths from the per-IP API limiter.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server handles HTTP API requests.
func NewServer ¶
func NewServer(repo *storage.Repository, hub *realtime.Hub, eventHub *realtime.EventHub, metrics *telemetry.Metrics) *Server
NewServer creates a new API server.
func (*Server) BroadcastLog ¶
BroadcastLog sends a log entry to the buffered WebSocket hub.
func (*Server) RegisterRoutes ¶
RegisterRoutes registers API endpoints on the provided mux.
func (*Server) SetDLQSaturationProbe ¶
SetDLQSaturationProbe registers a callback returning DLQ disk fullness as a fraction in [0.0, 1.0]. Used by /ready to flip to 503 when DLQ is at risk of FIFO-evicting unflushed batches. Pass nil to disable the check.
func (*Server) SetGraphRAG ¶
SetGraphRAG wires the GraphRAG instance for advanced queries.
func (*Server) SetPipelineSaturationProbe ¶
SetPipelineSaturationProbe registers a callback returning ingest pipeline queue fullness as a fraction in [0.0, 1.0]. Used by /ready to flip to 503 when the pipeline is at hard capacity (already returning 429/RESOURCE_EXHAUSTED to clients). Pass nil to disable the check.
func (*Server) SetVectorIndex ¶
SetVectorIndex wires the TF-IDF vector index for semantic log search.
type SystemGraphResponse ¶
type SystemGraphResponse struct {
Timestamp time.Time `json:"timestamp"`
System SystemSummary `json:"system"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
}
SystemGraphResponse is the full AI-consumable system graph.
type SystemSummary ¶
type SystemSummary struct {
TotalServices int `json:"total_services"`
Healthy int `json:"healthy"`
Degraded int `json:"degraded"`
Critical int `json:"critical"`
OverallHealthScore float64 `json:"overall_health_score"`
TotalErrorRate float64 `json:"total_error_rate"`
AvgLatencyMs float64 `json:"avg_latency_ms"`
UptimeSeconds float64 `json:"uptime_seconds"`
}
SystemSummary is the top-level system health summary.
type TenantKeyAuth ¶
type TenantKeyAuth struct {
// contains filtered or unexported fields
}
TenantKeyAuth holds a key→tenant map loaded from APITenantKeysFile and exposes middleware that both authenticates AND pins the tenant onto the request context. When enabled it OVERRIDES any X-Tenant-ID header — callers cannot cross tenants by swapping headers.
func NewTenantKeyAuth ¶
func NewTenantKeyAuth(entries map[string]string) *TenantKeyAuth
NewTenantKeyAuth constructs a TenantKeyAuth from a pre-loaded map. nil or empty disables the layer; callers should fall back to the shared API_KEY path in that case.
func (*TenantKeyAuth) Enabled ¶
func (a *TenantKeyAuth) Enabled() bool
Enabled reports whether per-tenant keys are configured.
func (*TenantKeyAuth) Lookup ¶
func (a *TenantKeyAuth) Lookup(key string) (string, bool)
Lookup returns the tenant bound to the given bearer key, using a constant-time compare against every entry so mismatches don't leak via timing.
func (*TenantKeyAuth) Middleware ¶
Middleware returns an http.Handler wrapper that requires an `Authorization: Bearer <key>` header present in the tenant-keys map. On success the matched tenant is pinned onto the request context via storage.WithTenantContext, overriding any client-supplied X-Tenant-ID. On mismatch (including missing header) it returns 401.
Public paths (IsProtectedPath == false) pass through unchanged so UI assets and health probes remain reachable without credentials.