Skip to content

Commit c850f89

Browse files
committed
feat: add comprehensive error telemetry to code-index service
- Add new CODE_INDEX_ERROR telemetry event type - Implement error tracking across all code-index components: - Scanner: track file scanning and processing errors - Parser: track parsing failures and language detection issues - File watcher: track file system monitoring errors - Orchestrator: track coordination and workflow errors - Cache manager: track cache operations and persistence errors - Search service: track search and indexing errors - Manager: track initialization and lifecycle errors - Service factory: track service creation errors This improves observability and debugging capabilities for the code indexing system.
1 parent b03d03d commit c850f89

File tree

17 files changed

+340
-43
lines changed

17 files changed

+340
-43
lines changed

packages/types/src/telemetry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export enum TelemetryEventName {
6565
DIFF_APPLICATION_ERROR = "Diff Application Error",
6666
SHELL_INTEGRATION_ERROR = "Shell Integration Error",
6767
CONSECUTIVE_MISTAKE_ERROR = "Consecutive Mistake Error",
68+
CODE_INDEX_ERROR = "Code Index Error",
6869
}
6970

7071
/**
@@ -152,6 +153,7 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
152153
TelemetryEventName.DIFF_APPLICATION_ERROR,
153154
TelemetryEventName.SHELL_INTEGRATION_ERROR,
154155
TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR,
156+
TelemetryEventName.CODE_INDEX_ERROR,
155157
TelemetryEventName.CONTEXT_CONDENSED,
156158
TelemetryEventName.SLIDING_WINDOW_TRUNCATION,
157159
TelemetryEventName.TAB_SHOWN,

src/services/code-index/cache-manager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { createHash } from "crypto"
33
import { ICacheManager } from "./interfaces/cache"
44
import debounce from "lodash.debounce"
55
import { safeWriteJson } from "../../utils/safeWriteJson"
6+
import { TelemetryService } from "@roo-code/telemetry"
7+
import { TelemetryEventName } from "@roo-code/types"
68

79
/**
810
* Manages the cache for code indexing
@@ -39,6 +41,11 @@ export class CacheManager implements ICacheManager {
3941
this.fileHashes = JSON.parse(cacheData.toString())
4042
} catch (error) {
4143
this.fileHashes = {}
44+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
45+
error: error instanceof Error ? error.message : String(error),
46+
stack: error instanceof Error ? error.stack : undefined,
47+
location: "initialize",
48+
})
4249
}
4350
}
4451

@@ -50,6 +57,11 @@ export class CacheManager implements ICacheManager {
5057
await safeWriteJson(this.cachePath.fsPath, this.fileHashes)
5158
} catch (error) {
5259
console.error("Failed to save cache:", error)
60+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
61+
error: error instanceof Error ? error.message : String(error),
62+
stack: error instanceof Error ? error.stack : undefined,
63+
location: "_performSave",
64+
})
5365
}
5466
}
5567

@@ -62,6 +74,11 @@ export class CacheManager implements ICacheManager {
6274
this.fileHashes = {}
6375
} catch (error) {
6476
console.error("Failed to clear cache file:", error, this.cachePath)
77+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
78+
error: error instanceof Error ? error.message : String(error),
79+
stack: error instanceof Error ? error.stack : undefined,
80+
location: "clearCacheFile",
81+
})
6582
}
6683
}
6784

src/services/code-index/embedders/__tests__/openai.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import { MAX_BATCH_TOKENS, MAX_ITEM_TOKENS, MAX_BATCH_RETRIES, INITIAL_RETRY_DEL
77
// Mock the OpenAI SDK
88
vitest.mock("openai")
99

10+
// Mock TelemetryService
11+
vitest.mock("@roo-code/telemetry", () => ({
12+
TelemetryService: {
13+
instance: {
14+
captureEvent: vitest.fn(),
15+
},
16+
},
17+
}))
18+
1019
// Mock i18n
1120
vitest.mock("../../../../i18n", () => ({
1221
t: (key: string, params?: Record<string, any>) => {

src/services/code-index/embedders/gemini.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { OpenAICompatibleEmbedder } from "./openai-compatible"
22
import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder"
33
import { GEMINI_MAX_ITEM_TOKENS } from "../constants"
44
import { t } from "../../../i18n"
5+
import { TelemetryEventName } from "@roo-code/types"
6+
import { TelemetryService } from "@roo-code/telemetry"
57

68
/**
79
* Gemini embedder implementation that wraps the OpenAI Compatible embedder
@@ -43,18 +45,36 @@ export class GeminiEmbedder implements IEmbedder {
4345
* @returns Promise resolving to embedding response
4446
*/
4547
async createEmbeddings(texts: string[], model?: string): Promise<EmbeddingResponse> {
46-
// Always use the fixed Gemini model, ignoring any passed model parameter
47-
return this.openAICompatibleEmbedder.createEmbeddings(texts, GeminiEmbedder.GEMINI_MODEL)
48+
try {
49+
// Always use the fixed Gemini model, ignoring any passed model parameter
50+
return await this.openAICompatibleEmbedder.createEmbeddings(texts, GeminiEmbedder.GEMINI_MODEL)
51+
} catch (error) {
52+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
53+
error: error instanceof Error ? error.message : String(error),
54+
stack: error instanceof Error ? error.stack : undefined,
55+
location: "GeminiEmbedder:createEmbeddings",
56+
})
57+
throw error
58+
}
4859
}
4960

