Unverified 提交 6aabe7c5 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

fix: capture IPC exceptions in PostHog (#3022)

## Summary - route main-process `$exception` telemetry through `posthog.captureException()` in the renderer - normalize exception fields in IPC telemetry payloads and strip them from extra context before capture - add focused unit coverage for exception reconstruction and context extraction ## Test plan - npm run fmt && npm run lint:fix && npm run ts - npm test 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3022" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end -->
上级 b372103a
{
"name": "dyad",
"version": "0.39.0",
"version": "0.40.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dyad",
"version": "0.39.0",
"version": "0.40.0",
"license": "MIT",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.46",
......@@ -68,7 +68,7 @@
"lucide-react": "^0.487.0",
"monaco-editor": "^0.52.2",
"perfect-freehand": "^1.2.2",
"posthog-js": "^1.236.3",
"posthog-js": "^1.265.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-i18next": "^16.5.4",
......@@ -19356,9 +19356,9 @@
}
},
"node_modules/posthog-js": {
"version": "1.261.8",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.261.8.tgz",
"integrity": "sha512-HohKQ5Fuvei/3ZLIdayq6lDpeXsG891t2y2izpHu6q/1SoCS+HlYjViz3WCu9KlE7AfjfpwvN1kjnFNNPWeOig==",
"version": "1.265.1",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.265.1.tgz",
"integrity": "sha512-FKZtHIOBZ8pH80qXmWhoc2qUCkmOnKRdwKiyGourG03h7uV2w74cEmpVDtBxs2jSEbjMCbRi554W5lp6GsREOA==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "1.0.2",
......
......@@ -107,7 +107,7 @@
"lucide-react": "^0.487.0",
"monaco-editor": "^0.52.2",
"perfect-freehand": "^1.2.2",
"posthog-js": "^1.236.3",
"posthog-js": "^1.265.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-i18next": "^16.5.4",
......
import { describe, expect, it } from "vitest";
import {
createExceptionFromTelemetry,
getExceptionTelemetryContext,
} from "@/lib/posthogTelemetry";
describe("createExceptionFromTelemetry", () => {
it("uses exception telemetry fields when present", () => {
const error = createExceptionFromTelemetry({
exception_name: "TypeError",
exception_message: "Boom",
exception_stack_trace: "TypeError: Boom\n at ipc-handler",
});
expect(error).toBeInstanceOf(Error);
expect(error.name).toBe("TypeError");
expect(error.message).toBe("Boom");
expect(error.stack).toBe("TypeError: Boom\n at ipc-handler");
});
it("falls back to a default message when telemetry is incomplete", () => {
const error = createExceptionFromTelemetry(undefined);
expect(error.name).toBe("Error");
expect(error.message).toBe("Unknown IPC exception");
});
});
describe("getExceptionTelemetryContext", () => {
it("removes exception payload fields before passing custom context to PostHog", () => {
expect(
getExceptionTelemetryContext({
exception_name: "TypeError",
exception_message: "Boom",
exception_stack_trace: "TypeError: Boom\n at ipc-handler",
ipc_channel: "window:minimize",
}),
).toEqual({
ipc_channel: "window:minimize",
});
});
it("returns undefined when there is no custom context", () => {
expect(
getExceptionTelemetryContext({
exception_name: "TypeError",
exception_message: "Boom",
}),
).toBeUndefined();
});
});
import { describe, expect, it } from "vitest";
import { shouldFilterTelemetryException } from "@/ipc/utils/telemetry";
describe("shouldFilterTelemetryException", () => {
it("filters the known Supabase auth noise message", () => {
expect(
shouldFilterTelemetryException(
new Error(
"Supabase access token not found. Please authenticate first.",
),
),
).toBe(true);
});
it("does not filter different Supabase auth failures", () => {
expect(
shouldFilterTelemetryException(
new Error(
"Supabase access token not found for organization acme. Please authenticate first.",
),
),
).toBe(false);
});
});
......@@ -3,6 +3,9 @@ import log from "electron-log";
import { TelemetryEventPayload } from "@/ipc/types";
const logger = log.scope("telemetry");
const FILTERED_EXCEPTION_MESSAGES = new Set([
"Supabase access token not found. Please authenticate first.",
]);
/**
* Sends a telemetry event from the main process to the renderer,
......@@ -36,10 +39,22 @@ export function sendTelemetryException(
error instanceof Error
? error
: new Error(String(error ?? "Unknown error"));
if (shouldFilterTelemetryException(err)) {
return;
}
sendTelemetryEvent("$exception", {
$exception_type: err.name,
$exception_message: err.message,
$exception_stack_trace_raw: err.stack,
exception_name: err.name,
exception_message: err.message,
exception_stack_trace: err.stack,
...context,
});
}
export function shouldFilterTelemetryException(error: unknown): boolean {
const message =
error instanceof Error ? error.message : String(error ?? "Unknown error");
return FILTERED_EXCEPTION_MESSAGES.has(message);
}
type TelemetryProperties = Record<string, unknown> | undefined;
export function createExceptionFromTelemetry(properties: TelemetryProperties) {
const exception = new Error(
typeof properties?.exception_message === "string"
? properties.exception_message
: "Unknown IPC exception",
);
if (typeof properties?.exception_name === "string") {
exception.name = properties.exception_name;
}
if (typeof properties?.exception_stack_trace === "string") {
exception.stack = properties.exception_stack_trace;
}
return exception;
}
export function getExceptionTelemetryContext(properties: TelemetryProperties) {
if (!properties) {
return undefined;
}
const {
exception_name: _exceptionName,
exception_message: _exceptionMessage,
exception_stack_trace: _exceptionStackTrace,
...context
} = properties;
return Object.keys(context).length > 0 ? context : undefined;
}
......@@ -28,6 +28,10 @@ import {
} from "./atoms/chatAtoms";
import { pendingQuestionnaireAtom } from "./atoms/planAtoms";
import { queryKeys } from "./lib/queryKeys";
import {
createExceptionFromTelemetry,
getExceptionTelemetryContext,
} from "./lib/posthogTelemetry";
// @ts-ignore
console.log("Running in mode:", import.meta.env.MODE);
......@@ -233,6 +237,14 @@ function App() {
useEffect(() => {
const unsubscribe = ipc.events.system.onTelemetryEvent(
({ eventName, properties }) => {
if (eventName === "$exception") {
posthog.captureException(
createExceptionFromTelemetry(properties),
getExceptionTelemetryContext(properties),
);
return;
}
posthog.capture(eventName, properties);
},
);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论