A practical guide to adding telemetry to your VS Code extension -- covering setup, data captured, and PII handling.
@vscode/extension-telemetry answers "what are my users doing and what's breaking?" It's built for product analytics and error monitoring -- the 80% case for extension authors.
| Use Case | Example | Question it answers |
|---|---|---|
| Usage analytics | sendTelemetryEvent('commandUsed', { command: 'formatDocument', language: 'python' }) |
Which commands are popular? Which languages do users work in? |
| Error tracking | sendTelemetryErrorEvent('activationFailed', { reason: 'missingConfig' }) |
How often does activation fail? What's the top error? |
| Performance measurements | sendTelemetryEvent('indexComplete', {}, { durationMs: 4200, fileCount: 350 }) |
How long does indexing take on average? |
| Feature adoption | sendTelemetryEvent('settingChanged', { setting: 'experimentalFeatureX', value: 'enabled' }) |
What % of users enabled the new feature? |
| Environment insights | Auto-collected (OS, VS Code version, remote type, etc.) | Are most users on Windows or Mac? Desktop or web? |
| Benefit | How |
|---|---|
| 3 lines to set up | Just new TelemetryReporter(connString) -- no providers, exporters, processors |
| Consent is automatic | Respects telemetry.telemetryLevel -- zero code from you |
| PII scrubbed | Stacks, paths, usernames are sanitized automatically |
| Auto-disposed | Push to context.subscriptions and forget |
| 12+ properties free | OS, version, session ID, etc. -- no manual wiring |
- Tracing a request across multiple services (no distributed traces)
- Visualizing operation waterfalls (no spans/parent-child)
- Measuring latency of sub-operations within a flow
- Sending telemetry to non-App Insights backends
For those, use the Raw OpenTelemetry SDK (Approach 2 below).
Clarification:
@vscode/extension-telemetryis not the OpenTelemetry SDK itself. It's a Microsoft-maintained wrapper that sends custom events and exceptions to Application Insights. It uses the App InsightsTrack Event/Track ExceptionAPIs -- not the OTel tracing pipeline (spans, traces, distributed context). If you need actual OTel distributed traces, use Approach 2.
- Approach 1:
@vscode/extension-telemetry(Recommended) - Approach 2: Raw OpenTelemetry Node.js SDK
- Data Captured
- PII Data Scrubbing
- Key Considerations for Extensions
The official VS Code telemetry module maintained by Microsoft. It exports to Azure Monitor / Application Insights, automatically respects user consent, and provides common properties out of the box.
npm install @vscode/extension-telemetryimport * as vscode from 'vscode';
import TelemetryReporter from '@vscode/extension-telemetry';
// The Application Insights connection string (not sensitive -- safe to hardcode)
const connectionString = '<your-app-insights-connection-string>';
let reporter: TelemetryReporter;
export function activate(context: vscode.ExtensionContext) {
// Create the reporter -- it automatically checks telemetry.telemetryLevel
reporter = new TelemetryReporter(connectionString);
// Register for auto-disposal (flushes events on deactivation)
context.subscriptions.push(reporter);
// Send a simple event
reporter.sendTelemetryEvent('extensionActivated', {
version: context.extension.packageJSON.version,
});
}// General event with string properties and numeric measurements
reporter.sendTelemetryEvent(
'commandExecuted',
{ command: 'myExt.refactor', language: 'typescript' },
{ durationMs: 342 }
);
// Error event (stack traces are auto-sanitized)
reporter.sendTelemetryErrorEvent(
'commandFailed',
{ command: 'myExt.refactor', error: error.message },
{ retryCount: 2 }
);
// Exception (sends to Application Insights exceptions table)
try {
riskyOperation();
} catch (err) {
reporter.sendTelemetryException(err as Error, {
context: 'refactor-operation',
});
}const reporter = new TelemetryReporter(
connectionString,
undefined, // replacementOptions
undefined, // initializationOptions
undefined, // customFetch
{
endpointUrl: 'https://my-telemetry-endpoint.example.com/collect',
commonProperties: {
'custom.environment': 'production',
},
tagOverrides: {
'ai.cloud.roleInstance': 'REDACTED',
},
}
);For exporting to Jaeger, Zipkin, Grafana, or any OTLP-compatible collector instead of Application Insights.
npm install @opentelemetry/api \
@opentelemetry/sdk-trace-node \
@opentelemetry/sdk-trace-base \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/semantic-conventionsimport * as vscode from 'vscode';
import { trace, SpanStatusCode } from '@opentelemetry/api';
import {
NodeTracerProvider,
BatchSpanProcessor,
} from '@opentelemetry/sdk-trace-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
let provider: NodeTracerProvider;
export function activate(context: vscode.ExtensionContext) {
// 1. Respect VS Code telemetry settings
if (!vscode.env.isTelemetryEnabled) {
return;
}
// 2. Configure the tracer provider
provider = new NodeTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-vscode-extension',
[ATTR_SERVICE_VERSION]: context.extension.packageJSON.version,
'vscode.session.id': vscode.env.sessionId,
'vscode.machine.id': vscode.env.machineId,
}),
spanProcessors: [
new BatchSpanProcessor(
new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces',
})
),
],
});
provider.register();
// 3. React to telemetry setting changes
context.subscriptions.push(
vscode.env.onDidChangeTelemetryEnabled((enabled) => {
if (!enabled) {
provider?.shutdown();
}
})
);
// 4. Instrument your commands
const tracer = trace.getTracer('my-extension');
context.subscriptions.push(
vscode.commands.registerCommand('myExt.doWork', async () => {
const span = tracer.startSpan('doWork');
try {
// ... your logic ...
span.setAttribute('result', 'success');
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
} finally {
span.end();
}
})
);
}
export async function deactivate() {
// Flush all pending spans before the extension shuts down
await provider?.shutdown();
}
⚠️ Important: With the raw OTel SDK, you are responsible for all PII scrubbing yourself. There is no automatic sanitization.
Every event sent through the telemetry module automatically includes:
| Property | Key | Description |
|---|---|---|
| Extension Name | common.extname |
Your extension's identifier |
| Extension Version | common.extversion |
Your extension's semver version |
| Machine ID | common.vscodemachineid |
Hashed machine identifier (pseudonymized) |
| Session ID | common.vscodesessionid |
Ephemeral per-session identifier |
| VS Code Version | common.vscodeversion |
e.g., 1.96.0 |
| VS Code Commit | common.vscodecommithash |
Git commit hash of the VS Code build |
| OS | common.os |
e.g., Windows_NT, Darwin, Linux |
| Platform Version | common.platformversion |
OS version string |
| Product | common.product |
desktop, github.dev, or codespaces |
| UI Kind | common.uikind |
Desktop or Web |
| Remote Name | common.remotename |
ssh, wsl, docker, or other |
| Node Architecture | common.nodeArch |
x64, arm64, or web |
In addition to the auto-collected properties, you control what you send:
- String properties -- e.g., command names, feature flags, language IDs
- Numeric measurements -- e.g., duration, count, file size
- Exceptions -- error objects (stack traces are sanitized)
With the raw SDK, you define all resource attributes and span attributes yourself. Nothing is auto-collected beyond what you configure.
| Data Type | Treatment |
|---|---|
| File paths in error stacks | Sanitized / stripped |
| Usernames in paths | Sanitized |
| URLs in error messages | Sanitized |
| Machine ID | Hashed (not a hardware serial or IP address) |
| Session ID | Ephemeral -- regenerated each VS Code session |
| IP Address | Not collected by the extension module |
You can add your own redaction rules via replacementOptions:
const reporter = new TelemetryReporter(connectionString, [
// Redact Unix-style home directories
{ lookup: /\/Users\/[^/]+/g, replacementString: '[REDACTED_USER]' },
// Redact Windows-style user paths
{ lookup: /C:\\Users\\[^\\]+/g, replacementString: '[REDACTED_USER]' },
// Strip API keys entirely (no replacement = removed)
{ lookup: /api[_-]?key=\w+/gi },
// Redact email addresses
{ lookup: /[\w.-]+@[\w.-]+\.\w+/g, replacementString: '[REDACTED_EMAIL]' },
]);✅ Do:
- Use
sendTelemetryErrorEventfor errors (auto-sanitizes stacks) - Add
replacementOptionsfor any domain-specific sensitive patterns - Collect the minimum data needed for your analysis
- Include a
telemetry.jsonfile in your extension root for transparency
❌ Don't:
- Include raw file paths, usernames, or emails in event properties
- Send user-generated content or input text
- Log authentication tokens or credentials
- Rely solely on
telemetry.telemetryLevelsetting -- use theisTelemetryEnabledAPI
If you use the OpenTelemetry SDK directly (Approach 2), there is no built-in PII scrubbing. You must sanitize all span attributes and event data before export. Consider writing a custom SpanProcessor that filters sensitive data:
import { SpanProcessor, ReadableSpan, Span } from '@opentelemetry/sdk-trace-base';
class PiiScrubber implements SpanProcessor {
private scrubPatterns = [
{ pattern: /\/Users\/[^/]+/g, replacement: '[REDACTED]' },
{ pattern: /C:\\Users\\[^\\]+/g, replacement: '[REDACTED]' },
{ pattern: /[\w.-]+@[\w.-]+\.\w+/g, replacement: '[EMAIL]' },
];
onStart(span: Span): void {}
onEnd(span: ReadableSpan): void {}
forceFlush(): Promise<void> {
return Promise.resolve();
}
shutdown(): Promise<void> {
return Promise.resolve();
}
}| Concern | How to handle |
|---|---|
| User consent | Always check vscode.env.isTelemetryEnabled and listen to onDidChangeTelemetryEnabled |
| Lifecycle | Initialize in activate(), shut down/dispose in deactivate() |
| No process signals | Extensions don't receive SIGTERM -- use deactivate() and context.subscriptions for cleanup |
| Bundling | If using webpack/esbuild, ensure OTel packages are properly bundled or externalized |
| Web extensions | NodeTracerProvider won't work -- use WebTracerProvider from @opentelemetry/sdk-trace-web |
| Transparency | Ship a telemetry.json file so users can audit via code --telemetry |
@vscode/extension-telemetry |
Raw OTel SDK | |
|---|---|---|
| Backend | Azure Monitor / App Insights | Any OTLP collector |
| Auto PII scrubbing | ✅ Yes | ❌ No -- DIY |
| User consent | ✅ Automatic | |
| Common properties | ✅ 12+ auto-collected | ❌ You define everything |
| Setup complexity | Low | Medium |
| Best for | Most extensions | Custom observability stacks |