Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions extensions/copilot/docs/monitoring/agent_monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Open **Settings** (`Ctrl+,`) and add:
}
```

> **Note:** You can also use environment variables instead of VS Code settings (see [Configuration](#configuration)). Precedence is **enterprise policy > environment variables > settings**.
> **Note:** You can also use environment variables instead of VS Code settings (see [Configuration](#configuration)). Applied policy is included in settings values, but environment variables can still override those values in this extension. See the [activation limitations](#activation).

### 3. Generate Telemetry

Expand Down Expand Up @@ -82,7 +82,11 @@ Open **Settings** (`Ctrl+,`) and search for `copilot otel`:

### Environment Variables

Environment variables take precedence over VS Code settings, and **enterprise managed settings (policy) take precedence over both** — admins can centrally mandate any `github.copilot.chat.otel.*` value.
Environment variables retain their existing precedence. When enterprise OTel configuration is
recognized through the application-scoped policy defaults, the entire Copilot OTel settings
block comes from those policy values and schema defaults. Personal `settings.json` values are
not used to fill omitted fields: headers and resource attributes default to empty maps, not
the user's maps. Other VS Code settings are unaffected.
Comment thread
rwoll marked this conversation as resolved.

| Variable | Default | Description |
|---|---|---|
Expand All @@ -102,9 +106,38 @@ Environment variables take precedence over VS Code settings, and **enterprise ma

### Activation

When late enterprise OTel settings turn on external export after Copilot's telemetry service

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is all this text necessary or could it be more concise?

started without it, Copilot can restart the extension hosts for that window to recover. It shows
a progress notification before requesting the restart; that notice clears automatically when
the host restarts or the attempt ends. Successful recovery is logged without another toast.
Restarting also interrupts other extensions in the window. If the restart is unavailable,
vetoed, or fails to apply the settings,
a warning offers **Reload Window** instead. User changes and policy withdrawal remain
opt-in reloads. Exporter behavior and environment-variable precedence are unchanged.
It uses changes to the application-scoped, policy-backed configuration defaults as a recovery
signal, without a new API. Normal personal settings changes do not change those defaults.
If a recognizable enterprise OTel block was already present at initialization, later changes
only offer a reload, including enabling a previously disabled managed configuration.
Automatic recovery additionally requires policy-enabled OTLP export targeting the collector in
those defaults. Disabled and DB-only pipelines, unrelated partial policies, and configurations
still redirected by environment variables to a different collector or file do not qualify.
Policy edits indistinguishable from schema defaults cannot be identified as new policy and retain
the opt-in reload behavior. Conflicting environment variables can still prevent recovery, and this
does not enforce precedence over environment variables or guarantee telemetry produced before
the restart. The default-value signal cannot distinguish a policy consisting entirely of
schema-default values from no policy.

There is no periodic polling or restart loop. A startup check and configuration events trigger
checks, coalesced by a 500 ms debounce. At most one automatic off-to-on recovery is attempted per
workspace and editor session, identified by `vscode.env.sessionId`. The attempt remains recorded
even after success or failure. Later policy updates only offer a reload, deduplicated while stale.
If policy first arrives after a later sign-in, that single recovery can happen then rather than
immediately at launch. A one-off 15-second grace period allows a requested restart to finish
before a still-running host shows the reload fallback.

OTel is **off by default** with zero overhead. It activates when:

- enterprise policy enables it (managed `telemetry.enabled` or a managed endpoint), or
- an applied enterprise policy makes `github.copilot.chat.otel.enabled` true, or
- `COPILOT_OTEL_ENABLED=true`, or
- `OTEL_EXPORTER_OTLP_ENDPOINT` is set, or
- `github.copilot.chat.otel.enabled` is `true`, or
Expand Down
18 changes: 9 additions & 9 deletions extensions/copilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5308,7 +5308,7 @@
"policyReference": {
"name": "CopilotOtelEnabled"
},
"markdownDescription": "Enable OpenTelemetry trace/metric/log emission for Copilot Chat operations. Precedence: enterprise policy > env var `COPILOT_OTEL_ENABLED` > user setting. Requires window reload.",
"markdownDescription": "Enable OpenTelemetry trace/metric/log emission for Copilot Chat operations. Recognized enterprise OTel settings replace personal OTel settings, but env var `COPILOT_OTEL_ENABLED` can still override enablement. Requires window reload.",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are documentation-only edits. Environment variables can already override or supplement managed OTel settings; this PR does not introduce that behavior. Full enterprise-over-environment enforcement across the resolver, exporters, and embedded SDK is separate work.

"tags": [
"advanced"
]
Expand All @@ -5326,7 +5326,7 @@
"policyReference": {
"name": "CopilotOtelProtocol"
},
"markdownDescription": "OTel exporter type for Copilot Chat telemetry. Configurable in user settings or managed by enterprise policy (policy takes precedence). Requires window reload.",
"markdownDescription": "OTel exporter type for Copilot Chat telemetry. Enterprise-managed OTel settings replace personal OTel settings. A configured output file, including env var `COPILOT_OTEL_FILE_EXPORTER_PATH`, can still select file export. Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5344,7 +5344,7 @@
"policyReference": {
"name": "CopilotOtelOtlpProtocol"
},
"markdownDescription": "OTLP wire protocol for Copilot Chat OTel data, mirroring `OTEL_EXPORTER_OTLP_PROTOCOL`. `http/protobuf` selects the protobuf-over-HTTP exporter; the default (empty) uses `http/json`. Precedence: enterprise policy > env var > user setting. Requires window reload.",
"markdownDescription": "OTLP wire protocol for Copilot Chat OTel data, mirroring `OTEL_EXPORTER_OTLP_PROTOCOL`. `http/protobuf` selects the protobuf-over-HTTP exporter; the default (empty) uses `http/json`. Env vars `OTEL_EXPORTER_OTLP_PROTOCOL`, then `COPILOT_OTEL_PROTOCOL`, take precedence over enterprise-managed or personal settings. Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5356,7 +5356,7 @@
"policyReference": {
"name": "CopilotOtelEndpoint"
},
"markdownDescription": "OTLP collector endpoint URL for Copilot Chat OTel data. Precedence: enterprise policy > env var `OTEL_EXPORTER_OTLP_ENDPOINT` > user setting. Requires window reload.",
"markdownDescription": "OTLP collector endpoint URL for Copilot Chat OTel data. Env vars `COPILOT_OTEL_ENDPOINT`, then `OTEL_EXPORTER_OTLP_ENDPOINT`, take precedence over enterprise-managed or personal settings. Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5368,7 +5368,7 @@
"policyReference": {
"name": "CopilotOtelCaptureContent"
},
"markdownDescription": "Capture input/output messages, system instructions, and tool definitions in OTel telemetry. **Contains potentially sensitive data.** Precedence: enterprise policy > env var `COPILOT_OTEL_CAPTURE_CONTENT` > user setting. Requires window reload.",
"markdownDescription": "Capture input/output messages, system instructions, and tool definitions in OTel telemetry. **Contains potentially sensitive data.** Env var `COPILOT_OTEL_CAPTURE_CONTENT` takes precedence over enterprise-managed or personal settings. Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5380,7 +5380,7 @@
"policyReference": {
"name": "CopilotOtelServiceName"
},
"markdownDescription": "OTel `service.name` resource attribute for Copilot Chat OTel data. Configurable in user settings only. Env var `OTEL_SERVICE_NAME` takes precedence over the setting; enterprise policy takes precedence over both. Requires window reload.",
"markdownDescription": "OTel `service.name` resource attribute for Copilot Chat OTel data. Env var `OTEL_SERVICE_NAME` takes precedence over enterprise-managed or personal settings. Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5395,7 +5395,7 @@
"policyReference": {
"name": "CopilotOtelResourceAttributes"
},
"markdownDescription": "Additional OTel resource attributes for Copilot Chat OTel data, as a `{ \"key\": \"value\" }` map. Configurable in user settings only. Merged per-key with `OTEL_RESOURCE_ATTRIBUTES` env (env wins over the setting); enterprise policy wins over both. Requires window reload.",
"markdownDescription": "Additional OTel resource attributes for Copilot Chat OTel data, as a `{ \"key\": \"value\" }` map. When enterprise OTel settings apply, personal OTel settings are ignored. Merged per-key with env var `OTEL_RESOURCE_ATTRIBUTES` (env values take precedence, including over enterprise-managed values). Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5410,7 +5410,7 @@
"policyReference": {
"name": "CopilotOtelHeaders"
},
"markdownDescription": "Extra OTLP exporter headers (e.g. auth tokens) for Copilot Chat OTel data, as a `{ \"key\": \"value\" }` map. Applied directly to the OTLP exporter, not via environment variables. Configurable in user settings only. Merged per-key with `OTEL_EXPORTER_OTLP_HEADERS` env (env wins over the setting); enterprise policy wins over both. **Contains potentially sensitive credentials.** Requires window reload.",
"markdownDescription": "Extra OTLP exporter headers (e.g. auth tokens) for Copilot Chat OTel data, as a `{ \"key\": \"value\" }` map. Applied directly to the OTLP exporter, not via environment variables. When enterprise OTel settings apply, personal OTel settings are ignored. Merged per-key with env var `OTEL_EXPORTER_OTLP_HEADERS` (env values take precedence, including over enterprise-managed values). **Contains potentially sensitive credentials.** Requires window reload.",
"tags": [
"advanced"
]
Expand All @@ -5432,7 +5432,7 @@
"policyReference": {
"name": "CopilotOtelOutfile"
},
"markdownDescription": "File path for file-based OTel exporter output (JSON-lines). When set, overrides exporter type to `file`. Configurable in user settings or managed by enterprise policy (policy takes precedence). Requires window reload.",
"markdownDescription": "File path for file-based OTel exporter output (JSON-lines). When set, overrides exporter type to `file`. Enterprise-managed OTel settings replace personal OTel settings, but env var `COPILOT_OTEL_FILE_EXPORTER_PATH` can still override the output path. Requires window reload.",
"tags": [
"advanced"
]
Expand Down
35 changes: 6 additions & 29 deletions extensions/copilot/src/extension/extension/vscode-node/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import { IFetcherService } from '../../../platform/networking/common/fetcherServ
import { IToolDeferralService } from '../../../platform/networking/common/toolDeferralService';
import { ChatWebSocketManager, IChatWebSocketManager } from '../../../platform/networking/node/chatWebSocketManager';
import { FetcherService } from '../../../platform/networking/vscode-node/fetcherServiceImpl';
import { resolveOTelConfig } from '../../../platform/otel/common/otelConfig';
import { IOTelConfigResolver } from '../../../platform/otel/common/otelConfigResolution';
import { IOTelService } from '../../../platform/otel/common/otelService';
import { InMemoryOTelService } from '../../../platform/otel/node/inMemoryOTelService';
import { IOTelSqliteStore, OTelSqliteStore } from '../../../platform/otel/node/sqlite/otelSqliteStore';
Expand Down Expand Up @@ -118,6 +118,7 @@ import { ILinkifyService, LinkifyService } from '../../linkify/common/linkifySer
import { DebugCommandToConfigConverter, IDebugCommandToConfigConverter } from '../../onboardDebug/node/commandToConfigConverter';
import { DebuggableCommandIdentifier, IDebuggableCommandIdentifier } from '../../onboardDebug/node/debuggableCommandIdentifier';
import { ILanguageToolsProvider, LanguageToolsProvider } from '../../onboardDebug/node/languageToolsProvider';
import { VSCodeOTelConfigResolver } from '../../otel/vscode-node/otelConfigResolver';
import { IPowerService } from '../../power/common/powerService';
import { PowerService } from '../../power/vscode-node/powerService';
import { ChatMLFetcherImpl } from '../../prompt/node/chatMLFetcher';
Expand Down Expand Up @@ -293,34 +294,10 @@ export function registerServices(builder: IInstantiationServiceBuilder, extensio
const otelSqliteStore = new OTelSqliteStore(otelDbPath);
builder.define(IOTelSqliteStore, otelSqliteStore);

// OTel service — resolve config from env + settings, create appropriate impl
const otelSettings = workspace.getConfiguration('github.copilot.chat.otel');
const policyValue = <T>(key: string): T | undefined => (otelSettings.inspect<T>(key) as { policyValue?: T } | undefined)?.policyValue;
const otelConfig = resolveOTelConfig({
env: process.env,
settingEnabled: otelSettings.get<boolean>('enabled'),
settingExporterType: otelSettings.get<'otlp-grpc' | 'otlp-http' | 'console' | 'file'>('exporterType'),
settingOtlpEndpoint: otelSettings.get<string>('otlpEndpoint'),
settingCaptureContent: otelSettings.get<boolean>('captureContent'),
settingMaxAttributeSizeChars: otelSettings.get<number>('maxAttributeSizeChars'),
settingOutfile: otelSettings.get<string>('outfile') || undefined,
settingDbSpanExporter: otelSettings.get<boolean>('dbSpanExporter.enabled'),
settingProtocol: otelSettings.get<string>('protocol') || undefined,
policyEnabled: policyValue<boolean>('enabled'),
policyExporterType: policyValue<'otlp-grpc' | 'otlp-http' | 'console' | 'file'>('exporterType'),
policyOtlpEndpoint: policyValue<string>('otlpEndpoint'),
policyCaptureContent: policyValue<boolean>('captureContent'),
policyOutfile: policyValue<string>('outfile'),
policyProtocol: policyValue<string>('protocol'),
settingServiceName: otelSettings.get<string>('serviceName') || undefined,
policyServiceName: policyValue<string>('serviceName'),
settingResourceAttributes: otelSettings.get<Record<string, string>>('resourceAttributes'),
policyResourceAttributes: policyValue<Record<string, string>>('resourceAttributes'),
settingHeaders: otelSettings.get<Record<string, string>>('headers'),
policyHeaders: policyValue<Record<string, string>>('headers'),
extensionVersion: extensionContext.extension.packageJSON.version ?? '0.0.0',
sessionId: env.sessionId,
});
// Keep the exact resolution the service uses so late policy can be detected.
const otelConfigResolver = new VSCodeOTelConfigResolver(process.env, extensionContext.extension.packageJSON.version ?? '0.0.0', env.sessionId);
builder.define(IOTelConfigResolver, otelConfigResolver);
const otelConfig = otelConfigResolver.activeResolution.config;
if (otelConfig.enabled) {
// Dynamic import to avoid loading OTel SDK when disabled
const { NodeOTelService } = require('../../../platform/otel/node/otelServiceImpl') as typeof import('../../../platform/otel/node/otelServiceImpl');
Expand Down
Loading
Loading