Add frontend error collector, CORS/ingest guards, and static doc routes (Phase 3)
- Add static/otel-rum.js: a framework-free browser script that captures
uncaught errors, unhandled promise rejections and failed resource loads,
translating them into OTLP log records POSTed to the existing /v1/logs
endpoint (source: frontend attribute) so they flow through the same
history/search/issue pipeline as backend telemetry.
- Add correlation-id support: traceHeaders() (manual) and same-origin
fetch auto-instrumentation inject a W3C traceparent header, so a
frontend error and the backend spans it triggered share a trace id and
can be found together via GET /api/traces/{traceId}.
- Add CORS handling (withCORS in cmd/server/main.go) on the OTLP HTTP
endpoints, required for any browser-origin POST.
- Add an optional shared ingest key (OTEL_INGEST_KEY, via '?key=' or
X-Otel-Ingest-Key) and a per-IP rate limit (20 rps/burst 50) on OTLP
HTTP ingestion, to scope/protect a public endpoint fed by a browser
script. Verified end-to-end (CORS preflight, cross-origin POST, ingest
key enforcement, rate limiting) against a running server.
- Serve static/otel-rum.js and agents_example.md as static HTTP routes so
both the script and the agent instructions doc are fetchable directly
from the base URL.
- Ignore the separately-cloned otel-ui/ frontend repo (has its own .git;
not meant to be nested into this repo's history).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d274ff89c
commit
c20a0b77f3
10 files changed
+489
-17
No files matched your search
+33
-7
@@ -149,6 +149,23 @@ func (s *logsService) Export(ctx context.Context, req *logsgrpc.ExportLogsServic
|
||||
return &logsgrpc.ExportLogsServiceResponse{}, nil
|
||||
}
|
||||
|
||||
// withCORS allows cross-origin ingestion (e.g. from the otel-rum.js browser
|
||||
// script running on a third-party site) into the OTLP HTTP endpoints. It's
|
||||
// deliberately permissive on origin, since this is a public ingest endpoint
|
||||
// scoped by the ingest key/rate limiting instead of same-origin policy.
|
||||
func withCORS(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Otel-Ingest-Key")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Configure history size (default 100, can be made configurable)
|
||||
@@ -162,6 +179,7 @@ func main() {
|
||||
wsServer := websocket.New(coll)
|
||||
// Update collector with wsServer
|
||||
coll.SetBroadcaster(wsServer)
|
||||
coll.SetIngestKey(os.Getenv("OTEL_INGEST_KEY"))
|
||||
|
||||
// Start gRPC server
|
||||
go func() {
|
||||
@@ -186,17 +204,19 @@ func main() {
|
||||
|
||||
// OTLP HTTP mux (standard OTel port 4318)
|
||||
otelMux := http.NewServeMux()
|
||||
otelMux.HandleFunc("/v1/traces", coll.HandleTraces)
|
||||
otelMux.HandleFunc("/v1/metrics", coll.HandleMetrics)
|
||||
otelMux.HandleFunc("/v1/logs", coll.HandleLogs)
|
||||
otelMux.HandleFunc("/v1/traces", withCORS(coll.HandleTraces))
|
||||
otelMux.HandleFunc("/v1/metrics", withCORS(coll.HandleMetrics))
|
||||
otelMux.HandleFunc("/v1/logs", withCORS(coll.HandleLogs))
|
||||
otelMux.HandleFunc("/health", coll.HandleHealth)
|
||||
|
||||
// UI and API mux (port 8080)
|
||||
uiMux := http.NewServeMux()
|
||||
// Also register OTLP endpoints on 8080 for backwards compatibility
|
||||
uiMux.HandleFunc("/v1/traces", coll.HandleTraces)
|
||||
uiMux.HandleFunc("/v1/metrics", coll.HandleMetrics)
|
||||
uiMux.HandleFunc("/v1/logs", coll.HandleLogs)
|
||||
// Also register OTLP endpoints on 8080 for backwards compatibility.
|
||||
// CORS is enabled here since this is the publicly exposed port (via the
|
||||
// ingress) that a browser script such as static/otel-rum.js posts to.
|
||||
uiMux.HandleFunc("/v1/traces", withCORS(coll.HandleTraces))
|
||||
uiMux.HandleFunc("/v1/metrics", withCORS(coll.HandleMetrics))
|
||||
uiMux.HandleFunc("/v1/logs", withCORS(coll.HandleLogs))
|
||||
uiMux.HandleFunc("/health", coll.HandleHealth)
|
||||
|
||||
uiMux.HandleFunc("/api/history", coll.HandleHistory)
|
||||
@@ -209,6 +229,12 @@ func main() {
|
||||
uiMux.HandleFunc("/api/issues", coll.HandleIssues)
|
||||
uiMux.HandleFunc("GET /api/issues/{id}", coll.HandleIssueByID)
|
||||
uiMux.HandleFunc("/ws", wsServer.HandleConnections)
|
||||
uiMux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
||||
// Serve the agent instructions doc as a static route so an AI agent can
|
||||
// fetch it directly from the base URL before making any API calls.
|
||||
uiMux.HandleFunc("GET /agents_example.md", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "agents_example.md")
|
||||
})
|
||||
uiMux.Handle("/", spaHandler(http.Dir("./dist")))
|
||||
|
||||
log.Printf("Starting HTTP server on port 8080 (UI/API)")
|
||||
|
||||
Reference in new issue
Block a user