mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
HIGH-1 amplification: cap events/envelope (1000); module.Ingest pre-aggregates occurrences by fingerprint and store.UpsertIssues writes the whole batch in ONE tx (a flood of identical events => one upsert, count+=N); per-org issue ceiling (10000) drops NEW fingerprints past the cap; per-org token-bucket rate limit (50/s burst 100) after DSN verify; retention/TTL sweep (DeleteStale) gated by O11Y_ERRORTRACKING_RETENTION_DAYS (default 90). MEDIUM-1: envelope item length is bounds-checked (>=0 && <= remaining) before it is added to pos — a MaxInt64/negative length no longer overflows the slice bound; falls back to newline framing. MEDIUM-2: default-on secret redaction (sk-/AKIA/bearer/JWT/conn-string/PAN, always) + PII scrub (email/IP) unless O11Y_ERRORTRACKING_CAPTURE_PII=true (fail-secure, mirrors llmobs capture-messages). Applied before the value enters the fingerprint. MEDIUM-3: DSN keys are versioned '<v>:<hmac>' with a per-org revocation watermark (o11y_ingest_revocations, wholesale-cached); rotate ONE org's min-version to revoke only its below-min DSNs — no global secret roll. LOW-1: lifecycle UpdateIssue is optimistic-concurrency guarded (version column, WHERE version=expected, bump on write; stale => 409 conflict, not clobber). Ingest never touches version. LOW-2: orgFromContext uses NewUUID+error (no MustNewUUID panic on a bad claim). INFO-1: corrected the occurrence-persistence claim + removed the dangling chsink.go reference (only NoopSink exists; logs sink is an honest fast-follow). Tests: 67 pass incl amplification-bounded (5000 events -> 1 upsert), event cap, per-org ceiling, MaxInt64/negative length no-panic, rate-limit, secret/PII scrub, versioned-key + isolated revocation, optimistic-update conflict, DeleteStale. Crown-jewel isolation/parity/DSN tests unchanged and still green.
42 lines
1.9 KiB
Go
42 lines
1.9 KiB
Go
package errortracking
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"github.com/hanzoai/o11y/pkg/types/errortrackingtypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
)
|
|
|
|
// Module is the native error/crash tracking surface (Sentry-class Issues) folded
|
|
// into the o11y plane. Occurrences are OTel exception data in the telemetry store;
|
|
// this module owns the grouped-Issue lifecycle over that data and the ingest that
|
|
// normalizes Sentry-SDK reports into it.
|
|
type Module interface {
|
|
// Ingest groups a BATCH of normalized occurrences into the caller's org (resolved
|
|
// from the DSN by the handler): occurrences are collapsed by fingerprint and
|
|
// upserted in one transaction under the per-org issue ceiling, bounding the write
|
|
// amplification of a single request. Returns issues written.
|
|
Ingest(ctx context.Context, orgID valuer.UUID, occs []*errortrackingtypes.Occurrence) (int, error)
|
|
|
|
ListIssues(ctx context.Context, orgID valuer.UUID, q *errortrackingtypes.IssuesQuery) ([]*errortrackingtypes.Issue, int, error)
|
|
GetIssue(ctx context.Context, orgID, id valuer.UUID) (*errortrackingtypes.GettableIssue, error)
|
|
UpdateIssue(ctx context.Context, orgID, id valuer.UUID, in *errortrackingtypes.UpdateIssue) (*errortrackingtypes.Issue, error)
|
|
}
|
|
|
|
// Handler is the HTTP surface. The ingest endpoints are PUBLIC (OpenAccess) and
|
|
// authenticate the Sentry DSN key in-handler; the read endpoints are behind the
|
|
// shared Hanzo IAM authz middleware and are org-scoped from the validated claims.
|
|
type Handler interface {
|
|
// EnvelopeIngest accepts the modern Sentry envelope wire format
|
|
// (POST /api/{project}/envelope/).
|
|
EnvelopeIngest(rw http.ResponseWriter, r *http.Request)
|
|
// StoreIngest accepts the legacy single-event wire format
|
|
// (POST /api/{project}/store/).
|
|
StoreIngest(rw http.ResponseWriter, r *http.Request)
|
|
|
|
ListIssues(rw http.ResponseWriter, r *http.Request)
|
|
GetIssue(rw http.ResponseWriter, r *http.Request)
|
|
UpdateIssue(rw http.ResponseWriter, r *http.Request)
|
|
}
|