feat(insights): Phase C — Sentry issues + events archive
Mirrors Sentry into our DB before the free tier prunes events (~30d).
- `SentryIssue` (aggregate state, upserted to latest) + `SentryEvent`
(raw occurrences, lossless full payload JSONB, dedup by eventId, cold
dump to MinIO `sentry-archive/{project}/YYYY/MM/DD.jsonl.gz`).
- `lib/sentry.ts`: read-only client, Link-header cursor pagination,
listIssuesPage / listEventsPage. EU-region aware (SENTRY_API_BASE).
- `sentry-archive` job @hourly: issues upsert + events newest-first with
skipDuplicates, stops once a page is all-duplicates (caught up).
- Config via env: SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT /
SENTRY_API_BASE.
Verified live against otolog/python (EU): a test event archived on run 1,
0 inserts / 1 duplicate on run 2 (dedup), issue upserted idempotently.
Note: Sase API currently sends to an inaccessible org's DSN (hardcoded
fallback); repointing SENTRY_DSN to otolog/python is a separate deploy step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -508,3 +508,51 @@ model PosthogCohortSnapshot {
|
||||
@@index([projectKey, capturedAt])
|
||||
@@map("posthog_cohort_snapshots")
|
||||
}
|
||||
|
||||
// ---------- Phase C: Sentry archive ----------
|
||||
|
||||
// Sentry issue (group) aggregate state. Upserted to the latest snapshot — the
|
||||
// raw history lives in SentryEvent; the issue row is the current grouping/metadata.
|
||||
model SentryIssue {
|
||||
id String @id // Sentry issue/group id
|
||||
projectKey String // "sase"
|
||||
shortId String?
|
||||
title String?
|
||||
culprit String?
|
||||
level String?
|
||||
status String?
|
||||
type String?
|
||||
count Int? // total events as of lastSyncedAt
|
||||
userCount Int?
|
||||
firstSeen DateTime?
|
||||
lastSeen DateTime?
|
||||
permalink String?
|
||||
metadata Json?
|
||||
ingestedAt DateTime @default(now())
|
||||
lastSyncedAt DateTime @default(now())
|
||||
|
||||
@@index([projectKey, lastSeen])
|
||||
@@map("sentry_issues")
|
||||
}
|
||||
|
||||
// Individual Sentry events (the at-risk raw occurrences; free tier prunes ~30d).
|
||||
// Lossless full payload as JSONB; also cold-dumped to MinIO.
|
||||
model SentryEvent {
|
||||
eventId String @id // Sentry event id (32 hex)
|
||||
issueId String? // parent issue/group id
|
||||
projectKey String
|
||||
title String?
|
||||
message String?
|
||||
level String?
|
||||
platform String?
|
||||
timestamp DateTime // event occurrence time
|
||||
tags Json?
|
||||
payload Json // full event payload — lossless
|
||||
ingestedAt DateTime @default(now())
|
||||
dumpedAt DateTime?
|
||||
|
||||
@@index([projectKey, timestamp])
|
||||
@@index([issueId, timestamp])
|
||||
@@index([dumpedAt])
|
||||
@@map("sentry_events")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user