@growth-labs/monitoring 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +115 -0
- package/SPEC.md +19 -0
- package/dist/alerting/dedup.d.ts +6 -0
- package/dist/alerting/dedup.d.ts.map +1 -0
- package/dist/alerting/dedup.js +49 -0
- package/dist/alerting/dedup.js.map +1 -0
- package/dist/alerting/escalation.d.ts +4 -0
- package/dist/alerting/escalation.d.ts.map +1 -0
- package/dist/alerting/escalation.js +26 -0
- package/dist/alerting/escalation.js.map +1 -0
- package/dist/alerting/index.d.ts +31 -0
- package/dist/alerting/index.d.ts.map +1 -0
- package/dist/alerting/index.js +50 -0
- package/dist/alerting/index.js.map +1 -0
- package/dist/alerting/thresholds.d.ts +8 -0
- package/dist/alerting/thresholds.d.ts.map +1 -0
- package/dist/alerting/thresholds.js +105 -0
- package/dist/alerting/thresholds.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/prober/index.d.ts +19 -0
- package/dist/prober/index.d.ts.map +1 -0
- package/dist/prober/index.js +48 -0
- package/dist/prober/index.js.map +1 -0
- package/dist/prober/persist.d.ts +4 -0
- package/dist/prober/persist.d.ts.map +1 -0
- package/dist/prober/persist.js +21 -0
- package/dist/prober/persist.js.map +1 -0
- package/dist/prober/runners/get-runner.d.ts +4 -0
- package/dist/prober/runners/get-runner.d.ts.map +1 -0
- package/dist/prober/runners/get-runner.js +43 -0
- package/dist/prober/runners/get-runner.js.map +1 -0
- package/dist/prober/runners/happy-path-runner.d.ts +13 -0
- package/dist/prober/runners/happy-path-runner.d.ts.map +1 -0
- package/dist/prober/runners/happy-path-runner.js +183 -0
- package/dist/prober/runners/happy-path-runner.js.map +1 -0
- package/dist/prober/runners/post-runner.d.ts +4 -0
- package/dist/prober/runners/post-runner.d.ts.map +1 -0
- package/dist/prober/runners/post-runner.js +44 -0
- package/dist/prober/runners/post-runner.js.map +1 -0
- package/dist/prober/surfaces.d.ts +46 -0
- package/dist/prober/surfaces.d.ts.map +1 -0
- package/dist/prober/surfaces.js +2 -0
- package/dist/prober/surfaces.js.map +1 -0
- package/dist/schemas/drizzle/schema.d.ts +519 -0
- package/dist/schemas/drizzle/schema.d.ts.map +1 -0
- package/dist/schemas/drizzle/schema.js +45 -0
- package/dist/schemas/drizzle/schema.js.map +1 -0
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/migrations/0001_uptime_checks.sql +12 -0
- package/dist/schemas/migrations/0002_uptime_incidents.sql +12 -0
- package/dist/schemas/migrations/0003_errors.sql +16 -0
- package/dist/schemas/migrations/README.md +15 -0
- package/dist/status-page/app.d.ts +10 -0
- package/dist/status-page/app.d.ts.map +1 -0
- package/dist/status-page/app.js +44 -0
- package/dist/status-page/app.js.map +1 -0
- package/dist/status-page/lib/queries.d.ts +6 -0
- package/dist/status-page/lib/queries.d.ts.map +1 -0
- package/dist/status-page/lib/queries.js +81 -0
- package/dist/status-page/lib/queries.js.map +1 -0
- package/dist/status-page/pages/api/status.json.d.ts +3 -0
- package/dist/status-page/pages/api/status.json.d.ts.map +1 -0
- package/dist/status-page/pages/api/status.json.js +19 -0
- package/dist/status-page/pages/api/status.json.js.map +1 -0
- package/dist/status-page/shell.d.ts +3 -0
- package/dist/status-page/shell.d.ts.map +1 -0
- package/dist/status-page/shell.js +18 -0
- package/dist/status-page/shell.js.map +1 -0
- package/dist/tail/categorize.d.ts +44 -0
- package/dist/tail/categorize.d.ts.map +1 -0
- package/dist/tail/categorize.js +113 -0
- package/dist/tail/categorize.js.map +1 -0
- package/dist/tail/fingerprint.d.ts +4 -0
- package/dist/tail/fingerprint.d.ts.map +1 -0
- package/dist/tail/fingerprint.js +18 -0
- package/dist/tail/fingerprint.js.map +1 -0
- package/dist/tail/index.d.ts +21 -0
- package/dist/tail/index.d.ts.map +1 -0
- package/dist/tail/index.js +50 -0
- package/dist/tail/index.js.map +1 -0
- package/dist/tail/persist.d.ts +15 -0
- package/dist/tail/persist.d.ts.map +1 -0
- package/dist/tail/persist.js +63 -0
- package/dist/tail/persist.js.map +1 -0
- package/dist/tail/redact.d.ts +5 -0
- package/dist/tail/redact.d.ts.map +1 -0
- package/dist/tail/redact.js +25 -0
- package/dist/tail/redact.js.map +1 -0
- package/dist/tail/sample.d.ts +9 -0
- package/dist/tail/sample.d.ts.map +1 -0
- package/dist/tail/sample.js +25 -0
- package/dist/tail/sample.js.map +1 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -0
- package/src/schemas/migrations/0001_uptime_checks.sql +12 -0
- package/src/schemas/migrations/0002_uptime_incidents.sql +12 -0
- package/src/schemas/migrations/0003_errors.sql +16 -0
- package/src/schemas/migrations/README.md +15 -0
- package/src/status-page/README.md +14 -0
- package/src/status-page/app.ts +58 -0
- package/src/status-page/components/ErrorRollup.astro +26 -0
- package/src/status-page/components/IncidentList.astro +25 -0
- package/src/status-page/components/SurfaceRow.astro +24 -0
- package/src/status-page/lib/queries.ts +114 -0
- package/src/status-page/pages/api/status.json.ts +24 -0
- package/src/status-page/pages/index.astro +45 -0
- package/src/status-page/shell.ts +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @growth-labs/monitoring
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-05-18
|
|
4
|
+
|
|
5
|
+
- Added synthetic prober factory with GET, POST, and code-signin happy-path runners.
|
|
6
|
+
- Added Tail Worker trace categorization, redaction, fingerprinting, sampling, D1 persistence, and WAE writes.
|
|
7
|
+
- Added alerting thresholds, incident dedup/open/close helpers, and escalation actions that delegate delivery to `@growth-labs/notify`.
|
|
8
|
+
- Added D1 SQL migrations plus matching Drizzle schema for uptime checks, incidents, and error events.
|
|
9
|
+
- Added Astro status-page integration, query helpers, read-only HTML view, and `/api/status.json`.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @growth-labs/monitoring
|
|
2
|
+
|
|
3
|
+
Operational observability for Growth Labs Cloudflare Workers: synthetic probes,
|
|
4
|
+
Tail Worker error capture, uptime/error schemas, alerting, and a small public
|
|
5
|
+
status page.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @growth-labs/monitoring @growth-labs/notify @growth-labs/analytics
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer: `astro ^6.0.0` for the status-page app. Runtime deps:
|
|
14
|
+
`@growth-labs/notify`, `@growth-labs/analytics`, `drizzle-orm`, `zod`.
|
|
15
|
+
|
|
16
|
+
## Modules
|
|
17
|
+
|
|
18
|
+
| Subpath | Purpose |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `@growth-labs/monitoring` | Umbrella exports for the main factories. |
|
|
21
|
+
| `@growth-labs/monitoring/prober` | `createProber(config)` for Cron Workers that run GET, POST, and happy-path checks. |
|
|
22
|
+
| `@growth-labs/monitoring/tail` | `createTailWorker(config)` for Cloudflare Tail Worker trace batches. |
|
|
23
|
+
| `@growth-labs/monitoring/alerting` | Threshold, dedup, incident, escalation, and notify delegation helpers. |
|
|
24
|
+
| `@growth-labs/monitoring/status-page` | Astro integration that injects the status page and JSON route. |
|
|
25
|
+
| `@growth-labs/monitoring/schemas` | Drizzle schema matching the shipped D1 migrations. |
|
|
26
|
+
|
|
27
|
+
Raw SQL migrations ship at:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
node_modules/@growth-labs/monitoring/src/schemas/migrations
|
|
31
|
+
node_modules/@growth-labs/monitoring/dist/schemas/migrations
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Prober
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createProber } from '@growth-labs/monitoring/prober'
|
|
38
|
+
|
|
39
|
+
const prober = createProber({
|
|
40
|
+
realmId: 'fulcrum-labs',
|
|
41
|
+
d1Binding: 'MONITORING_DB',
|
|
42
|
+
notifyConfig: {
|
|
43
|
+
channels: ['slack', 'email'],
|
|
44
|
+
emailProvider: 'resend',
|
|
45
|
+
},
|
|
46
|
+
alertingConfig: { consecutiveFailuresToOpen: 2, minSeverity: 'warning' },
|
|
47
|
+
surfaces: [
|
|
48
|
+
{
|
|
49
|
+
name: 'fronts.co:login',
|
|
50
|
+
kind: 'get',
|
|
51
|
+
schedule: '*/5 * * * *',
|
|
52
|
+
url: 'https://fronts.co/login/',
|
|
53
|
+
assertions: { statusCode: 200, contentMarker: 'auth-eyebrow' },
|
|
54
|
+
timeoutMs: 10_000,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export default { scheduled: prober.scheduledHandler }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`auth-monitor` must use `emailProvider: 'resend'` when email alerts are enabled.
|
|
63
|
+
The monitor exists partly to detect Cloudflare auth delivery failures, so alert
|
|
64
|
+
email must not depend on Cloudflare Email.
|
|
65
|
+
|
|
66
|
+
## Tail Worker
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createTailWorker } from '@growth-labs/monitoring/tail'
|
|
70
|
+
|
|
71
|
+
const tail = createTailWorker({
|
|
72
|
+
realmId: 'fulcrum-labs',
|
|
73
|
+
d1Binding: 'MONITORING_DB',
|
|
74
|
+
waeBinding: 'MONITORING_ERRORS',
|
|
75
|
+
notifyConfig: { channels: ['slack', 'email'], emailProvider: 'resend' },
|
|
76
|
+
alertingConfig: {
|
|
77
|
+
newErrorDedupWindowMs: 60 * 60 * 1000,
|
|
78
|
+
rateSpikeThreshold: 10,
|
|
79
|
+
rateSpikeWindowMs: 5 * 60 * 1000,
|
|
80
|
+
},
|
|
81
|
+
sampling: {
|
|
82
|
+
exceptionsPct: 1,
|
|
83
|
+
fivexxPct: 1,
|
|
84
|
+
consoleErrorPct: 1,
|
|
85
|
+
consoleWarnPct: 0.1,
|
|
86
|
+
slowRequestsPct: 0.01,
|
|
87
|
+
slowRequestThresholdMs: 1000,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export default { tail: tail.tailHandler }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Fingerprints are computed before redaction. Every persisted error is redacted
|
|
95
|
+
and truncated before D1/WAE writes.
|
|
96
|
+
|
|
97
|
+
## Status Page
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { createStatusPageApp } from '@growth-labs/monitoring/status-page'
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
integrations: [
|
|
104
|
+
createStatusPageApp({
|
|
105
|
+
realm: 'fulcrum-labs',
|
|
106
|
+
d1Binding: 'MONITORING_DB',
|
|
107
|
+
surfaces: [{ name: 'fronts.co:login' }],
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The status page is read-only. It injects `/` and `/api/status.json` by default
|
|
114
|
+
and reads only the `gl_uptime_checks`, `gl_uptime_incidents`, and `gl_errors`
|
|
115
|
+
tables.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @growth-labs/monitoring v0.1.0 Spec
|
|
2
|
+
|
|
3
|
+
This package implements the operational observability contract for
|
|
4
|
+
`growth-labs/monitoring-platform`.
|
|
5
|
+
|
|
6
|
+
Included modules:
|
|
7
|
+
|
|
8
|
+
- Synthetic prober Cron Worker helpers for GET, POST, and code-signin happy paths.
|
|
9
|
+
- Tail Worker error capture for exceptions, 5xx responses, console errors/warnings, and slow requests.
|
|
10
|
+
- D1 schemas for `gl_uptime_checks`, `gl_uptime_incidents`, and `gl_errors`.
|
|
11
|
+
- Alerting thresholds, dedup, incident lifecycle, and escalation actions.
|
|
12
|
+
- Read-only Astro status page and JSON snapshot route.
|
|
13
|
+
|
|
14
|
+
Out of scope:
|
|
15
|
+
|
|
16
|
+
- Surface ownership. Consumers pass surface lists in config.
|
|
17
|
+
- Scheduling ownership. Cloudflare Cron triggers invoke the exported handler.
|
|
18
|
+
- Notification transport implementation. `@growth-labs/notify` sends alerts.
|
|
19
|
+
- Active remediation or historical replay.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type IdFactory, type UptimeIncident } from '../types.js';
|
|
2
|
+
import type { AlertAction } from './index.js';
|
|
3
|
+
export declare function isIncidentOpen(surface: string, db: D1Database): Promise<UptimeIncident | null>;
|
|
4
|
+
export declare function openIncident(action: AlertAction, db: D1Database, idFactory?: IdFactory, now?: () => number): Promise<string>;
|
|
5
|
+
export declare function closeIncident(incidentId: string, resolveCheckId: string, db: D1Database, now?: () => number): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=dedup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup.d.ts","sourceRoot":"","sources":["../../src/alerting/dedup.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAC3F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,wBAAsB,cAAc,CACnC,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,UAAU,GACZ,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAWhC;AAED,wBAAsB,YAAY,CACjC,MAAM,EAAE,WAAW,EACnB,EAAE,EAAE,UAAU,EACd,SAAS,GAAE,SAAsB,EACjC,GAAG,eAAW,GACZ,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,wBAAsB,aAAa,CAClC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,UAAU,EACd,GAAG,eAAW,GACZ,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { errorMessage, generateId } from '../types.js';
|
|
2
|
+
export async function isIncidentOpen(surface, db) {
|
|
3
|
+
return db
|
|
4
|
+
.prepare(`
|
|
5
|
+
SELECT id, surface, opened_at, closed_at, trigger_check_id, resolve_check_id, severity, notes
|
|
6
|
+
FROM gl_uptime_incidents
|
|
7
|
+
WHERE surface = ? AND closed_at IS NULL
|
|
8
|
+
ORDER BY opened_at DESC
|
|
9
|
+
LIMIT 1
|
|
10
|
+
`)
|
|
11
|
+
.bind(surface)
|
|
12
|
+
.first();
|
|
13
|
+
}
|
|
14
|
+
export async function openIncident(action, db, idFactory = generateId, now = Date.now) {
|
|
15
|
+
const existing = await isIncidentOpen(action.surface, db);
|
|
16
|
+
if (existing)
|
|
17
|
+
return existing.id;
|
|
18
|
+
const id = idFactory();
|
|
19
|
+
try {
|
|
20
|
+
await db
|
|
21
|
+
.prepare(`
|
|
22
|
+
INSERT INTO gl_uptime_incidents
|
|
23
|
+
(id, surface, opened_at, trigger_check_id, severity, notes)
|
|
24
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
25
|
+
`)
|
|
26
|
+
.bind(id, action.surface, Math.floor(now() / 1000), typeof action.payload?.triggerCheckId === 'string' ? action.payload.triggerCheckId : null, action.severity === 'critical' ? 'critical' : 'warning', typeof action.payload?.notes === 'string' ? action.payload.notes : null)
|
|
27
|
+
.run();
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(`[monitoring] failed to open incident ${action.surface}: ${errorMessage(error)}`);
|
|
31
|
+
}
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
export async function closeIncident(incidentId, resolveCheckId, db, now = Date.now) {
|
|
35
|
+
try {
|
|
36
|
+
await db
|
|
37
|
+
.prepare(`
|
|
38
|
+
UPDATE gl_uptime_incidents
|
|
39
|
+
SET closed_at = ?, resolve_check_id = ?
|
|
40
|
+
WHERE id = ? AND closed_at IS NULL
|
|
41
|
+
`)
|
|
42
|
+
.bind(Math.floor(now() / 1000), resolveCheckId, incidentId)
|
|
43
|
+
.run();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(`[monitoring] failed to close incident ${incidentId}: ${errorMessage(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=dedup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/alerting/dedup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAuC,MAAM,aAAa,CAAA;AAG3F,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,OAAe,EACf,EAAc;IAEd,OAAO,EAAE;SACP,OAAO,CAAC;;;;;;GAMR,CAAC;SACD,IAAI,CAAC,OAAO,CAAC;SACb,KAAK,EAAkB,CAAA;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,MAAmB,EACnB,EAAc,EACd,YAAuB,UAAU,EACjC,GAAG,GAAG,IAAI,CAAC,GAAG;IAEd,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IACzD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,EAAE,CAAA;IAEhC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAA;IACtB,IAAI,CAAC;QACJ,MAAM,EAAE;aACN,OAAO,CAAC;;;;IAIR,CAAC;aACD,IAAI,CACJ,EAAE,EACF,MAAM,CAAC,OAAO,EACd,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EACxB,OAAO,MAAM,CAAC,OAAO,EAAE,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,EACzF,MAAM,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EACvD,OAAO,MAAM,CAAC,OAAO,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACvE;aACA,GAAG,EAAE,CAAA;IACR,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,wCAAwC,MAAM,CAAC,OAAO,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAChG,CAAC;IACD,OAAO,EAAE,CAAA;AACV,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,UAAkB,EAClB,cAAsB,EACtB,EAAc,EACd,GAAG,GAAG,IAAI,CAAC,GAAG;IAEd,IAAI,CAAC;QACJ,MAAM,EAAE;aACN,OAAO,CAAC;;;;IAIR,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,cAAc,EAAE,UAAU,CAAC;aAC1D,GAAG,EAAE,CAAA;IACR,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,yCAAyC,UAAU,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC7F,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UptimeIncident } from '../types.js';
|
|
2
|
+
import type { AlertAction } from './index.js';
|
|
3
|
+
export declare function evaluateEscalation(incident: UptimeIncident, nowSeconds?: number, alreadySent?: Set<string>): AlertAction | null;
|
|
4
|
+
//# sourceMappingURL=escalation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation.d.ts","sourceRoot":"","sources":["../../src/alerting/escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,cAAc,EACxB,UAAU,SAAgC,EAC1C,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GAClC,WAAW,GAAG,IAAI,CAqBpB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function evaluateEscalation(incident, nowSeconds = Math.floor(Date.now() / 1000), alreadySent = new Set()) {
|
|
2
|
+
if (incident.closed_at !== null)
|
|
3
|
+
return null;
|
|
4
|
+
const ageSeconds = nowSeconds - incident.opened_at;
|
|
5
|
+
const checkpoints = [
|
|
6
|
+
{ key: '24h', seconds: 24 * 60 * 60, severity: 'critical' },
|
|
7
|
+
{ key: '4h', seconds: 4 * 60 * 60, severity: 'critical' },
|
|
8
|
+
{ key: '30m', seconds: 30 * 60, severity: 'critical' },
|
|
9
|
+
];
|
|
10
|
+
const checkpoint = checkpoints.find((item) => ageSeconds >= item.seconds);
|
|
11
|
+
if (!checkpoint)
|
|
12
|
+
return null;
|
|
13
|
+
const dedupKey = `escalation:${incident.id}:${checkpoint.key}`;
|
|
14
|
+
if (alreadySent.has(dedupKey))
|
|
15
|
+
return null;
|
|
16
|
+
return {
|
|
17
|
+
kind: 'escalation',
|
|
18
|
+
surface: incident.surface,
|
|
19
|
+
severity: checkpoint.severity,
|
|
20
|
+
title: `${incident.surface} incident still open`,
|
|
21
|
+
body: `Incident ${incident.id} has been open for ${checkpoint.key}.`,
|
|
22
|
+
dedupKey,
|
|
23
|
+
payload: { incidentId: incident.id, checkpoint: checkpoint.key },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=escalation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escalation.js","sourceRoot":"","sources":["../../src/alerting/escalation.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,kBAAkB,CACjC,QAAwB,EACxB,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAC1C,cAA2B,IAAI,GAAG,EAAE;IAEpC,IAAI,QAAQ,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC5C,MAAM,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAA;IAClD,MAAM,WAAW,GAAG;QACnB,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,UAAmB,EAAE;QACpE,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,UAAmB,EAAE;QAClE,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,UAAmB,EAAE;KAC/D,CAAA;IACD,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,CAAA;IACzE,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAC5B,MAAM,QAAQ,GAAG,cAAc,QAAQ,CAAC,EAAE,IAAI,UAAU,CAAC,GAAG,EAAE,CAAA;IAC9D,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1C,OAAO;QACN,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,KAAK,EAAE,GAAG,QAAQ,CAAC,OAAO,sBAAsB;QAChD,IAAI,EAAE,YAAY,QAAQ,CAAC,EAAE,sBAAsB,UAAU,CAAC,GAAG,GAAG;QACpE,QAAQ;QACR,OAAO,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,EAAE;KAChE,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type NotifyResult } from '@growth-labs/notify';
|
|
2
|
+
import type { CategorizedEvent } from '../tail/categorize.js';
|
|
3
|
+
import type { AlertSeverity, NotifyConfig, PersistedCheckResult, ThresholdConfig } from '../types.js';
|
|
4
|
+
export type AlertKind = 'open-incident' | 'close-incident' | 'rate-spike' | 'new-error' | 'escalation';
|
|
5
|
+
export interface AlertAction {
|
|
6
|
+
kind: AlertKind;
|
|
7
|
+
surface: string;
|
|
8
|
+
severity: AlertSeverity;
|
|
9
|
+
title: string;
|
|
10
|
+
body: string;
|
|
11
|
+
dedupKey: string;
|
|
12
|
+
payload?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface AlertEngine {
|
|
15
|
+
activeIncidentSurfaces: Set<string>;
|
|
16
|
+
handleProbeResult(result: PersistedCheckResult, surfaceName: string, db: D1Database, env: Record<string, unknown>): Promise<AlertAction | null>;
|
|
17
|
+
handleErrorEvent(event: CategorizedEvent & {
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
}, db: D1Database, env: Record<string, unknown>): Promise<AlertAction | null>;
|
|
20
|
+
send(env: Record<string, unknown>, action: AlertAction): Promise<NotifyResult>;
|
|
21
|
+
}
|
|
22
|
+
export interface AlerterConfig {
|
|
23
|
+
notifyConfig: NotifyConfig;
|
|
24
|
+
alertingConfig?: ThresholdConfig;
|
|
25
|
+
notifyFn?: (env: Record<string, unknown>, action: AlertAction) => Promise<NotifyResult>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createAlerter(config: AlerterConfig): AlertEngine;
|
|
28
|
+
export { closeIncident, isIncidentOpen, openIncident } from './dedup.js';
|
|
29
|
+
export { evaluateEscalation } from './escalation.js';
|
|
30
|
+
export { evaluateError, evaluateProbe } from './thresholds.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/alerting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,YAAY,EAAU,MAAM,qBAAqB,CAAA;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,eAAe,EACf,MAAM,aAAa,CAAA;AAIpB,MAAM,MAAM,SAAS,GAClB,eAAe,GACf,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,YAAY,CAAA;AAEf,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,aAAa,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,WAAW;IAC3B,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACnC,iBAAiB,CAChB,MAAM,EAAE,oBAAoB,EAC5B,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC9B,gBAAgB,CACf,KAAK,EAAE,gBAAgB,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,EACjD,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CAC9E;AAED,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,CAAC,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACvF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAwChE;AAED,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { notify } from '@growth-labs/notify';
|
|
2
|
+
import { closeIncident, openIncident } from './dedup.js';
|
|
3
|
+
import { evaluateError, evaluateProbe } from './thresholds.js';
|
|
4
|
+
export function createAlerter(config) {
|
|
5
|
+
const activeIncidentSurfaces = new Set();
|
|
6
|
+
const thresholds = config.alertingConfig ?? {};
|
|
7
|
+
async function send(env, action) {
|
|
8
|
+
if (config.notifyFn)
|
|
9
|
+
return config.notifyFn(env, action);
|
|
10
|
+
return notify(env, {
|
|
11
|
+
...config.notifyConfig,
|
|
12
|
+
severity: action.severity,
|
|
13
|
+
title: action.title,
|
|
14
|
+
body: action.body,
|
|
15
|
+
dedupKey: action.dedupKey,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
activeIncidentSurfaces,
|
|
20
|
+
async handleProbeResult(result, surfaceName, db, env) {
|
|
21
|
+
const action = await evaluateProbe(result, surfaceName, db, thresholds);
|
|
22
|
+
if (!action)
|
|
23
|
+
return null;
|
|
24
|
+
if (action.kind === 'open-incident') {
|
|
25
|
+
await openIncident(action, db);
|
|
26
|
+
activeIncidentSurfaces.add(action.surface);
|
|
27
|
+
}
|
|
28
|
+
if (action.kind === 'close-incident' && typeof action.payload?.incidentId === 'string') {
|
|
29
|
+
await closeIncident(action.payload.incidentId, result.id, db);
|
|
30
|
+
activeIncidentSurfaces.delete(action.surface);
|
|
31
|
+
}
|
|
32
|
+
await send(env, action);
|
|
33
|
+
return action;
|
|
34
|
+
},
|
|
35
|
+
async handleErrorEvent(event, db, env) {
|
|
36
|
+
const action = await evaluateError(event, db, thresholds);
|
|
37
|
+
if (!action)
|
|
38
|
+
return null;
|
|
39
|
+
if (action.kind === 'rate-spike')
|
|
40
|
+
activeIncidentSurfaces.add(action.surface);
|
|
41
|
+
await send(env, action);
|
|
42
|
+
return action;
|
|
43
|
+
},
|
|
44
|
+
send,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export { closeIncident, isIncidentOpen, openIncident } from './dedup.js';
|
|
48
|
+
export { evaluateEscalation } from './escalation.js';
|
|
49
|
+
export { evaluateError, evaluateProbe } from './thresholds.js';
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/alerting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAQ/E,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAyC9D,MAAM,UAAU,aAAa,CAAC,MAAqB;IAClD,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAA;IAChD,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,IAAI,EAAE,CAAA;IAE9C,KAAK,UAAU,IAAI,CAAC,GAA4B,EAAE,MAAmB;QACpE,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,GAAgB,EAAE;YAC/B,GAAG,MAAM,CAAC,YAAY;YACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;SACzB,CAAC,CAAA;IACH,CAAC;IAED,OAAO;QACN,sBAAsB;QACtB,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG;YACnD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,UAAU,CAAC,CAAA;YACvE,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YACxB,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACrC,MAAM,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;gBAC9B,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC3C,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,KAAK,gBAAgB,IAAI,OAAO,MAAM,CAAC,OAAO,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC;gBACxF,MAAM,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;gBAC7D,sBAAsB,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC9C,CAAC;YACD,MAAM,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YACvB,OAAO,MAAM,CAAA;QACd,CAAC;QACD,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG;YACpC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,UAAU,CAAC,CAAA;YACzD,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YACxB,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;gBAAE,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC5E,MAAM,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YACvB,OAAO,MAAM,CAAA;QACd,CAAC;QACD,IAAI;KACJ,CAAA;AACF,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CategorizedEvent } from '../tail/categorize.js';
|
|
2
|
+
import type { CheckResult, ThresholdConfig } from '../types.js';
|
|
3
|
+
import type { AlertAction } from './index.js';
|
|
4
|
+
export declare function evaluateProbe(result: CheckResult, surfaceName: string, db: D1Database, config?: ThresholdConfig): Promise<AlertAction | null>;
|
|
5
|
+
export declare function evaluateError(event: CategorizedEvent & {
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
}, db: D1Database, config?: ThresholdConfig): Promise<AlertAction | null>;
|
|
8
|
+
//# sourceMappingURL=thresholds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.d.ts","sourceRoot":"","sources":["../../src/alerting/thresholds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAkB,MAAM,aAAa,CAAA;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAkB7C,wBAAsB,aAAa,CAClC,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,UAAU,EACd,MAAM,GAAE,eAAoB,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA8B7B;AAED,wBAAsB,aAAa,CAClC,KAAK,EAAE,gBAAgB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,EACjD,EAAE,EAAE,UAAU,EACd,MAAM,GAAE,eAAoB,GAC1B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA2C7B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isIncidentOpen } from './dedup.js';
|
|
2
|
+
const DEFAULT_FAILURES_TO_OPEN = 2;
|
|
3
|
+
const DEFAULT_SUCCESSES_TO_CLOSE = 1;
|
|
4
|
+
const DEFAULT_DEDUP_WINDOW_MS = 60 * 60 * 1000;
|
|
5
|
+
const DEFAULT_RATE_SPIKE_THRESHOLD = 10;
|
|
6
|
+
const DEFAULT_RATE_SPIKE_WINDOW_MS = 5 * 60 * 1000;
|
|
7
|
+
export async function evaluateProbe(result, surfaceName, db, config = {}) {
|
|
8
|
+
const open = await isIncidentOpen(surfaceName, db);
|
|
9
|
+
if (result.status === 'pass') {
|
|
10
|
+
if (!open)
|
|
11
|
+
return null;
|
|
12
|
+
const successesToClose = config.consecutiveSuccessesToClose ?? DEFAULT_SUCCESSES_TO_CLOSE;
|
|
13
|
+
const recent = await recentChecks(db, surfaceName, successesToClose);
|
|
14
|
+
if (recent.length >= successesToClose && recent.every((row) => row.status === 'pass')) {
|
|
15
|
+
return closeIncidentAction(open, result);
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (open)
|
|
20
|
+
return null;
|
|
21
|
+
const failuresToOpen = config.consecutiveFailuresToOpen ?? DEFAULT_FAILURES_TO_OPEN;
|
|
22
|
+
const recent = await recentChecks(db, surfaceName, failuresToOpen);
|
|
23
|
+
if (recent.length < failuresToOpen)
|
|
24
|
+
return null;
|
|
25
|
+
if (!recent.every((row) => row.status !== 'pass'))
|
|
26
|
+
return null;
|
|
27
|
+
const severity = config.minSeverity ?? 'warning';
|
|
28
|
+
return {
|
|
29
|
+
kind: 'open-incident',
|
|
30
|
+
surface: surfaceName,
|
|
31
|
+
severity,
|
|
32
|
+
title: `${surfaceName} synthetic check failing`,
|
|
33
|
+
body: `${failuresToOpen} consecutive synthetic checks failed. Latest status: ${result.status}${result.errorMessage ? ` (${result.errorMessage})` : ''}.`,
|
|
34
|
+
dedupKey: `incident:${surfaceName}`,
|
|
35
|
+
payload: { triggerCheckId: result.id },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function evaluateError(event, db, config = {}) {
|
|
39
|
+
const occurredAtSeconds = Math.floor(event.occurredAt / 1000);
|
|
40
|
+
const rateWindowMs = config.rateSpikeWindowMs ?? DEFAULT_RATE_SPIKE_WINDOW_MS;
|
|
41
|
+
const rateSpikeThreshold = config.rateSpikeThreshold ?? DEFAULT_RATE_SPIKE_THRESHOLD;
|
|
42
|
+
const rateCount = await countFingerprintSince(db, event.fingerprint, occurredAtSeconds - Math.floor(rateWindowMs / 1000));
|
|
43
|
+
if (rateCount >= rateSpikeThreshold) {
|
|
44
|
+
return {
|
|
45
|
+
kind: 'rate-spike',
|
|
46
|
+
surface: event.surface,
|
|
47
|
+
severity: 'critical',
|
|
48
|
+
title: `${event.surface} error rate spike`,
|
|
49
|
+
body: `${rateCount} events with fingerprint ${event.fingerprint} occurred in the last ${Math.round(rateWindowMs / 60_000)} minutes.`,
|
|
50
|
+
dedupKey: `rate-spike:${event.surface}:${event.fingerprint}`,
|
|
51
|
+
payload: { fingerprint: event.fingerprint, count: rateCount },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const dedupWindowMs = config.newErrorDedupWindowMs ?? DEFAULT_DEDUP_WINDOW_MS;
|
|
55
|
+
const dedupCount = await countFingerprintSince(db, event.fingerprint, occurredAtSeconds - Math.floor(dedupWindowMs / 1000));
|
|
56
|
+
if (dedupCount > 0)
|
|
57
|
+
return null;
|
|
58
|
+
return {
|
|
59
|
+
kind: 'new-error',
|
|
60
|
+
surface: event.surface,
|
|
61
|
+
severity: event.category === 'console-warn' || event.category === 'slow-request'
|
|
62
|
+
? 'warning'
|
|
63
|
+
: 'critical',
|
|
64
|
+
title: `${event.surface} new error fingerprint`,
|
|
65
|
+
body: `${event.category} fingerprint ${event.fingerprint}: ${event.message}`,
|
|
66
|
+
dedupKey: `new-error:${event.fingerprint}`,
|
|
67
|
+
payload: { fingerprint: event.fingerprint, category: event.category },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function recentChecks(db, surface, limit) {
|
|
71
|
+
const { results } = await db
|
|
72
|
+
.prepare(`
|
|
73
|
+
SELECT id, status, checked_at
|
|
74
|
+
FROM gl_uptime_checks
|
|
75
|
+
WHERE surface = ?
|
|
76
|
+
ORDER BY checked_at DESC
|
|
77
|
+
LIMIT ?
|
|
78
|
+
`)
|
|
79
|
+
.bind(surface, limit)
|
|
80
|
+
.all();
|
|
81
|
+
return results ?? [];
|
|
82
|
+
}
|
|
83
|
+
async function countFingerprintSince(db, fingerprint, since) {
|
|
84
|
+
const row = await db
|
|
85
|
+
.prepare(`
|
|
86
|
+
SELECT COUNT(*) AS count
|
|
87
|
+
FROM gl_errors
|
|
88
|
+
WHERE fingerprint = ? AND occurred_at >= ?
|
|
89
|
+
`)
|
|
90
|
+
.bind(fingerprint, since)
|
|
91
|
+
.first();
|
|
92
|
+
return Number(row?.count ?? 0);
|
|
93
|
+
}
|
|
94
|
+
function closeIncidentAction(open, result) {
|
|
95
|
+
return {
|
|
96
|
+
kind: 'close-incident',
|
|
97
|
+
surface: open.surface,
|
|
98
|
+
severity: 'info',
|
|
99
|
+
title: `${open.surface} recovered`,
|
|
100
|
+
body: `Synthetic check ${result.id ?? 'unknown'} passed and closed incident ${open.id}.`,
|
|
101
|
+
dedupKey: `incident-closed:${open.id}`,
|
|
102
|
+
payload: { incidentId: open.id, resolveCheckId: result.id },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=thresholds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.js","sourceRoot":"","sources":["../../src/alerting/thresholds.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AlertAction, AlertEngine, AlertKind } from './alerting/index.js';
|
|
2
|
+
export { createAlerter } from './alerting/index.js';
|
|
3
|
+
export type { ProberConfig } from './prober/index.js';
|
|
4
|
+
export { createProber } from './prober/index.js';
|
|
5
|
+
export type { StatusPageAppConfig } from './status-page/app.js';
|
|
6
|
+
export { createStatusPageApp } from './status-page/app.js';
|
|
7
|
+
export type { TailWorkerConfig } from './tail/index.js';
|
|
8
|
+
export { createTailWorker } from './tail/index.js';
|
|
9
|
+
export type { AlertSeverity, CheckResult, CheckStatus, CheckType, ErrorRollup, ErrorSeverity, NotifyConfig, PersistedCheckResult, SamplingConfig, StatusPageConfig, SurfaceStatus, ThresholdConfig, UptimeIncident, } from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,YAAY,EACX,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,EACT,WAAW,EACX,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,GACd,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ExecutionContextLike, NotifyConfig, ProbeAlertingConfig, ScheduledEventLike } from '../types.js';
|
|
2
|
+
import type { SurfaceConfig } from './surfaces.js';
|
|
3
|
+
export interface ProberConfig {
|
|
4
|
+
realmId: string;
|
|
5
|
+
d1Binding: string;
|
|
6
|
+
notifyConfig: NotifyConfig;
|
|
7
|
+
alertingConfig?: ProbeAlertingConfig;
|
|
8
|
+
surfaces: SurfaceConfig[];
|
|
9
|
+
}
|
|
10
|
+
export declare function createProber(config: ProberConfig): {
|
|
11
|
+
scheduledHandler: (event: ScheduledEventLike, env: Record<string, unknown>, ctx: ExecutionContextLike) => Promise<void>;
|
|
12
|
+
checkSurface: (surface: SurfaceConfig, env: Record<string, unknown>) => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export { persistCheckResult } from './persist.js';
|
|
15
|
+
export { runGet } from './runners/get-runner.js';
|
|
16
|
+
export { extractCodeFromMessage, runHappyPath } from './runners/happy-path-runner.js';
|
|
17
|
+
export { runPost } from './runners/post-runner.js';
|
|
18
|
+
export type { CodeSigninHappyPathConfig, GetSurface, GmailPollingConfig, HappyPathSurface, PostSurface, SurfaceConfig, } from './surfaces.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prober/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,MAAM,aAAa,CAAA;AAMpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,CAAC,EAAE,mBAAmB,CAAA;IACpC,QAAQ,EAAE,aAAa,EAAE,CAAA;CACzB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY;8BAwBxC,kBAAkB,OACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OACvB,oBAAoB,KACvB,OAAO,CAAC,IAAI,CAAC;4BArBqB,aAAa,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;EA8BhG;AAaD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAClD,YAAY,EACX,yBAAyB,EACzB,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,aAAa,GACb,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createAlerter } from '../alerting/index.js';
|
|
2
|
+
import { errorMessage } from '../types.js';
|
|
3
|
+
import { persistCheckResult } from './persist.js';
|
|
4
|
+
import { runGet } from './runners/get-runner.js';
|
|
5
|
+
import { runHappyPath } from './runners/happy-path-runner.js';
|
|
6
|
+
import { runPost } from './runners/post-runner.js';
|
|
7
|
+
export function createProber(config) {
|
|
8
|
+
const alerter = createAlerter({
|
|
9
|
+
notifyConfig: config.notifyConfig,
|
|
10
|
+
alertingConfig: config.alertingConfig,
|
|
11
|
+
});
|
|
12
|
+
async function checkSurface(surface, env) {
|
|
13
|
+
const db = env[config.d1Binding];
|
|
14
|
+
try {
|
|
15
|
+
const result = await runSurface(surface, env);
|
|
16
|
+
if (!db) {
|
|
17
|
+
console.error(`[monitoring] missing D1 binding env.${config.d1Binding}`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const persisted = await persistCheckResult(db, surface, result);
|
|
21
|
+
await alerter.handleProbeResult(persisted, surface.name, db, env);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`[monitoring] synthetic check failed for ${surface.name}: ${errorMessage(error)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function scheduledHandler(event, env, ctx) {
|
|
28
|
+
for (const surface of config.surfaces.filter((candidate) => candidate.schedule === event.cron)) {
|
|
29
|
+
ctx.waitUntil(checkSurface(surface, env));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { scheduledHandler, checkSurface };
|
|
33
|
+
}
|
|
34
|
+
async function runSurface(surface, env) {
|
|
35
|
+
switch (surface.kind) {
|
|
36
|
+
case 'get':
|
|
37
|
+
return runGet(surface);
|
|
38
|
+
case 'post':
|
|
39
|
+
return runPost(surface);
|
|
40
|
+
case 'happy_path':
|
|
41
|
+
return runHappyPath(surface, { env });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export { persistCheckResult } from './persist.js';
|
|
45
|
+
export { runGet } from './runners/get-runner.js';
|
|
46
|
+
export { extractCodeFromMessage, runHappyPath } from './runners/happy-path-runner.js';
|
|
47
|
+
export { runPost } from './runners/post-runner.js';
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/prober/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAOpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAChD,OAAO,EAAyB,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AAWlD,MAAM,UAAU,YAAY,CAAC,MAAoB;IAChD,MAAM,OAAO,GAAG,aAAa,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc,EAAE,MAAM,CAAC,cAAc;KACrC,CAAC,CAAA;IAEF,KAAK,UAAU,YAAY,CAAC,OAAsB,EAAE,GAA4B;QAC/E,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAA2B,CAAA;QAC1D,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YAC7C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,OAAO,CAAC,KAAK,CAAC,uCAAuC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAA;gBACxE,OAAM;YACP,CAAC;YACD,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;YAC/D,MAAM,OAAO,CAAC,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CACZ,2CAA2C,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CACjF,CAAA;QACF,CAAC;IACF,CAAC;IAED,KAAK,UAAU,gBAAgB,CAC9B,KAAyB,EACzB,GAA4B,EAC5B,GAAyB;QAEzB,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAC3C,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,KAAK,KAAK,CAAC,IAAI,CAChD,EAAE,CAAC;YACH,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;QAC1C,CAAC;IACF,CAAC;IAED,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAA;AAC1C,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,OAAsB,EAAE,GAA4B;IAC7E,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,KAAK;YACT,OAAO,MAAM,CAAC,OAAO,CAAC,CAAA;QACvB,KAAK,MAAM;YACV,OAAO,OAAO,CAAC,OAAO,CAAC,CAAA;QACxB,KAAK,YAAY;YAChB,OAAO,YAAY,CAAC,OAAO,EAAE,EAAE,GAAG,EAA6B,CAAC,CAAA;IAClE,CAAC;AACF,CAAC;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CheckResult, type IdFactory, type PersistedCheckResult } from '../types.js';
|
|
2
|
+
import type { SurfaceConfig } from './surfaces.js';
|
|
3
|
+
export declare function persistCheckResult(db: D1Database, surface: SurfaceConfig, result: CheckResult, idFactory?: IdFactory, now?: () => number): Promise<PersistedCheckResult>;
|
|
4
|
+
//# sourceMappingURL=persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/prober/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD,wBAAsB,kBAAkB,CACvC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,WAAW,EACnB,SAAS,GAAE,SAAsB,EACjC,GAAG,eAAW,GACZ,OAAO,CAAC,oBAAoB,CAAC,CA8B/B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { errorMessage, generateId, } from '../types.js';
|
|
2
|
+
export async function persistCheckResult(db, surface, result, idFactory = generateId, now = Date.now) {
|
|
3
|
+
const id = result.id ?? idFactory();
|
|
4
|
+
const checkedAt = result.checkedAt ?? Math.floor(now() / 1000);
|
|
5
|
+
const persisted = { ...result, id, checkedAt };
|
|
6
|
+
try {
|
|
7
|
+
await db
|
|
8
|
+
.prepare(`
|
|
9
|
+
INSERT INTO gl_uptime_checks
|
|
10
|
+
(id, surface, check_type, status, status_code, latency_ms, error_message, checked_at)
|
|
11
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
12
|
+
`)
|
|
13
|
+
.bind(id, surface.name, surface.kind, result.status, result.statusCode ?? null, result.latencyMs, result.errorMessage ?? null, checkedAt)
|
|
14
|
+
.run();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error(`[monitoring] failed to persist uptime check ${surface.name}: ${errorMessage(error)}`);
|
|
18
|
+
}
|
|
19
|
+
return persisted;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=persist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/prober/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,YAAY,EACZ,UAAU,GAGV,MAAM,aAAa,CAAA;AAGpB,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,EAAc,EACd,OAAsB,EACtB,MAAmB,EACnB,YAAuB,UAAU,EACjC,GAAG,GAAG,IAAI,CAAC,GAAG;IAEd,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,SAAS,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAC9D,MAAM,SAAS,GAAyB,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAA;IAEpE,IAAI,CAAC;QACJ,MAAM,EAAE;aACN,OAAO,CAAC;;;;IAIR,CAAC;aACD,IAAI,CACJ,EAAE,EACF,OAAO,CAAC,IAAI,EACZ,OAAO,CAAC,IAAI,EACZ,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,UAAU,IAAI,IAAI,EACzB,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,YAAY,IAAI,IAAI,EAC3B,SAAS,CACT;aACA,GAAG,EAAE,CAAA;IACR,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CACZ,+CAA+C,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CACrF,CAAA;IACF,CAAC;IAED,OAAO,SAAS,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-runner.d.ts","sourceRoot":"","sources":["../../../src/prober/runners/get-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAkC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACpG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhD,wBAAsB,MAAM,CAC3B,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,WAAW,CAAC,CA0CtB"}
|