mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(telemetry): Sentry + PostHog quality pass (#546)
Comprehensive telemetry quality improvements across Sentry and PostHog, grounded in an audit of the live data plus current best-practice research. Sentry: job_id/instance_id tags, operational fingerprinting, PII-safe settings context on bug events, web tag population + extension-noise filtering, an early-crash buffer, http status/method kept on breadcrumbs, and a gated-off-by-default performance-tracing re-enable (tracesSampler that zeroes db/redis/queue-poll root spans + drops the Redis integration) with worker job spans and canonical-host cron monitors. PostHog: history_change SPA pageviews, instance_id super property for fleet rollups, enriched tool_used (formats, byte sizes, is_batch, execution_hint, real error_kind taxonomy), the previously-dead result_saved/batch_processed/ai_bundle_prompted events fired, search click-through, editor + Automate authoring + auth instrumentation, a before_send PII boundary, and minimal opt-in landing-site pageviews.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
flushEarlyErrors,
|
||||
resetEarlyErrorsForTests,
|
||||
startEarlyErrorCapture,
|
||||
} from "../../../apps/web/src/lib/early-errors.js";
|
||||
|
||||
const h = vi.hoisted(() => ({ captureException: vi.fn(), client: {} as object | undefined }));
|
||||
vi.mock("@sentry/react", () => ({
|
||||
captureException: h.captureException,
|
||||
getClient: () => h.client,
|
||||
}));
|
||||
|
||||
// Guard so jsdom does not surface these synthetic error events as "uncaught"
|
||||
// (production code intentionally never preventDefaults real errors).
|
||||
const guard = (e: Event) => e.preventDefault();
|
||||
beforeEach(() => {
|
||||
resetEarlyErrorsForTests();
|
||||
vi.clearAllMocks();
|
||||
h.client = {};
|
||||
window.addEventListener("error", guard);
|
||||
});
|
||||
afterEach(() => window.removeEventListener("error", guard));
|
||||
|
||||
describe("early error buffer", () => {
|
||||
it("buffers a pre-init error and replays it to Sentry on flush", async () => {
|
||||
startEarlyErrorCapture();
|
||||
const boom = new Error("early boom");
|
||||
window.dispatchEvent(new ErrorEvent("error", { error: boom, cancelable: true }));
|
||||
|
||||
await flushEarlyErrors();
|
||||
|
||||
expect(h.captureException).toHaveBeenCalledTimes(1);
|
||||
expect(h.captureException).toHaveBeenCalledWith(boom);
|
||||
});
|
||||
|
||||
it("does not replay when Sentry never initialized (opt-out safe)", async () => {
|
||||
startEarlyErrorCapture();
|
||||
window.dispatchEvent(new ErrorEvent("error", { error: new Error("x"), cancelable: true }));
|
||||
h.client = undefined; // no Sentry client -> analytics off
|
||||
|
||||
await flushEarlyErrors();
|
||||
|
||||
expect(h.captureException).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("stops buffering after flush so post-init errors are left to the SDK", async () => {
|
||||
startEarlyErrorCapture();
|
||||
await flushEarlyErrors();
|
||||
window.dispatchEvent(new ErrorEvent("error", { error: new Error("late"), cancelable: true }));
|
||||
await flushEarlyErrors();
|
||||
|
||||
expect(h.captureException).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -71,19 +71,23 @@ describe("buildWebBeforeSend", () => {
|
||||
expect(buildWebBeforeSend(() => false)(baseEvent(), {})).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the breadcrumb trail, redacting paths/urls and dropping data payloads", () => {
|
||||
it("keeps the breadcrumb trail, redacting urls but keeping safe fetch status/method", () => {
|
||||
const send = buildWebBeforeSend(() => true);
|
||||
const out = send(
|
||||
baseEvent({
|
||||
breadcrumbs: [
|
||||
{ message: "fetch https://host/user.png", category: "fetch", data: { url: "x" } },
|
||||
{
|
||||
message: "fetch https://host/user.png",
|
||||
category: "fetch",
|
||||
data: { url: "https://host/user.png", status_code: 500, method: "POST" },
|
||||
},
|
||||
{ message: "open /Users/a/secret.pdf", category: "console", level: "warning" },
|
||||
],
|
||||
}),
|
||||
{},
|
||||
)!;
|
||||
expect(out.breadcrumbs).toEqual([
|
||||
{ message: "fetch <url>", category: "fetch" },
|
||||
{ message: "fetch <url>", category: "fetch", data: { status_code: 500, method: "POST" } },
|
||||
{ message: "open <path>", category: "console", level: "warning" },
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user