mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-29 16:14:53 +00:00
feat: phase 3 observability — IMS telemetry merge
Port IMS telemetry into Primora conventions: - core.components/core.events (migration 00004 + sqlc) scoped to projects - POST /api/v1/ingest — single or <=500-event batches via project prm_ keys (X-Primora-Key alias), auto-registering components, permissive CORS - Issues: fingerprint-grouped errors with count/severity/first+last seen - Reads: events w/ filters + before-cursor, components, stats (1h/24h/7d/30d bucketed series + component health + totals + metric names), metric series (avg/p50/p95/max) - GET .../telemetry/stream — project-scoped SSE through an in-process hub (buffered subs, drop-on-slow, 25s keepalives); ?api_key=/?token= auth for EventSource - Telemetry page: health/issues/events/metrics/integrate tabs, live badge, 390px-clean; demo stubs - @primora/client SDK (createPrimoraClient: capture*/heartbeat/installAuto, queued keepalive flushes) - MCP: primora_list_issues/list_events/telemetry_stats/metric_series - IMS README deprecation note pointing here Also fixes workspace pins to 0.3.0 — npm ci in the frontend image was resolving the unpublished 0.2.0 spec from the registry. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6507eee606
commit
8cfddf9184
60 files changed
+3694
-30
No files matched your search
@@ -0,0 +1,50 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EventHub fans out ingested telemetry events to SSE subscribers, keyed by
|
||||
// project ID. Slow consumers are dropped rather than back-pressured.
|
||||
type EventHub struct {
|
||||
mu sync.Mutex
|
||||
subs map[string]map[chan []byte]struct{}
|
||||
}
|
||||
|
||||
func NewEventHub() *EventHub {
|
||||
return &EventHub{subs: map[string]map[chan []byte]struct{}{}}
|
||||
}
|
||||
|
||||
func (h *EventHub) Subscribe(projectID string) chan []byte {
|
||||
ch := make(chan []byte, 64)
|
||||
h.mu.Lock()
|
||||
if h.subs[projectID] == nil {
|
||||
h.subs[projectID] = map[chan []byte]struct{}{}
|
||||
}
|
||||
h.subs[projectID][ch] = struct{}{}
|
||||
h.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (h *EventHub) Unsubscribe(projectID string, ch chan []byte) {
|
||||
h.mu.Lock()
|
||||
delete(h.subs[projectID], ch)
|
||||
h.mu.Unlock()
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func (h *EventHub) Broadcast(projectID string, v any) {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
for ch := range h.subs[projectID] {
|
||||
select {
|
||||
case ch <- data:
|
||||
default:
|
||||
}
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
Reference in new issue
Block a user