5061
/**
5162
* Validates the Gemini embedder configuration by delegating to the underlying OpenAI-compatible embedder
5263
* @returns Promise resolving to validation result with success status and optional error message
5364
*/
5465
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
55-
// Delegate validation to the OpenAI-compatible embedder
56-
// The error messages will be specific to Gemini since we're using Gemini's base URL
57-
return this.openAICompatibleEmbedder.validateConfiguration()
66+
try {
67+
// Delegate validation to the OpenAI-compatible embedder
68+
// The error messages will be specific to Gemini since we're using Gemini's base URL
69+
return await this.openAICompatibleEmbedder.validateConfiguration()
70+
} catch (error) {
71+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
72+
error: error instanceof Error ? error.message : String(error),
73+
stack: error instanceof Error ? error.stack : undefined,
74+
location: "GeminiEmbedder:validateConfiguration",
75+
})
76+
throw error
77+
}
5878
}
5979

6080
/**

src/services/code-index/embedders/ollama.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { getModelQueryPrefix } from "../../../shared/embeddingModels"
44
import { MAX_ITEM_TOKENS } from "../constants"
55
import { t } from "../../../i18n"
66
import { withValidationErrorHandling } from "../shared/validation-helpers"
7+
import { TelemetryService } from "@roo-code/telemetry"
8+
import { TelemetryEventName } from "@roo-code/types"
79

810
/**
911
* Implements the IEmbedder interface using a local Ollama instance.
@@ -102,6 +104,15 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
102104
embeddings: embeddings,
103105
}
104106
} catch (error: any) {
107+
// Capture telemetry before reformatting the error
108+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
109+
error: error instanceof Error ? error.message : String(error),
110+
stack: error instanceof Error ? error.stack : undefined,
111+
location: "OllamaEmbedder:createEmbeddings",
112+
baseUrl: this.baseUrl,
113+
modelToUse: modelToUse,
114+
})
115+
105116
// Log the original error for debugging purposes
106117
console.error("Ollama embedding failed:", error)
107118

@@ -222,16 +233,37 @@ export class CodeIndexOllamaEmbedder implements IEmbedder {
222233
error?.code === "ECONNREFUSED" ||
223234
error?.message?.includes("ECONNREFUSED")
224235
) {
236+
// Capture telemetry for connection failed error
237+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
238+
error: error instanceof Error ? error.message : String(error),
239+
stack: error instanceof Error ? error.stack : undefined,
240+
location: "OllamaEmbedder:validateConfiguration:connectionFailed",
241+
baseUrl: this.baseUrl,
242+
})
225243
return {
226244
valid: false,
227245
error: t("embeddings:ollama.serviceNotRunning", { baseUrl: this.baseUrl }),
228246
}
229247
} else if (error?.code === "ENOTFOUND" || error?.message?.includes("ENOTFOUND")) {
248+
// Capture telemetry for host not found error
249+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
250+
error: error instanceof Error ? error.message : String(error),
251+
stack: error instanceof Error ? error.stack : undefined,
252+
location: "OllamaEmbedder:validateConfiguration:hostNotFound",
253+
baseUrl: this.baseUrl,
254+
})
230255
return {
231256
valid: false,
232257
error: t("embeddings:ollama.hostNotFound", { baseUrl: this.baseUrl }),
233258
}
234259
} else if (error?.name === "AbortError") {
260+
// Capture telemetry for timeout error
261+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
262+
error: error instanceof Error ? error.message : String(error),
263+
stack: error instanceof Error ? error.stack : undefined,
264+
location: "OllamaEmbedder:validateConfiguration:timeout",
265+
baseUrl: this.baseUrl,
266+
})
235267
// Handle timeout
236268
return {
237269
valid: false,

src/services/code-index/embedders/openai-compatible.ts

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
import { getDefaultModelId, getModelQueryPrefix } from "../../../shared/embeddingModels"
1010
import { t } from "../../../i18n"
1111
import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "../shared/validation-helpers"
12+
import { TelemetryEventName } from "@roo-code/types"
13+
import { TelemetryService } from "@roo-code/telemetry"
1214

1315
interface EmbeddingItem {
1416
embedding: string | number[]
@@ -284,6 +286,16 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
284286
},
285287
}
286288
} catch (error) {
289+
// Capture telemetry before error is reformatted
290+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
291+
error: error instanceof Error ? error.message : String(error),
292+
stack: error instanceof Error ? error.stack : undefined,
293+
location: "OpenAICompatibleEmbedder:_embedBatchWithRetries",
294+
model: model,
295+
attempt: attempts + 1,
296+
baseUrl: this.baseUrl,
297+
})
298+
287299
const hasMoreAttempts = attempts < MAX_RETRIES - 1
288300

289301
// Check if it's a rate limit error
@@ -318,33 +330,45 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
318330
*/
319331
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
320332
return withValidationErrorHandling(async () => {
321-
// Test with a minimal embedding request
322-
const testTexts = ["test"]
323-
const modelToUse = this.defaultModelId
324-
325-
let response: OpenAIEmbeddingResponse
326-
327-
if (this.isFullUrl) {
328-
// Test direct HTTP request for full endpoint URLs
329-
response = await this.makeDirectEmbeddingRequest(this.baseUrl, testTexts, modelToUse)
330-
} else {
331-
// Test using OpenAI SDK for base URLs
332-
response = (await this.embeddingsClient.embeddings.create({
333-
input: testTexts,
334-
model: modelToUse,
335-
encoding_format: "base64",
336-
})) as OpenAIEmbeddingResponse
337-
}
333+
try {
334+
// Test with a minimal embedding request
335+
const testTexts = ["test"]
336+
const modelToUse = this.defaultModelId
338337

339-
// Check if we got a valid response
340-
if (!response?.data || response.data.length === 0) {
341-
return {
342-
valid: false,
343-
error: "embeddings:validation.invalidResponse",
338+
let response: OpenAIEmbeddingResponse
339+
340+
if (this.isFullUrl) {
341+
// Test direct HTTP request for full endpoint URLs
342+
response = await this.makeDirectEmbeddingRequest(this.baseUrl, testTexts, modelToUse)
343+
} else {
344+
// Test using OpenAI SDK for base URLs
345+
response = (await this.embeddingsClient.embeddings.create({
346+
input: testTexts,
347+
model: modelToUse,
348+
encoding_format: "base64",
349+
})) as OpenAIEmbeddingResponse
344350
}
345-
}
346351

347-
return { valid: true }
352+
// Check if we got a valid response
353+
if (!response?.data || response.data.length === 0) {
354+
return {
355+
valid: false,
356+
error: "embeddings:validation.invalidResponse",
357+
}
358+
}
359+
360+
return { valid: true }
361+
} catch (error) {
362+
// Capture telemetry for validation errors
363+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
364+
error: error instanceof Error ? error.message : String(error),
365+
stack: error instanceof Error ? error.stack : undefined,
366+
location: "OpenAICompatibleEmbedder:validateConfiguration",
367+
baseUrl: this.baseUrl,
368+
modelToUse: this.defaultModelId,
369+
})
370+
throw error
371+
}
348372
}, "openai-compatible")
349373
}
350374

