Skip to content

Instantly share code, notes, and snippets.

@kvenkatrajan
Last active March 30, 2026 19:10
Show Gist options
  • Select an option

  • Save kvenkatrajan/e5c64f5cc1d9b417159f51dcfc08fca1 to your computer and use it in GitHub Desktop.

Select an option

Save kvenkatrajan/e5c64f5cc1d9b417159f51dcfc08fca1 to your computer and use it in GitHub Desktop.
Wiring OpenTelemetry in a VS Code Extension — setup, data captured, and PII scrubbing

Wiring OpenTelemetry in a VS Code Extension

A practical guide to adding telemetry to your VS Code extension -- covering setup, data captured, and PII handling.


What does this help with?

@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.

Concrete use cases

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?

Why "simpler and safer" than raw OTel

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

What it does NOT help with

  • 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-telemetry is not the OpenTelemetry SDK itself. It's a Microsoft-maintained wrapper that sends custom events and exceptions to Application Insights. It uses the App Insights Track Event / Track Exception APIs -- not the OTel tracing pipeline (spans, traces, distributed context). If you need actual OTel distributed traces, use Approach 2.


Table of Contents

  1. Approach 1: @vscode/extension-telemetry (Recommended)
  2. Approach 2: Raw OpenTelemetry Node.js SDK
  3. Data Captured
  4. PII Data Scrubbing
  5. Key Considerations for Extensions

Approach 1: @vscode/extension-telemetry (Recommended)

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.

Install

npm install @vscode/extension-telemetry

Setup

import * 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,
  });
}

Sending Events

// 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',
  });
}

Custom Endpoints (Non-Azure)

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',
    },
  }
);

Approach 2: Raw OpenTelemetry Node.js SDK

For exporting to Jaeger, Zipkin, Grafana, or any OTLP-compatible collector instead of Application Insights.

Install

npm install @opentelemetry/api \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions

Setup

import * 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.


Data Captured

Auto-collected common properties (@vscode/extension-telemetry)

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

Custom data you send

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)

Raw OTel SDK data

With the raw SDK, you define all resource attributes and span attributes yourself. Nothing is auto-collected beyond what you configure.


PII Data Scrubbing

What @vscode/extension-telemetry does automatically

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

Custom regex-based scrubbing

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]' },
]);

Best practices

✅ Do:

  • Use sendTelemetryErrorEvent for errors (auto-sanitizes stacks)
  • Add replacementOptions for any domain-specific sensitive patterns
  • Collect the minimum data needed for your analysis
  • Include a telemetry.json file 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.telemetryLevel setting -- use the isTelemetryEnabled API

Raw OTel SDK -- no automatic scrubbing

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();
  }
}

Key Considerations for Extensions

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

Summary

@vscode/extension-telemetry Raw OTel SDK
Backend Azure Monitor / App Insights Any OTLP collector
Auto PII scrubbing ✅ Yes ❌ No -- DIY
User consent ✅ Automatic ⚠️ Manual check required
Common properties ✅ 12+ auto-collected ❌ You define everything
Setup complexity Low Medium
Best for Most extensions Custom observability stacks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment