@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +115 -0
  3. package/SPEC.md +19 -0
  4. package/dist/alerting/dedup.d.ts +6 -0
  5. package/dist/alerting/dedup.d.ts.map +1 -0
  6. package/dist/alerting/dedup.js +49 -0
  7. package/dist/alerting/dedup.js.map +1 -0
  8. package/dist/alerting/escalation.d.ts +4 -0
  9. package/dist/alerting/escalation.d.ts.map +1 -0
  10. package/dist/alerting/escalation.js +26 -0
  11. package/dist/alerting/escalation.js.map +1 -0
  12. package/dist/alerting/index.d.ts +31 -0
  13. package/dist/alerting/index.d.ts.map +1 -0
  14. package/dist/alerting/index.js +50 -0
  15. package/dist/alerting/index.js.map +1 -0
  16. package/dist/alerting/thresholds.d.ts +8 -0
  17. package/dist/alerting/thresholds.d.ts.map +1 -0
  18. package/dist/alerting/thresholds.js +105 -0
  19. package/dist/alerting/thresholds.js.map +1 -0
  20. package/dist/index.d.ts +10 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +5 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/prober/index.d.ts +19 -0
  25. package/dist/prober/index.d.ts.map +1 -0
  26. package/dist/prober/index.js +48 -0
  27. package/dist/prober/index.js.map +1 -0
  28. package/dist/prober/persist.d.ts +4 -0
  29. package/dist/prober/persist.d.ts.map +1 -0
  30. package/dist/prober/persist.js +21 -0
  31. package/dist/prober/persist.js.map +1 -0
  32. package/dist/prober/runners/get-runner.d.ts +4 -0
  33. package/dist/prober/runners/get-runner.d.ts.map +1 -0
  34. package/dist/prober/runners/get-runner.js +43 -0
  35. package/dist/prober/runners/get-runner.js.map +1 -0
  36. package/dist/prober/runners/happy-path-runner.d.ts +13 -0
  37. package/dist/prober/runners/happy-path-runner.d.ts.map +1 -0
  38. package/dist/prober/runners/happy-path-runner.js +183 -0
  39. package/dist/prober/runners/happy-path-runner.js.map +1 -0
  40. package/dist/prober/runners/post-runner.d.ts +4 -0
  41. package/dist/prober/runners/post-runner.d.ts.map +1 -0
  42. package/dist/prober/runners/post-runner.js +44 -0
  43. package/dist/prober/runners/post-runner.js.map +1 -0
  44. package/dist/prober/surfaces.d.ts +46 -0
  45. package/dist/prober/surfaces.d.ts.map +1 -0
  46. package/dist/prober/surfaces.js +2 -0
  47. package/dist/prober/surfaces.js.map +1 -0
  48. package/dist/schemas/drizzle/schema.d.ts +519 -0
  49. package/dist/schemas/drizzle/schema.d.ts.map +1 -0
  50. package/dist/schemas/drizzle/schema.js +45 -0
  51. package/dist/schemas/drizzle/schema.js.map +1 -0
  52. package/dist/schemas/index.d.ts +2 -0
  53. package/dist/schemas/index.d.ts.map +1 -0
  54. package/dist/schemas/index.js +2 -0
  55. package/dist/schemas/index.js.map +1 -0
  56. package/dist/schemas/migrations/0001_uptime_checks.sql +12 -0
  57. package/dist/schemas/migrations/0002_uptime_incidents.sql +12 -0
  58. package/dist/schemas/migrations/0003_errors.sql +16 -0
  59. package/dist/schemas/migrations/README.md +15 -0
  60. package/dist/status-page/app.d.ts +10 -0
  61. package/dist/status-page/app.d.ts.map +1 -0
  62. package/dist/status-page/app.js +44 -0
  63. package/dist/status-page/app.js.map +1 -0
  64. package/dist/status-page/lib/queries.d.ts +6 -0
  65. package/dist/status-page/lib/queries.d.ts.map +1 -0
  66. package/dist/status-page/lib/queries.js +81 -0
  67. package/dist/status-page/lib/queries.js.map +1 -0
  68. package/dist/status-page/pages/api/status.json.d.ts +3 -0
  69. package/dist/status-page/pages/api/status.json.d.ts.map +1 -0
  70. package/dist/status-page/pages/api/status.json.js +19 -0
  71. package/dist/status-page/pages/api/status.json.js.map +1 -0
  72. package/dist/status-page/shell.d.ts +3 -0
  73. package/dist/status-page/shell.d.ts.map +1 -0
  74. package/dist/status-page/shell.js +18 -0
  75. package/dist/status-page/shell.js.map +1 -0
  76. package/dist/tail/categorize.d.ts +44 -0
  77. package/dist/tail/categorize.d.ts.map +1 -0
  78. package/dist/tail/categorize.js +113 -0
  79. package/dist/tail/categorize.js.map +1 -0
  80. package/dist/tail/fingerprint.d.ts +4 -0
  81. package/dist/tail/fingerprint.d.ts.map +1 -0
  82. package/dist/tail/fingerprint.js +18 -0
  83. package/dist/tail/fingerprint.js.map +1 -0
  84. package/dist/tail/index.d.ts +21 -0
  85. package/dist/tail/index.d.ts.map +1 -0
  86. package/dist/tail/index.js +50 -0
  87. package/dist/tail/index.js.map +1 -0
  88. package/dist/tail/persist.d.ts +15 -0
  89. package/dist/tail/persist.d.ts.map +1 -0
  90. package/dist/tail/persist.js +63 -0
  91. package/dist/tail/persist.js.map +1 -0
  92. package/dist/tail/redact.d.ts +5 -0
  93. package/dist/tail/redact.d.ts.map +1 -0
  94. package/dist/tail/redact.js +25 -0
  95. package/dist/tail/redact.js.map +1 -0
  96. package/dist/tail/sample.d.ts +9 -0
  97. package/dist/tail/sample.d.ts.map +1 -0
  98. package/dist/tail/sample.js +25 -0
  99. package/dist/tail/sample.js.map +1 -0
  100. package/dist/types.d.ts +87 -0
  101. package/dist/types.d.ts.map +1 -0
  102. package/dist/types.js +11 -0
  103. package/dist/types.js.map +1 -0
  104. package/package.json +85 -0
  105. package/src/schemas/migrations/0001_uptime_checks.sql +12 -0
  106. package/src/schemas/migrations/0002_uptime_incidents.sql +12 -0
  107. package/src/schemas/migrations/0003_errors.sql +16 -0
  108. package/src/schemas/migrations/README.md +15 -0
  109. package/src/status-page/README.md +14 -0
  110. package/src/status-page/app.ts +58 -0
  111. package/src/status-page/components/ErrorRollup.astro +26 -0
  112. package/src/status-page/components/IncidentList.astro +25 -0
  113. package/src/status-page/components/SurfaceRow.astro +24 -0
  114. package/src/status-page/lib/queries.ts +114 -0
  115. package/src/status-page/pages/api/status.json.ts +24 -0
  116. package/src/status-page/pages/index.astro +45 -0
  117. package/src/status-page/shell.ts +17 -0