src/services/code-index/embedders/openai.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
import { getModelQueryPrefix } from "../../../shared/embeddingModels"
1212
import { t } from "../../../i18n"
1313
import { withValidationErrorHandling, formatEmbeddingError, HttpError } from "../shared/validation-helpers"
14+
import { TelemetryEventName } from "@roo-code/types"
15+
import { TelemetryService } from "@roo-code/telemetry"
1416

1517
/**
1618
* OpenAI implementation of the embedder interface with batching and rate limiting
@@ -156,6 +158,15 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
156158
continue
157159
}
158160

161+
// Capture telemetry before reformatting the error
162+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
163+
error: error instanceof Error ? error.message : String(error),
164+
stack: error instanceof Error ? error.stack : undefined,
165+
location: "OpenAiEmbedder:_embedBatchWithRetries",
166+
model: model,
167+
attempt: attempts + 1,
168+
})
169+
159170
// Log the error for debugging
160171
console.error(`OpenAI embedder error (attempt ${attempts + 1}/${MAX_RETRIES}):`, error)
161172

@@ -173,21 +184,32 @@ export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder {
173184
*/
174185
async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
175186
return withValidationErrorHandling(async () => {
176-
// Test with a minimal embedding request
177-
const response = await this.embeddingsClient.embeddings.create({
178-
input: ["test"],
179-
model: this.defaultModelId,
180-
})
181-
182-
// Check if we got a valid response
183-
if (!response.data || response.data.length === 0) {
184-
return {
185-
valid: false,
186-
error: t("embeddings:openai.invalidResponseFormat"),
187+
try {
188+
// Test with a minimal embedding request
189+
const response = await this.embeddingsClient.embeddings.create({
190+
input: ["test"],
191+
model: this.defaultModelId,
192+
})
193+
194+
// Check if we got a valid response
195+
if (!response.data || response.data.length === 0) {
196+
return {
197+
valid: false,
198+
error: t("embeddings:openai.invalidResponseFormat"),
199+
}
187200
}
188-
}
189201

190-
return { valid: true }
202+
return { valid: true }
203+
} catch (error) {
204+
// Capture telemetry for validation errors
205+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
206+
error: error instanceof Error ? error.message : String(error),
207+
stack: error instanceof Error ? error.stack : undefined,
208+
location: "OpenAiEmbedder:validateConfiguration",
209+
defaultModelId: this.defaultModelId,
210+
})
211+
throw error
212+
}
191213
}, "openai")
192214
}
193215

