mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
import { PostHog } from "posthog-node"
|
|
import * as vscode from "vscode"
|
|
|
|
import {
|
|
type TelemetryProperties,
|
|
type TelemetryEvent,
|
|
TelemetryEventName,
|
|
getErrorStatusCode,
|
|
getErrorMessage,
|
|
shouldReportApiErrorToTelemetry,
|
|
isApiProviderError,
|
|
extractApiProviderErrorProperties,
|
|
isConsecutiveMistakeError,
|
|
extractConsecutiveMistakeErrorProperties,
|
|
} from "@roo-code/types"
|
|
|
|
import { BaseTelemetryClient } from "./BaseTelemetryClient"
|
|
|
|
/**
|
|
* PostHogTelemetryClient handles telemetry event tracking for the Roo Code extension.
|
|
* Uses PostHog analytics to track user interactions and system events.
|
|
* Respects user privacy settings and VSCode's global telemetry configuration.
|
|
*/
|
|
export class PostHogTelemetryClient extends BaseTelemetryClient {
|
|
private client: PostHog
|
|
private distinctId: string = vscode.env.machineId
|
|
// Git repository properties that should be filtered out
|
|
private readonly gitPropertyNames = ["repositoryUrl", "repositoryName", "defaultBranch"]
|
|
|
|
constructor(debug = false) {
|
|
super(
|
|
{
|
|
type: "exclude",
|
|
events: [TelemetryEventName.TASK_MESSAGE, TelemetryEventName.LLM_COMPLETION],
|
|
},
|
|
debug,
|
|
)
|
|
|
|
this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { host: "https://ph.roocode.com" })
|
|
}
|
|
|
|
/**
|
|
* Filter out git repository properties for PostHog telemetry
|
|
* @param propertyName The property name to check
|
|
* @returns Whether the property should be included in telemetry events
|
|
*/
|
|
protected override isPropertyCapturable(propertyName: string): boolean {
|
|
// Filter out git repository properties
|
|
if (this.gitPropertyNames.includes(propertyName)) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public override async capture(event: TelemetryEvent): Promise<void> {
|
|
if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
|
|
if (this.debug) {
|
|
console.info(`[PostHogTelemetryClient#capture] Skipping event: ${event.event}`)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if (this.debug) {
|
|
console.info(`[PostHogTelemetryClient#capture] ${event.event}`)
|
|
}
|
|
|
|
const properties = await this.getEventProperties(event)
|
|
|
|
this.client.capture({
|
|
distinctId: this.distinctId,
|
|
event: event.event,
|
|
properties,
|
|
})
|
|
}
|
|
|
|
public override async captureException(
|
|
error: Error,
|
|
additionalProperties?: Record<string, unknown>,
|
|
): Promise<void> {
|
|
if (!this.isTelemetryEnabled()) {
|
|
if (this.debug) {
|
|
console.info(`[PostHogTelemetryClient#captureException] Skipping exception: ${error.message}`)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Extract error status code and message for filtering.
|
|
const errorCode = getErrorStatusCode(error)
|
|
const errorMessage = getErrorMessage(error) ?? error.message
|
|
|
|
// Filter out expected errors (e.g., 402 billing, 429 rate limits)
|
|
if (!shouldReportApiErrorToTelemetry(errorCode, errorMessage)) {
|
|
if (this.debug) {
|
|
console.info(
|
|
`[PostHogTelemetryClient#captureException] Filtering out expected error: ${errorCode} - ${errorMessage}`,
|
|
)
|
|
}
|
|
return
|
|
}
|
|
|
|
if (this.debug) {
|
|
console.info(`[PostHogTelemetryClient#captureException] ${error.message}`)
|
|
}
|
|
|
|
// Auto-extract properties from known error types and merge with additionalProperties.
|
|
// Explicit additionalProperties take precedence over auto-extracted properties.
|
|
let mergedProperties = additionalProperties
|
|
|
|
if (isApiProviderError(error)) {
|
|
const extractedProperties = extractApiProviderErrorProperties(error)
|
|
mergedProperties = { ...extractedProperties, ...additionalProperties }
|
|
} else if (isConsecutiveMistakeError(error)) {
|
|
const extractedProperties = extractConsecutiveMistakeErrorProperties(error)
|
|
mergedProperties = { ...extractedProperties, ...additionalProperties }
|
|
}
|
|
|
|
// Override the error message with the extracted error message.
|
|
error.message = errorMessage
|
|
|
|
const provider = this.providerRef?.deref()
|
|
let telemetryProperties: TelemetryProperties | undefined = undefined
|
|
|
|
if (provider) {
|
|
try {
|
|
telemetryProperties = await provider.getTelemetryProperties()
|
|
} catch (_error) {
|
|
// Ignore.
|
|
}
|
|
}
|
|
|
|
const exceptionProperties = {
|
|
...mergedProperties,
|
|
$app_version: telemetryProperties?.appVersion,
|
|
}
|
|
|
|
this.client.captureException(error, this.distinctId, exceptionProperties)
|
|
}
|
|
|
|
/**
|
|
* Updates the telemetry state based on user preferences and VSCode settings.
|
|
* Only enables telemetry if both VSCode global telemetry is enabled and
|
|
* user has opted in.
|
|
* @param didUserOptIn Whether the user has explicitly opted into telemetry
|
|
*/
|
|
public override updateTelemetryState(didUserOptIn: boolean): void {
|
|
this.telemetryEnabled = false
|
|
|
|
// First check global telemetry level - telemetry should only be enabled when level is "all".
|
|
const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
|
|
const globalTelemetryEnabled = telemetryLevel === "all"
|
|
|
|
// We only enable telemetry if global vscode telemetry is enabled.
|
|
if (globalTelemetryEnabled) {
|
|
this.telemetryEnabled = didUserOptIn
|
|
}
|
|
|
|
// Update PostHog client state based on telemetry preference.
|
|
if (this.telemetryEnabled) {
|
|
this.client.optIn()
|
|
} else {
|
|
this.client.optOut()
|
|
}
|
|
}
|
|
|
|
public override async shutdown(): Promise<void> {
|
|
await this.client.shutdown()
|
|
}
|
|
}
|