@@ -0,0 +1,44 @@
1
+ export function createStatusPageApp(config) {
2
+ const routePath = config.routePath ?? '/';
3
+ const jsonRoutePath = config.jsonRoutePath ?? '/api/status.json';
4
+ const serializable = {
5
+ realm: config.realm,
6
+ d1Binding: config.d1Binding ?? 'MONITORING_DB',
7
+ surfaces: config.surfaces,
8
+ };
9
+ return {
10
+ name: '@growth-labs/monitoring/status-page',
11
+ hooks: {
12
+ 'astro:config:setup': ({ injectRoute, updateConfig }) => {
13
+ updateConfig({
14
+ vite: {
15
+ plugins: [
16
+ {
17
+ name: '@growth-labs/monitoring:status-page-config',
18
+ resolveId(id) {
19
+ if (id === 'virtual:growth-labs/monitoring/status-page/config')
20
+ return id;
21
+ },
22
+ load(id) {
23
+ if (id === 'virtual:growth-labs/monitoring/status-page/config') {
24
+ return `export const config = ${JSON.stringify(serializable)}`;
25
+ }
26
+ },
27
+ },
28
+ ],
29
+ },
30
+ });
31
+ injectRoute({
32
+ pattern: routePath,
33
+ entrypoint: '@growth-labs/monitoring/status-page/pages/index',
34
+ });
35
+ injectRoute({
36
+ pattern: jsonRoutePath,
37
+ entrypoint: '@growth-labs/monitoring/status-page/pages/api/status.json',
38
+ });
39
+ },
40
+ },
41
+ };
42
+ }
43
+ export { getCurrentSurfaceStatuses, getOpenIncidents, getSurfaceUptime, getTopErrors, } from './lib/queries.js';
44
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/status-page/app.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,mBAAmB,CAAC,MAA2B;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,GAAG,CAAA;IACzC,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,kBAAkB,CAAA;IAChE,MAAM,YAAY,GAAG;QACpB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,eAAe;QAC9C,QAAQ,EAAE,MAAM,CAAC,QAAQ;KACzB,CAAA;IAED,OAAO;QACN,IAAI,EAAE,qCAAqC;QAC3C,KAAK,EAAE;YACN,oBAAoB,EAAE,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,EAAE;gBACvD,YAAY,CAAC;oBACZ,IAAI,EAAE;wBACL,OAAO,EAAE;4BACR;gCACC,IAAI,EAAE,4CAA4C;gCAClD,SAAS,CAAC,EAAU;oCACnB,IAAI,EAAE,KAAK,mDAAmD;wCAAE,OAAO,EAAE,CAAA;gCAC1E,CAAC;gCACD,IAAI,CAAC,EAAU;oCACd,IAAI,EAAE,KAAK,mDAAmD,EAAE,CAAC;wCAChE,OAAO,yBAAyB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAA;oCAC/D,CAAC;gCACF,CAAC;6BACD;yBACD;qBACD;iBACD,CAAC,CAAA;gBACF,WAAW,CAAC;oBACX,OAAO,EAAE,SAAS;oBAClB,UAAU,EAAE,iDAAiD;iBAC7D,CAAC,CAAA;gBACF,WAAW,CAAC;oBACX,OAAO,EAAE,aAAa;oBACtB,UAAU,EAAE,2DAA2D;iBACvE,CAAC,CAAA;YACH,CAAC;SACD;KACD,CAAA;AACF,CAAC;AAGD,OAAO,EACN,yBAAyB,EACzB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,GACZ,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { ErrorRollup, StatusPageSurfaceConfig, SurfaceStatus, UptimeIncident } from '../../types.js';
2
+ export declare function getCurrentSurfaceStatuses(db: D1Database, surfaces: StatusPageSurfaceConfig[]): Promise<SurfaceStatus[]>;
3
+ export declare function getOpenIncidents(db: D1Database): Promise<UptimeIncident[]>;
4
+ export declare function getTopErrors(db: D1Database, hoursBack: number): Promise<ErrorRollup[]>;
5
+ export declare function getSurfaceUptime(db: D1Database, surface: string, daysBack: number): Promise<number>;
6
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../../src/status-page/lib/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EACX,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,MAAM,gBAAgB,CAAA;AAiBvB,wBAAsB,yBAAyB,CAC9C,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,uBAAuB,EAAE,GACjC,OAAO,CAAC,aAAa,EAAE,CAAC,CAwB1B;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhF;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAoB5F;AAED,wBAAsB,gBAAgB,CACrC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAcjB"}
@@ -0,0 +1,81 @@
1
+ export async function getCurrentSurfaceStatuses(db, surfaces) {
2
+ const openIncidents = await getOpenIncidents(db);
3
+ return Promise.all(surfaces.map(async (surface) => {
4
+ const lastCheck = await db
5
+ .prepare(`
6
+ SELECT id, surface, status, checked_at
7
+ FROM gl_uptime_checks
8
+ WHERE surface = ?
9
+ ORDER BY checked_at DESC
10
+ LIMIT 1
11
+ `)
12
+ .bind(surface.name)
13
+ .first();
14
+ const incident = openIncidents.find((row) => row.surface === surface.name);
15
+ const uptime7d = await getSurfaceUptime(db, surface.name, 7);
16
+ return {
17
+ name: surface.name,
18
+ status: statusFor(lastCheck ?? null, incident),
19
+ lastCheckedAt: lastCheck?.checked_at ?? null,
20
+ uptime7d,
21
+ };
22
+ }));
23
+ }
24
+ export async function getOpenIncidents(db) {
25
+ const { results } = await db
26
+ .prepare(`
27
+ SELECT id, surface, opened_at, closed_at, trigger_check_id, resolve_check_id, severity, notes
28
+ FROM gl_uptime_incidents
29
+ WHERE closed_at IS NULL
30
+ ORDER BY opened_at DESC
31
+ `)
32
+ .all();
33
+ return results ?? [];
34
+ }
35
+ export async function getTopErrors(db, hoursBack) {
36
+ const since = Math.floor(Date.now() / 1000) - hoursBack * 60 * 60;
37
+ const { results } = await db
38
+ .prepare(`
39
+ SELECT fingerprint, surface, message, COUNT(*) AS count, MAX(occurred_at) AS occurred_at
40
+ FROM gl_errors
41
+ WHERE occurred_at >= ?
42
+ GROUP BY fingerprint
43
+ ORDER BY count DESC, occurred_at DESC
44
+ LIMIT 5
45
+ `)
46
+ .bind(since)
47
+ .all();
48
+ return (results ?? []).map((row) => ({
49
+ fingerprint: row.fingerprint,
50
+ surface: row.surface,
51
+ message: row.message,
52
+ count: Number(row.count),
53
+ lastOccurredAt: Number(row.occurred_at),
54
+ }));
55
+ }
56
+ export async function getSurfaceUptime(db, surface, daysBack) {
57
+ const since = Math.floor(Date.now() / 1000) - daysBack * 24 * 60 * 60;
58
+ const { results } = await db
59
+ .prepare(`
60
+ SELECT id, surface, status, checked_at
61
+ FROM gl_uptime_checks
62
+ WHERE surface = ? AND checked_at >= ?
63
+ ORDER BY checked_at DESC
64
+ `)
65
+ .bind(surface, since)
66
+ .all();
67
+ const rows = results ?? [];
68
+ if (rows.length === 0)
69
+ return 1;
70
+ return rows.filter((row) => row.status === 'pass').length / rows.length;
71
+ }
72
+ function statusFor(check, incident) {
73
+ if (incident?.severity === 'critical')
74
+ return 'red';
75
+ if (incident?.severity === 'warning')
76
+ return 'yellow';
77
+ if (!check)
78
+ return 'yellow';
79
+ return check.status === 'pass' ? 'green' : 'yellow';
80
+ }
81
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../src/status-page/lib/queries.ts"],"names":[],"mappings":"AAsBA,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,EAAc,EACd,QAAmC;IAEnC,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,EAAE,CAAC,CAAA;IAChD,OAAO,OAAO,CAAC,GAAG,CACjB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,MAAM,SAAS,GAAG,MAAM,EAAE;aACxB,OAAO,CAAC;;;;;;KAMR,CAAC;aACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;aAClB,KAAK,EAAY,CAAA;QACnB,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC1E,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAC5D,OAAO;YACN,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,SAAS,CAAC,SAAS,IAAI,IAAI,EAAE,QAAQ,CAAC;YAC9C,aAAa,EAAE,SAAS,EAAE,UAAU,IAAI,IAAI;YAC5C,QAAQ;SACR,CAAA;IACF,CAAC,CAAC,CACF,CAAA;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAc;IACpD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;SAC1B,OAAO,CAAC;;;;;GAKR,CAAC;SACD,GAAG,EAAkB,CAAA;IACvB,OAAO,OAAO,IAAI,EAAE,CAAA;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAc,EAAE,SAAiB;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,CAAA;IACjE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;SAC1B,OAAO,CAAC;;;;;;;GAOR,CAAC;SACD,IAAI,CAAC,KAAK,CAAC;SACX,GAAG,EAAkB,CAAA;IACvB,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACpC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;KACvC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,EAAc,EACd,OAAe,EACf,QAAgB;IAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;IACrE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;SAC1B,OAAO,CAAC;;;;;GAKR,CAAC;SACD,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC;SACpB,GAAG,EAAY,CAAA;IACjB,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,CAAA;IAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;AACxE,CAAC;AAED,SAAS,SAAS,CACjB,KAAsB,EACtB,QAAoC;IAEpC,IAAI,QAAQ,EAAE,QAAQ,KAAK,UAAU;QAAE,OAAO,KAAK,CAAA;IACnD,IAAI,QAAQ,EAAE,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAA;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAA;IAC3B,OAAO,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAA;AACpD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { APIRoute } from 'astro';
2
+ export declare const GET: APIRoute;
3
+ //# sourceMappingURL=status.json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.json.d.ts","sourceRoot":"","sources":["../../../../src/status-page/pages/api/status.json.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrC,eAAO,MAAM,GAAG,EAAE,QAmBjB,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { config } from 'virtual:growth-labs/monitoring/status-page/config';
2
+ import { getCurrentSurfaceStatuses, getOpenIncidents, getTopErrors } from '../../lib/queries.js';
3
+ export const GET = async ({ locals }) => {
4
+ const env = locals.runtime?.env ?? {};
5
+ const db = env[config.d1Binding];
6
+ const [surfaces, openIncidents, topErrors] = await Promise.all([
7
+ getCurrentSurfaceStatuses(db, config.surfaces),
8
+ getOpenIncidents(db),
9
+ getTopErrors(db, 24),
10
+ ]);
11
+ return new Response(JSON.stringify({
12
+ realm: config.realm,
13
+ generatedAt: Math.floor(Date.now() / 1000),
14
+ surfaces,
15
+ openIncidents,
16
+ topErrors,
17
+ }), { headers: { 'Content-Type': 'application/json' } });
18
+ };
19
+ //# sourceMappingURL=status.json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.json.js","sourceRoot":"","sources":["../../../../src/status-page/pages/api/status.json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mDAAmD,CAAA;AAE1E,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEhG,MAAM,CAAC,MAAM,GAAG,GAAa,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IACjD,MAAM,GAAG,GAAI,MAA0D,CAAC,OAAO,EAAE,GAAG,IAAI,EAAE,CAAA;IAC1F,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAe,CAAA;IAC9C,MAAM,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC9D,yBAAyB,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC9C,gBAAgB,CAAC,EAAE,CAAC;QACpB,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC;KACpB,CAAC,CAAA;IAEF,OAAO,IAAI,QAAQ,CAClB,IAAI,CAAC,SAAS,CAAC;QACd,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAC1C,QAAQ;QACR,aAAa;QACb,SAAS;KACT,CAAC,EACF,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACnD,CAAA;AACF,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare function relativeTime(timestampSeconds: number | null, nowSeconds?: number): string;
2
+ export declare function formatUptime(value: number): string;
3
+ //# sourceMappingURL=shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/status-page/shell.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,UAAU,SAAgC,GACxC,MAAM,CASR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,18 @@
1
+ export function relativeTime(timestampSeconds, nowSeconds = Math.floor(Date.now() / 1000)) {
2
+ if (!timestampSeconds)
3
+ return 'never';
4
+ const diff = Math.max(0, nowSeconds - timestampSeconds);
5
+ if (diff < 60)
6
+ return `${diff}s ago`;
7
+ const minutes = Math.floor(diff / 60);
8
+ if (minutes < 60)
9
+ return `${minutes}m ago`;
10
+ const hours = Math.floor(minutes / 60);
11
+ if (hours < 24)
12
+ return `${hours}h ago`;
13
+ return `${Math.floor(hours / 24)}d ago`;
14
+ }
15
+ export function formatUptime(value) {
16
+ return `${(value * 100).toFixed(2)}%`;
17
+ }
18
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/status-page/shell.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAC3B,gBAA+B,EAC/B,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAE1C,IAAI,CAAC,gBAAgB;QAAE,OAAO,OAAO,CAAA;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAAC,CAAA;IACvD,IAAI,IAAI,GAAG,EAAE;QAAE,OAAO,GAAG,IAAI,OAAO,CAAA;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;IACrC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,OAAO,CAAA;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACtC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,OAAO,CAAA;IACtC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,OAAO,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,OAAO,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAA;AACtC,CAAC"}
@@ -0,0 +1,44 @@
1
+ export type ErrorCategory = 'exception' | 'fivexx' | 'console-error' | 'console-warn' | 'slow-request';
2
+ export interface CategorizedEvent {
3
+ category: ErrorCategory;
4
+ surface: string;
5
+ message: string;
6
+ stack?: string;
7
+ statusCode?: number;
8
+ durationMs?: number;
9
+ requestId?: string;
10
+ occurredAt: number;
11
+ fingerprint?: string;
12
+ }
13
+ export interface TraceItem {
14
+ scriptName?: string;
15
+ outcome?: string;
16
+ exceptions?: Array<{
17
+ name?: string;
18
+ message?: string;
19
+ stack?: string;
20
+ }>;
21
+ logs?: Array<{
22
+ level?: string;
23
+ message?: unknown;
24
+ args?: unknown[];
25
+ }>;
26
+ request?: {
27
+ method?: string;
28
+ url?: string;
29
+ headers?: Record<string, string>;
30
+ };
31
+ response?: {
32
+ status?: number;
33
+ };
34
+ eventTimestamp?: number | string;
35
+ wallTime?: number;
36
+ dispatchNamespace?: string;
37
+ }
38
+ interface CategorizeOptions {
39
+ slowRequestThresholdMs?: number;
40
+ }
41
+ export declare function categorize(traceEvent: TraceItem, options?: CategorizeOptions): CategorizedEvent[];
42
+ export declare function normalizePath(pathname: string): string;
43
+ export {};
44
+ //# sourceMappingURL=categorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categorize.d.ts","sourceRoot":"","sources":["../../src/tail/categorize.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACtB,WAAW,GACX,QAAQ,GACR,eAAe,GACf,cAAc,GACd,cAAc,CAAA;AAEjB,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,aAAa,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvE,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAA;IACrE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC7E,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,UAAU,iBAAiB;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,wBAAgB,UAAU,CACzB,UAAU,EAAE,SAAS,EACrB,OAAO,GAAE,iBAAsB,GAC7B,gBAAgB,EAAE,CAkEpB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAStD"}
@@ -0,0 +1,113 @@
1
+ export function categorize(traceEvent, options = {}) {
2
+ const occurredAt = parseTimestamp(traceEvent.eventTimestamp);
3
+ const surface = surfaceForTrace(traceEvent);
4
+ const requestId = traceEvent.request?.headers?.['cf-ray'] ?? traceEvent.request?.headers?.['CF-Ray'];
5
+ const durationMs = typeof traceEvent.wallTime === 'number' ? traceEvent.wallTime : undefined;
6
+ const events = [];
7
+ for (const exception of traceEvent.exceptions ?? []) {
8
+ const prefix = exception.name ? `${exception.name}: ` : '';
9
+ events.push({
10
+ category: 'exception',
11
+ surface,
12
+ message: `${prefix}${exception.message ?? 'Unhandled exception'}`,
13
+ stack: exception.stack,
14
+ requestId,
15
+ durationMs,
16
+ occurredAt,
17
+ });
18
+ }
19
+ const statusCode = traceEvent.response?.status;
20
+ if (typeof statusCode === 'number' && statusCode >= 500) {
21
+ events.push({
22
+ category: 'fivexx',
23
+ surface,
24
+ message: `HTTP ${statusCode} response`,
25
+ statusCode,
26
+ requestId,
27
+ durationMs,
28
+ occurredAt,
29
+ });
30
+ }
31
+ for (const log of traceEvent.logs ?? []) {
32
+ if (log.level !== 'error' && log.level !== 'warn')
33
+ continue;
34
+ events.push({
35
+ category: log.level === 'error' ? 'console-error' : 'console-warn',
36
+ surface,
37
+ message: logMessage(log),
38
+ statusCode,
39
+ requestId,
40
+ durationMs,
41
+ occurredAt,
42
+ });
43
+ }
44
+ const slowThreshold = options.slowRequestThresholdMs;
45
+ if (events.length === 0 &&
46
+ typeof durationMs === 'number' &&
47
+ typeof slowThreshold === 'number' &&
48
+ durationMs > slowThreshold) {
49
+ events.push({
50
+ category: 'slow-request',
51
+ surface,
52
+ message: `Slow request took ${durationMs}ms`,
53
+ statusCode,
54
+ requestId,
55
+ durationMs,
56
+ occurredAt,
57
+ });
58
+ }
59
+ return events;
60
+ }
61
+ export function normalizePath(pathname) {
62
+ return ('/' +
63
+ pathname
64
+ .split('/')
65
+ .filter(Boolean)
66
+ .map((segment) => (/^[A-Za-z]+$/.test(segment) ? segment : '[slug]'))
67
+ .join('/'));
68
+ }
69
+ function surfaceForTrace(traceEvent) {
70
+ const scriptName = traceEvent.scriptName ?? traceEvent.dispatchNamespace ?? 'unknown';
71
+ const method = traceEvent.request?.method ?? 'GET';
72
+ const url = traceEvent.request?.url ?? 'https://unknown/';
73
+ let pathname = '/';
74
+ try {
75
+ pathname = new URL(url).pathname;
76
+ }
77
+ catch {
78
+ pathname = url.split('?')[0] || '/';
79
+ }
80
+ return `${scriptName}:${method} ${normalizePath(pathname)}`;
81
+ }
82
+ function parseTimestamp(value) {
83
+ if (typeof value === 'number')
84
+ return value < 10_000_000_000 ? value * 1000 : value;
85
+ if (typeof value === 'string') {
86
+ const numeric = Number(value);
87
+ if (Number.isFinite(numeric))
88
+ return parseTimestamp(numeric);
89
+ const parsed = Date.parse(value);
90
+ if (Number.isFinite(parsed))
91
+ return parsed;
92
+ }
93
+ return Date.now();
94
+ }
95
+ function logMessage(log) {
96
+ const value = log.message ?? log.args ?? '';
97
+ if (Array.isArray(value))
98
+ return value.map(formatLogPart).join(' ');
99
+ return formatLogPart(value);
100
+ }
101
+ function formatLogPart(value) {
102
+ if (typeof value === 'string')
103
+ return value;
104
+ if (value instanceof Error)
105
+ return value.message;
106
+ try {
107
+ return JSON.stringify(value);
108
+ }
109
+ catch {
110
+ return String(value);
111
+ }
112
+ }
113
+ //# sourceMappingURL=categorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categorize.js","sourceRoot":"","sources":["../../src/tail/categorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { CategorizedEvent } from './categorize.js';
2
+ export declare function computeFingerprint(event: CategorizedEvent): Promise<string>;
3
+ export declare function normalizeMessage(message: string): string;
4
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/tail/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAIvD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CASjF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,18 @@
1
+ const encoder = new TextEncoder();
2
+ export async function computeFingerprint(event) {
3
+ const firstStackFrame = event.stack?.split('\n')[1]?.trim() ?? '';
4
+ const normalizedMessage = normalizeMessage(event.message);
5
+ const input = `${event.category}|${normalizedMessage}|${firstStackFrame}`;
6
+ const digest = await crypto.subtle.digest('SHA-256', encoder.encode(input));
7
+ return [...new Uint8Array(digest)]
8
+ .map((byte) => byte.toString(16).padStart(2, '0'))
9
+ .join('')
10
+ .slice(0, 16);
11
+ }
12
+ export function normalizeMessage(message) {
13
+ return message
14
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, '[uuid]')
15
+ .replace(/\/[a-z0-9_-]+(?=\b)/gi, '/[seg]')
16
+ .replace(/\b\d{4,}\b/g, '[n]');
17
+ }
18
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/tail/fingerprint.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;AAEjC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAuB;IAC/D,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACjE,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACzD,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,iBAAiB,IAAI,eAAe,EAAE,CAAA;IACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;SAChC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACjD,IAAI,CAAC,EAAE,CAAC;SACR,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC/C,OAAO,OAAO;SACZ,OAAO,CAAC,oEAAoE,EAAE,QAAQ,CAAC;SACvF,OAAO,CAAC,uBAAuB,EAAE,QAAQ,CAAC;SAC1C,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type NotifyConfig, type SamplingConfig, type TailAlertingConfig } from '../types.js';
2
+ import { type TraceItem } from './categorize.js';
3
+ export interface TailWorkerConfig {
4
+ realmId: string;
5
+ d1Binding: string;
6
+ waeBinding: string;
7
+ notifyConfig: NotifyConfig;
8
+ alertingConfig?: TailAlertingConfig;
9
+ sampling: SamplingConfig;
10
+ }
11
+ export declare function createTailWorker(config: TailWorkerConfig): {
12
+ tail: (events: TraceItem[], env: Record<string, unknown>) => Promise<void>;
13
+ tailHandler: (events: TraceItem[], env: Record<string, unknown>) => Promise<void>;
14
+ };
15
+ export type { CategorizedEvent, ErrorCategory, TraceItem } from './categorize.js';
16
+ export { categorize, normalizePath } from './categorize.js';
17
+ export { computeFingerprint, normalizeMessage } from './fingerprint.js';
18
+ export { persistErrorEvent, severityFromCategory } from './persist.js';
19
+ export { redact, redactString, redactSurface } from './redact.js';
20
+ export { shouldKeep } from './sample.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tail/index.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAM5D,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,CAAC,EAAE,kBAAkB,CAAA;IACnC,QAAQ,EAAE,cAAc,CAAA;CACxB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB;mBAMrB,SAAS,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;0BAAzD,SAAS,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;EAmC5F;AAED,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AACjF,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,50 @@
1
+ import { createAlerter } from '../alerting/index.js';
2
+ import { errorMessage, } from '../types.js';
3
+ import { categorize } from './categorize.js';
4
+ import { computeFingerprint } from './fingerprint.js';
5
+ import { persistErrorEvent } from './persist.js';
6
+ import { redact } from './redact.js';
7
+ import { shouldKeep } from './sample.js';
8
+ export function createTailWorker(config) {
9
+ const alerter = createAlerter({
10
+ notifyConfig: config.notifyConfig,
11
+ alertingConfig: config.alertingConfig,
12
+ });
13
+ async function tailHandler(events, env) {
14
+ const db = env[config.d1Binding];
15
+ const wae = env[config.waeBinding];
16
+ if (!db) {
17
+ console.error(`[monitoring] missing D1 binding env.${config.d1Binding}`);
18
+ return;
19
+ }
20
+ for (const traceEvent of events) {
21
+ for (const rawEvent of categorize(traceEvent, {
22
+ slowRequestThresholdMs: config.sampling.slowRequestThresholdMs,
23
+ })) {
24
+ try {
25
+ const fingerprint = await computeFingerprint(rawEvent);
26
+ const withFingerprint = { ...rawEvent, fingerprint };
27
+ if (!shouldKeep(withFingerprint.category, config.sampling, {
28
+ surface: withFingerprint.surface,
29
+ activeIncidentSurfaces: alerter.activeIncidentSurfaces,
30
+ })) {
31
+ continue;
32
+ }
33
+ const redacted = redact(withFingerprint);
34
+ await persistErrorEvent(db, wae, { realmId: config.realmId }, redacted);
35
+ await alerter.handleErrorEvent(withFingerprint, db, env);
36
+ }
37
+ catch (error) {
38
+ console.error(`[monitoring] failed to process tail event: ${errorMessage(error)}`);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return { tail: tailHandler, tailHandler };
44
+ }
45
+ export { categorize, normalizePath } from './categorize.js';
46
+ export { computeFingerprint, normalizeMessage } from './fingerprint.js';
47
+ export { persistErrorEvent, severityFromCategory } from './persist.js';
48
+ export { redact, redactString, redactSurface } from './redact.js';
49
+ export { shouldKeep } from './sample.js';
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tail/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EACN,YAAY,GAIZ,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAkB,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAWxC,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACxD,MAAM,OAAO,GAAG,aAAa,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc,EAAE,MAAM,CAAC,cAAc;KACrC,CAAC,CAAA;IAEF,KAAK,UAAU,WAAW,CAAC,MAAmB,EAAE,GAA4B;QAC3E,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAA2B,CAAA;QAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAErB,CAAA;QACZ,IAAI,CAAC,EAAE,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,uCAAuC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAA;YACxE,OAAM;QACP,CAAC;QACD,KAAK,MAAM,UAAU,IAAI,MAAM,EAAE,CAAC;YACjC,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,UAAU,EAAE;gBAC7C,sBAAsB,EAAE,MAAM,CAAC,QAAQ,CAAC,sBAAsB;aAC9D,CAAC,EAAE,CAAC;gBACJ,IAAI,CAAC;oBACJ,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAA;oBACtD,MAAM,eAAe,GAAG,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,CAAA;oBACpD,IACC,CAAC,UAAU,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;wBACtD,OAAO,EAAE,eAAe,CAAC,OAAO;wBAChC,sBAAsB,EAAE,OAAO,CAAC,sBAAsB;qBACtD,CAAC,EACD,CAAC;wBACF,SAAQ;oBACT,CAAC;oBACD,MAAM,QAAQ,GAAG,MAAM,CAAC,eAAe,CAA2B,CAAA;oBAClE,MAAM,iBAAiB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAA;oBACvE,MAAM,OAAO,CAAC,gBAAgB,CAAC,eAAe,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;gBACzD,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,OAAO,CAAC,KAAK,CAAC,8CAA8C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;gBACnF,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,CAAA;AAC1C,CAAC;AAGD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { type ErrorSeverity, type IdFactory } from '../types.js';
2
+ import type { CategorizedEvent, ErrorCategory } from './categorize.js';
3
+ interface PersistConfig {
4
+ realmId: string;
5
+ }
6
+ interface AnalyticsEngineBinding {
7
+ writeDataPoint(dataPoint: unknown): Promise<unknown> | unknown;
8
+ }
9
+ export interface FingerprintedEvent extends CategorizedEvent {
10
+ fingerprint: string;
11
+ }
12
+ export declare function persistErrorEvent(db: D1Database, wae: AnalyticsEngineBinding | undefined, config: PersistConfig, event: FingerprintedEvent, idFactory?: IdFactory): Promise<void>;
13
+ export declare function severityFromCategory(category: ErrorCategory): ErrorSeverity;
14
+ export {};
15
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/tail/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAA4B,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEtE,UAAU,aAAa;IACtB,OAAO,EAAE,MAAM,CAAA;CACf;AAED,UAAU,sBAAsB;IAC/B,cAAc,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CAC9D;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC3D,WAAW,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,iBAAiB,CACtC,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,sBAAsB,GAAG,SAAS,EACvC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,kBAAkB,EACzB,SAAS,GAAE,SAAsB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,CAW3E"}
@@ -0,0 +1,63 @@
1
+ import { errorMessage, generateId } from '../types.js';
2
+ export async function persistErrorEvent(db, wae, config, event, idFactory = generateId) {
3
+ const severity = severityFromCategory(event.category);
4
+ try {
5
+ await db
6
+ .prepare(`
7
+ INSERT INTO gl_errors
8
+ (id, realm_key, surface, severity, message, stack, request_id, status_code, duration_ms, occurred_at, fingerprint)
9
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10
+ `)
11
+ .bind(idFactory(), config.realmId, event.surface, severity, event.message, event.stack ?? null, event.requestId ?? null, event.statusCode ?? null, event.durationMs ?? null, Math.floor(event.occurredAt / 1000), event.fingerprint)
12
+ .run();
13
+ }
14
+ catch (error) {
15
+ console.error(`[monitoring] failed to persist error event: ${errorMessage(error)}`);
16
+ }
17
+ if (!wae)
18
+ return;
19
+ try {
20
+ await wae.writeDataPoint({
21
+ blobs: [
22
+ severity,
23
+ config.realmId,
24
+ '',
25
+ '',
26
+ event.surface,
27
+ '',
28
+ '',
29
+ '',
30
+ '',
31
+ '',
32
+ '',
33
+ '',
34
+ '',
35
+ '',
36
+ '',
37
+ '',
38
+ '',
39
+ event.category,
40
+ event.fingerprint,
41
+ '',
42
+ ],
43
+ doubles: [event.occurredAt, event.statusCode ?? 0, event.durationMs ?? 0],
44
+ indexes: [severity],
45
+ });
46
+ }
47
+ catch (error) {
48
+ console.error(`[monitoring] failed to write WAE error event: ${errorMessage(error)}`);
49
+ }
50
+ }
51
+ export function severityFromCategory(category) {
52
+ switch (category) {
53
+ case 'exception':
54
+ return 'exception';
55
+ case 'console-warn':
56
+ case 'slow-request':
57
+ return 'warning';
58
+ case 'fivexx':
59
+ case 'console-error':
60
+ return 'error';
61
+ }
62
+ }
63
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/tail/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,YAAY,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAA;AAe1F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACtC,EAAc,EACd,GAAuC,EACvC,MAAqB,EACrB,KAAyB,EACzB,YAAuB,UAAU;IAEjC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IACrD,IAAI,CAAC;QACJ,MAAM,EAAE;aACN,OAAO,CAAC;;;;IAIR,CAAC;aACD,IAAI,CACJ,SAAS,EAAE,EACX,MAAM,CAAC,OAAO,EACd,KAAK,CAAC,OAAO,EACb,QAAQ,EACR,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,KAAK,IAAI,IAAI,EACnB,KAAK,CAAC,SAAS,IAAI,IAAI,EACvB,KAAK,CAAC,UAAU,IAAI,IAAI,EACxB,KAAK,CAAC,UAAU,IAAI,IAAI,EACxB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,EACnC,KAAK,CAAC,WAAW,CACjB;aACA,GAAG,EAAE,CAAA;IACR,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,+CAA+C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,IAAI,CAAC,GAAG;QAAE,OAAM;IAChB,IAAI,CAAC;QACJ,MAAM,GAAG,CAAC,cAAc,CAAC;YACxB,KAAK,EAAE;gBACN,QAAQ;gBACR,MAAM,CAAC,OAAO;gBACd,EAAE;gBACF,EAAE;gBACF,KAAK,CAAC,OAAO;gBACb,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,EAAE;gBACF,KAAK,CAAC,QAAQ;gBACd,KAAK,CAAC,WAAW;gBACjB,EAAE;aACF;YACD,OAAO,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;YACzE,OAAO,EAAE,CAAC,QAAQ,CAAC;SACnB,CAAC,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,iDAAiD,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACtF,CAAC;AACF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAuB;IAC3D,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,WAAW;YACf,OAAO,WAAW,CAAA;QACnB,KAAK,cAAc,CAAC;QACpB,KAAK,cAAc;YAClB,OAAO,SAAS,CAAA;QACjB,KAAK,QAAQ,CAAC;QACd,KAAK,eAAe;YACnB,OAAO,OAAO,CAAA;IAChB,CAAC;AACF,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { CategorizedEvent } from './categorize.js';
2
+ export declare function redact(event: CategorizedEvent): CategorizedEvent;
3
+ export declare function redactString(value: string): string;
4
+ export declare function redactSurface(surface: string): string;
5
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/tail/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAKvD,wBAAgB,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAOhE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAErD"}
@@ -0,0 +1,25 @@
1
+ const MAX_MESSAGE_LENGTH = 2000;
2
+ const MAX_STACK_LENGTH = 4000;
3
+ export function redact(event) {
4
+ return {
5
+ ...event,
6
+ message: truncate(redactString(event.message), MAX_MESSAGE_LENGTH),
7
+ stack: event.stack ? truncate(redactString(event.stack), MAX_STACK_LENGTH) : undefined,
8
+ surface: redactSurface(event.surface),
9
+ };
10
+ }
11
+ export function redactString(value) {
12
+ return value
13
+ .replace(/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g, '[redacted-email]')
14
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, '[redacted-jwt]')
15
+ .replace(/\bcfut_[A-Za-z0-9_-]+\b/g, '[redacted-cf-token]')
16
+ .replace(/\bsk-[A-Za-z0-9_-]{20,}\b/g, '[redacted-api-key]')
17
+ .replace(/\b(?=[A-Za-z0-9]{32,}\b)(?=[A-Za-z0-9]*\d)[A-Za-z0-9]+\b/g, '[redacted-token]');
18
+ }
19
+ export function redactSurface(surface) {
20
+ return surface.replace(/\?[^\s]+/, '');
21
+ }
22
+ function truncate(value, maxLength) {
23
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
24
+ }
25
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/tail/redact.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAC/B,MAAM,gBAAgB,GAAG,IAAI,CAAA;AAE7B,MAAM,UAAU,MAAM,CAAC,KAAuB;IAC7C,OAAO;QACN,GAAG,KAAK;QACR,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;QAClE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;QACtF,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC;KACrC,CAAA;AACF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,OAAO,KAAK;SACV,OAAO,CAAC,qDAAqD,EAAE,kBAAkB,CAAC;SAClF,OAAO,CAAC,wDAAwD,EAAE,gBAAgB,CAAC;SACnF,OAAO,CAAC,0BAA0B,EAAE,qBAAqB,CAAC;SAC1D,OAAO,CAAC,4BAA4B,EAAE,oBAAoB,CAAC;SAC3D,OAAO,CAAC,2DAA2D,EAAE,kBAAkB,CAAC,CAAA;AAC3F,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC5C,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AACvC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAa,EAAE,SAAiB;IACjD,OAAO,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACpE,CAAC"}