src/services/code-index/manager.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import fs from "fs/promises"
1313
import ignore from "ignore"
1414
import path from "path"
1515
import { t } from "../../i18n"
16+
import { TelemetryService } from "@roo-code/telemetry"
17+
import { TelemetryEventName } from "@roo-code/types"
1618

1719
export class CodeIndexManager {
1820
// --- Singleton Implementation ---
@@ -250,6 +252,11 @@ export class CodeIndexManager {
250252
} catch (error) {
251253
// Should never happen: reading file failed even though it exists
252254
console.error("Unexpected error loading .gitignore:", error)
255+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
256+
error: error instanceof Error ? error.message : String(error),
257+
stack: error instanceof Error ? error.stack : undefined,
258+
location: "_recreateServices",
259+
})
253260
}
254261

255262
// (Re)Create shared service instances
@@ -310,6 +317,11 @@ export class CodeIndexManager {
310317
} catch (error) {
311318
// Error state already set in _recreateServices
312319
console.error("Failed to recreate services:", error)
320+
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
321+
error: error instanceof Error ? error.message : String(error),
322+
stack: error instanceof Error ? error.stack : undefined,
323+
location: "handleSettingsChange",
324+
})
313325
// Re-throw the error so the caller knows validation failed
314326
throw error
315327
}

0 commit comments

Comments
 (0)