Skip to main content

telemetry-instrumentation

Instrumentation patterns for telemetry in the vscode-documentdb extension. Use when adding telemetry to a feature, enriching existing telemetry with properties or measurements, linking related events with correlation IDs, instrumenting commands or tree views, suppressing noisy events, or deciding what data points to capture. Also use when asked to "add telemetry" or "improve telemetry" for any feature.

Informations de source

Dépôt
microsoft/vscode-documentdb
Dernière activité de la source
15 juillet 2026 à 09:22
Langue détectée de SKILL.md
anglais
Étoiles
34
Forks
22

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
telemetry-instrumentation
description
Instrumentation patterns for telemetry in the vscode-documentdb extension. Use when adding telemetry to a feature, enriching existing telemetry with properties or measurements, linking related events with correlation IDs, instrumenting commands or tree views, suppressing noisy events, or deciding what data points to capture. Also use when asked to "add telemetry" or "improve telemetry" for any feature.
# Telemetry Instrumentation How to instrument code in vscode-documentdb to produce actionable telemetry. ## What the Framework Gives You for Free The `@microsoft/vscode-azext-utils` library **automatically captures** these — never add them manually: | Auto-captured | Details | | ----------------------- | ------------------------------------------------------------------ | | **Duration** | `measurements.duration` — wall-clock ms from callback start to end | | **Result** | `properties.result` — `"Succeeded"`, `"Failed"`, or `"Canceled"` | | **Error info** | `properties.error` (name), `properties.errorMessage` (message) | | **Cancel detection** | `UserCancelledError` sets result to `"Canceled"` automatically | | **Machine/session IDs** | `VSCodeMachineId`, `VSCodeSessionId` — always present | | **Extension metadata** | Extension name, version, VS Code version | **Do not** re-measure duration, set `result`, or catch errors just for telemetry — the wrapper handles it. ## Core API ### `callWithTelemetryAndErrorHandling(eventId, callback)` The primary wrapper. Creates a telemetry event, runs the callback, reports result + duration + errors. The callback's return value is passed through, so you can wrap any function call directly: ```typescript import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils'; // Simple — wrap logic, telemetry is automatic await callWithTelemetryAndErrorHandling('myFeature.doSomething', async (context) => { context.telemetry.properties.someProperty = 'value'; context.telemetry.measurements.itemCount = items.length; // ... your logic — errors are caught and reported automatically }); // With return value — no need for intermediate variables const result = await callWithTelemetryAndErrorHandling('myFeature.query', async (context) => { context.telemetry.properties.queryType = 'find'; return doWork(); // return value passes through to the caller }); ``` **Event name convention**: use dot-separated hierarchy: `connect`, `connect.getmetadata`, `connect.promptForCredentials`. ### Command Registration (auto-telemetry) Every registered command gets a telemetry event automatically. Prefer the correlation-wrapped variants: ```typescript import { registerCommandWithTreeNodeUnwrapping } from '@microsoft/vscode-azext-utils'; import { withTreeNodeCommandCorrelation, withCommandCorrelation } from '../../utils/commandTelemetry'; // Tree node commands — unwraps tree item + tracks journeyCorrelationId registerCommandWithTreeNodeUnwrapping( 'vscode-documentdb.command.connectionsView.myCommand', withTreeNodeCommandCorrelation(myCommandHandler), ); // Plain commands — tracks journeyCorrelationId registerCommand('vscode-documentdb.command.myCommand', withCommandCorrelation(myCommandHandler)); ``` For critical commands where errors need modal dialogs: ```typescript import { registerCommandWithTreeNodeUnwrappingAndModalErrors } from '../../utils/commandErrorHandling'; ``` ### tRPC Procedures (auto-telemetry) Webview tRPC calls get telemetry automatically via `publicProcedureWithTelemetry`. Each call emits `documentDB.rpc.{type}.{path}` with result, error, abort tracking. The DocumentDB `TelemetryRunner` contributes the full `IActionContext` to `ctx.actionContext` (narrow `ctx` to `WithTelemetry<RouterContext>` to read it): ```typescript myProcedure: publicProcedureWithTelemetry.input(schema).query(async ({ ctx, input }) => { const myCtx = ctx as WithTelemetry<RouterContext>; myCtx.actionContext.telemetry.properties.someDetail = 'value'; myCtx.actionContext.telemetry.measurements.resultCount = results.length; return results; }), ``` ## Naming Conventions ### Property & measurement names - **Default**: use **camelCase** — `authMethod`, `connectionMode`, `discoveryProviderId` - **Namespaced metadata**: when spreading a flat metadata object into telemetry (e.g., cluster metadata collected once and reused), use **prefix_camelCase** — `domainInfo_isAzure`, `serverInfo_version`. The prefix acts as a namespace. This pattern exists in `getClusterMetadata.ts` and is matched by dashboard queries. - **Never mix** within one prefix: `task_initializationCompleted` (mixed snake + camelCase) is wrong — use `task_initializationCompleted` → `taskInitializationCompleted` or `task_initialization_completed` consistently. - **Correlation IDs**: always use camelCase — `journeyCorrelationId`, `connectionCorrelationId`. ### Boolean properties **Always use the ternary pattern** — it makes the intent explicit: ```typescript // ✅ Correct — clear intent, consistent context.telemetry.properties.hasFolders = totalFolders > 0 ? 'true' : 'false'; context.telemetry.properties.isFirstConnection = isFirst ? 'true' : 'false'; // ❌ Avoid — works but less readable, can accidentally stringify non-booleans context.telemetry.properties.isWindows = isWindows.toString(); ``` ### Numbers: property or measurement? **Rule: if you'd ever want to compute P50, average, sum, or max over it, use a measurement.** Numbers stored as string properties can't be aggregated in analytics without casting. ```typescript // ✅ Measurement — enables P50(pageNumber), avg(itemCount) context.telemetry.measurements.pageNumber = input.pageNumber; context.telemetry.measurements.itemCount = items.length; // ❌ Wrong — forces dashboard to todouble(Properties.pageNumber) context.telemetry.properties.pageNumber = input.pageNumber.toString(); ``` **Exception**: numeric values used purely as categories (e.g., an error code used for grouping, not aggregation) can be properties. ## Properties vs Measurements | Use | Type | Example | | ------------------------- | ------------------------------------ | ------------------------------------------------ | | **Properties** (string) | Categorical data, flags, identifiers | `serverType`, `view`, `authMethod`, `hasFolders` | | **Measurements** (number) | Counts, sizes, durations, rates | `itemCount`, `loadTimeMs`, `retryAttempts` | ### Properties — what to capture ```typescript // Feature context — what variant/mode is being used context.telemetry.properties.view = 'connectionsView'; context.telemetry.properties.experience = node.experience.api; // 'vCore' | 'RU' context.telemetry.properties.authMethod = 'connectionString'; // Boolean flags — always use 'true'/'false' strings context.telemetry.properties.hasFolders = totalFolders > 0 ? 'true' : 'false'; context.telemetry.properties.isFirstConnection = isFirst ? 'true' : 'false'; // Classification of inputs or results context.telemetry.properties.connectionMode = 'documentdb'; // what was selected context.telemetry.properties.terminalType = 'PowerShell'; // what environment // Error classification beyond the auto-captured error context.telemetry.properties.connectionErrorType = 'auth'; // error category context.telemetry.properties.parseError = 'SyntaxError'; // specific error type // View identification — set for user-initiated or view-specific operations // Skip for background tasks, activation events, or cross-cutting concerns context.telemetry.properties.view = Views.ConnectionsView; ``` ### Measurements — what to capture ```typescript // Counts — how many items were involved context.telemetry.measurements.itemCount = results.length; context.telemetry.measurements.totalConnections = connections.length; context.telemetry.measurements.savedConnections = rootItems.length; // Requested vs returned — reveals pagination, limits, empty results context.telemetry.measurements.pageSize = input.pageSize; context.telemetry.measurements.documentCount = docs.length; // Sub-durations — where time is spent (the total is auto-captured) context.telemetry.measurements.loadTimeMs = Date.now() - startTime; context.telemetry.measurements.mainFileLoad = (loadEnd - loadStart) / 1000; // Attempts/retries context.telemetry.measurements.connectionViewActivationAttempts = attempt + 1; context.telemetry.measurements.cleanupIterations = iteration; // Breakdown counts — for multi-category results context.telemetry.measurements.createRecommendationCount = createCount; context.telemetry.measurements.dropRecommendationCount = dropCount; // Structural complexity — depth, nesting context.telemetry.measurements.maxFolderDepth = maxDepth; ``` ### Dynamic property/measurement names When the set of categories is data-driven, use a prefix: ```typescript context.telemetry.measurements[`${zonePrefix}_Connections`] = connectionsInZone; context.telemetry.measurements[`${zonePrefix}_Folders`] = foldersInZone; ``` ## Sub-step Telemetry Use nested `callWithTelemetryAndErrorHandling` inside a parent event to get fine-grained timing and success/failure per step: ```typescript await callWithTelemetryAndErrorHandling('connect', async (context) => { context.telemetry.properties.view = 'discoveryView'; // Sub-step: prompting — gets its own duration, result, errors await callWithTelemetryAndErrorHandling('connect.promptForCredentials', async (subCtx) => { subCtx.errorHandling.rethrow = true; // let parent see failures await wizard.prompt(); }); // Sub-step: metadata collection (fire-and-forget, non-blocking) void callWithTelemetryAndErrorHandling('connect.getmetadata', async (metaCtx) => { const metadata = await collectMetadata(); metaCtx.telemetry.properties = { ...metaCtx.telemetry.properties, ...metadata }; }); }); ``` **When to use sub-steps**: - Operation has distinct phases (prompt → connect → collect metadata) - You need per-phase duration/success data - A sub-step can fail independently without failing the parent **Naming**: use parent event as prefix: `connect.promptForCredentials`, `connect.getmetadata`, `connect.staticmetadata`. ## Correlation IDs — Linking Related Events ### journeyCorrelationId — user journey across commands Links a chain of user actions that form a logical journey (e.g., discover → select → connect → browse). Generated once at the start and propagated through tree items: ```typescript // Generate at the journey start (e.g., when a discovery tree root is created) this.journeyCorrelationId = randomUUID(); // Tree items carry it to children new MongoRUResourceItem(this, account, this.journeyCorrelationId); // Commands pick it up automatically via withTreeNodeCommandCorrelation/withCommandCorrelation // OR manually in a callWithTelemetryAndErrorHandling: if (this.journeyCorrelationId) { context.telemetry.properties.journeyCorrelationId = this.journeyCorrelationId; } ``` **Use `journeyCorrelationId` when**: a user flow spans multiple command invocations that should be analyzed together (discovery → connect → browse, folder operations in sequence). ### connectionCorrelationId — linking connection sub-events Links the `connect`, `connect.staticmetadata`, and `connect.getmetadata` events for a single connection attempt: ```typescript this.connectionCorrelationId = randomUUID(); void callWithTelemetryAndErrorHandling('connect.staticmetadata', async (context) => { context.telemetry.properties.connectionCorrelationId = this.connectionCorrelationId; // ...domain metadata }); void callWithTelemetryAndErrorHandling('connect.getmetadata', async (context) => { context.telemetry.properties.connectionCorrelationId = this.connectionCorrelationId; // ...server metadata }); ``` **Use `connectionCorrelationId` when**: an operation emits multiple independent events that must be joined for analysis (especially fire-and-forget sub-events). ### Session-scoped correlation — ongoing sessions For features where a user session produces many events (e.g., shell, REPL, interactive query editing), generate a session-scoped correlation ID and attach it to every event in that session. This enables aggregation (total commands per session, session duration as max-min timestamps): ```typescript const sessionId = randomUUID(); // Each action within the session carries the same ID context.telemetry.properties.shellSessionId = sessionId; context.telemetry.measurements.commandIndex = commandCount++; ``` ## Controlling Telemetry Behavior ### Suppressing noisy events ```typescript // High-frequency events (keystroke handlers, document change listeners) context.telemetry.suppressIfSuccessful = true; // only emit on error/cancel // Fully silent (internal bookkeeping) context.telemetry.suppressAll = true; ``` ### Error handling control ```typescript // Don't show error notification to user (background/internal operations) context.errorHandling.suppressDisplay = true; // Re-throw so the parent caller sees the error context.errorHandling.rethrow = true; // Don't offer "Report Issue" button context.errorHandling.suppressReportIssue = true; ``` ### Activation events Mark startup/initialization telemetry so it can be filtered out of active-usage analysis: ```typescript context.telemetry.properties.isActivationEvent = 'true'; ``` ## Common Mistakes to Avoid ### ❌ Don't measure duration manually — the framework does it The framework auto-captures `measurements.duration` for every `callWithTelemetryAndErrorHandling` event. Never add `Date.now()` tracking for the same purpose: ```typescript // ❌ Wrong — redundant manual timing const startTime = Date.now(); await callWithTelemetryAndErrorHandling('myEvent', async (context) => { await doWork(); context.telemetry.measurements.durationMs = Date.now() - startTime; // duplicates framework }); // ✅ Correct — framework captures duration automatically await callWithTelemetryAndErrorHandling('myEvent', async (context) => { await doWork(); // duration is auto-captured — no manual timing needed }); ``` **Exception**: sub-durations within a single event (e.g., "time spent on init vs. time on eval") are valid because they measure a _phase_, not the total. ### ❌ Don't create separate success/failure telemetry blocks Wrap the operation in a single `callWithTelemetryAndErrorHandling`. The framework sets `result=Succeeded` or `result=Failed` automatically: ```typescript // ❌ Wrong — two fire-and-forget blocks, error rethrow inside void is swallowed try {
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub