Skip to content

Commit 02003f7

Browse files
committed
feat(telemetry): add app version to exception captures and filter 402 errors
- Add appVersion as required parameter in captureException across TelemetryClient interface - TelemetryService stores appVersion (set once at activation) and automatically passes it to all clients - PostHogTelemetryClient includes $app_version in all exception reports sent to PostHog - Add 402 (payment required) to EXPECTED_API_ERROR_CODES to filter out billing-related errors - Add tests for shouldReportApiErrorToTelemetry and updated captureException tests
1 parent 57ea735 commit 02003f7

File tree

9 files changed

+115
-6
lines changed

9 files changed

+115
-6
lines changed

packages/cloud/src/TelemetryClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ abstract class BaseTelemetryClient implements TelemetryClient {
6969

7070
public abstract capture(event: TelemetryEvent): Promise<void>
7171

72-
public captureException(_error: Error, _additionalProperties?: Record<string, unknown>): void {
72+
public captureException(_error: Error, _appVersion: string, _additionalProperties?: Record<string, unknown>): void {
7373
// No-op - exception capture is only supported by PostHog
7474
}
7575

packages/telemetry/src/BaseTelemetryClient.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ export abstract class BaseTelemetryClient implements TelemetryClient {
5959

6060
public abstract capture(event: TelemetryEvent): Promise<void>
6161

62-
public abstract captureException(error: Error, additionalProperties?: Record<string, unknown>): void
62+
public abstract captureException(
63+
error: Error,
64+
appVersion: string,
65+
additionalProperties?: Record<string, unknown>,
66+
): void
6367

6468
public setProvider(provider: TelemetryPropertiesProvider): void {
6569
this.providerRef = new WeakRef(provider)

packages/telemetry/src/PostHogTelemetryClient.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ export class PostHogTelemetryClient extends BaseTelemetryClient {
6161
})
6262
}
6363

64-
public override captureException(error: Error, additionalProperties?: Record<string, unknown>): void {
64+
public override captureException(
65+
error: Error,
66+
appVersion: string,
67+
additionalProperties?: Record<string, unknown>,
68+
): void {
6569
if (!this.isTelemetryEnabled()) {
6670
if (this.debug) {
6771
console.info(`[PostHogTelemetryClient#captureException] Skipping exception: ${error.message}`)
@@ -74,7 +78,10 @@ export class PostHogTelemetryClient extends BaseTelemetryClient {
7478
console.info(`[PostHogTelemetryClient#captureException] ${error.message}`)
7579
}
7680

77-
this.client.captureException(error, this.distinctId, additionalProperties)
81+
this.client.captureException(error, this.distinctId, {
82+
...additionalProperties,
83+
$app_version: appVersion,
84+
})
7885
}
7986

8087
/**

packages/telemetry/src/TelemetryService.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,22 @@ import {
1313
* variables are loaded.
1414
*/
1515
export class TelemetryService {
16+
private appVersion: string = "unknown"
17+
1618
constructor(private clients: TelemetryClient[]) {}
1719

1820
public register(client: TelemetryClient): void {
1921
this.clients.push(client)
2022
}
2123

24+
/**
25+
* Sets the app version for exception tracking
26+
* @param version The app version string
27+
*/
28+
public setAppVersion(version: string): void {
29+
this.appVersion = version
30+
}
31+
2232
/**
2333
* Sets the ClineProvider reference to use for global properties
2434
* @param provider A ClineProvider instance to use
@@ -75,7 +85,7 @@ export class TelemetryService {
7585
return
7686
}
7787

78-
this.clients.forEach((client) => client.captureException(error, additionalProperties))
88+
this.clients.forEach((client) => client.captureException(error, this.appVersion, additionalProperties))
7989
}
8090

8191
public captureTaskCreated(taskId: string): void {

packages/telemetry/src/__tests__/PostHogTelemetryClient.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe("PostHogTelemetryClient", () => {
3232

3333
mockPostHogClient = {
3434
capture: vi.fn(),
35+
captureException: vi.fn(),
3536
optIn: vi.fn(),
3637
optOut: vi.fn(),
3738
shutdown: vi.fn().mockResolvedValue(undefined),
@@ -366,6 +367,47 @@ describe("PostHogTelemetryClient", () => {
366367
})
367368
})
368369

370+
describe("captureException", () => {
371+
it("should not capture exceptions when telemetry is disabled", () => {
372+
const client = new PostHogTelemetryClient()
373+
client.updateTelemetryState(false)
374+
375+
const error = new Error("Test error")
376+
client.captureException(error, "1.0.0")
377+
378+
expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
379+
})
380+
381+
it("should capture exceptions with app version", () => {
382+
const client = new PostHogTelemetryClient()
383+
client.updateTelemetryState(true)
384+
385+
const error = new Error("Test error")
386+
client.captureException(error, "1.0.0", { customProp: "value" })
387+
388+
expect(mockPostHogClient.captureException).toHaveBeenCalledWith(
389+
error,
390+
"test-machine-id",
391+
expect.objectContaining({
392+
customProp: "value",
393+
$app_version: "1.0.0",
394+
}),
395+
)
396+
})
397+
398+
it("should capture exceptions with only app version (no additional properties)", () => {
399+
const client = new PostHogTelemetryClient()
400+
client.updateTelemetryState(true)
401+
402+
const error = new Error("Test error")
403+
client.captureException(error, "2.0.0")
404+
405+
expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
406+
$app_version: "2.0.0",
407+
})
408+
})
409+
})
410+
369411
describe("shutdown", () => {
370412
it("should call shutdown on the PostHog client", async () => {
371413
const client = new PostHogTelemetryClient()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// pnpm --filter @roo-code/types test src/__tests__/telemetry.test.ts
2+
3+
import { describe, it, expect } from "vitest"
4+
import { shouldReportApiErrorToTelemetry, EXPECTED_API_ERROR_CODES } from "../telemetry.js"
5+
6+
describe("shouldReportApiErrorToTelemetry", () => {
7+
it("should return true when errorCode is undefined", () => {
8+
expect(shouldReportApiErrorToTelemetry(undefined)).toBe(true)
9+
})
10+
11+
it("should return false for 402 (payment required) errors", () => {
12+
expect(shouldReportApiErrorToTelemetry(402)).toBe(false)
13+
})
14+
15+
it("should return false for 429 (rate limit) errors", () => {
16+
expect(shouldReportApiErrorToTelemetry(429)).toBe(false)
17+
})
18+
19+
it("should return true for other error codes", () => {
20+
expect(shouldReportApiErrorToTelemetry(400)).toBe(true)
21+
expect(shouldReportApiErrorToTelemetry(401)).toBe(true)
22+
expect(shouldReportApiErrorToTelemetry(403)).toBe(true)
23+
expect(shouldReportApiErrorToTelemetry(404)).toBe(true)
24+
expect(shouldReportApiErrorToTelemetry(500)).toBe(true)
25+
expect(shouldReportApiErrorToTelemetry(502)).toBe(true)
26+
expect(shouldReportApiErrorToTelemetry(503)).toBe(true)
27+
})
28+
})
29+
30+
describe("EXPECTED_API_ERROR_CODES", () => {
31+
it("should contain 402 (payment required)", () => {
32+
expect(EXPECTED_API_ERROR_CODES.has(402)).toBe(true)
33+
})
34+
35+
it("should contain 429 (rate limit)", () => {
36+
expect(EXPECTED_API_ERROR_CODES.has(429)).toBe(true)
37+
})
38+
39+
it("should only contain expected error codes", () => {
40+
expect(EXPECTED_API_ERROR_CODES.size).toBe(2)
41+
})
42+
})

packages/types/src/telemetry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export interface TelemetryClient {
262262

263263
setProvider(provider: TelemetryPropertiesProvider): void
264264
capture(options: TelemetryEvent): Promise<void>
265-
captureException(error: Error, additionalProperties?: Record<string, unknown>): void
265+
captureException(error: Error, appVersion: string, additionalProperties?: Record<string, unknown>): void
266266
updateTelemetryState(isOptedIn: boolean): void
267267
isTelemetryEnabled(): boolean
268268
shutdown(): Promise<void>
@@ -273,6 +273,7 @@ export interface TelemetryClient {
273273
* These are normal/expected errors that users can't do much about.
274274
*/
275275
export const EXPECTED_API_ERROR_CODES = new Set([
276+
402, // Payment required - billing issues
276277
429, // Rate limit - expected when hitting API limits
277278
])
278279

src/__tests__/extension.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,14 @@ vi.mock("@roo-code/telemetry", () => ({
7272
createInstance: vi.fn().mockReturnValue({
7373
register: vi.fn(),
7474
setProvider: vi.fn(),
75+
setAppVersion: vi.fn(),
7576
shutdown: vi.fn(),
7677
}),
7778
get instance() {
7879
return {
7980
register: vi.fn(),
8081
setProvider: vi.fn(),
82+
setAppVersion: vi.fn(),
8183
shutdown: vi.fn(),
8284
}
8385
},

src/extension.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export async function activate(context: vscode.ExtensionContext) {
7171

7272
// Initialize telemetry service.
7373
const telemetryService = TelemetryService.createInstance()
74+
telemetryService.setAppVersion(Package.version)
7475

7576
try {
7677
telemetryService.register(new PostHogTelemetryClient())

0 commit comments

Comments
 (0)