Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/otel/common/otelConfig.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
export type OTelExporterType = 'otlp-grpc' | 'otlp-http' | 'console' | 'file';
7
8
export type OTelEnabledVia = 'envVar' | 'setting' | 'otlpEndpointEnvVar' | 'dbSpanExporterOnly' | 'disabled';
9
10
/** Default OTLP endpoint used when no env var or setting overrides it. */
11
export const DEFAULT_OTLP_ENDPOINT = 'http://localhost:4318';
12
13
export interface OTelConfig {
14
readonly enabled: boolean;
15
/** True when OTel was enabled via setting/env var, not just implied by dbSpanExporter. */
16
readonly enabledExplicitly: boolean;
17
/** How OTel was enabled — used for telemetry to track adoption channels. */
18
readonly enabledVia: OTelEnabledVia;
19
readonly exporterType: OTelExporterType;
20
readonly otlpEndpoint: string;
21
readonly otlpProtocol: 'grpc' | 'http';
22
readonly captureContent: boolean;
23
readonly fileExporterPath?: string;
24
readonly dbSpanExporter: boolean;
25
readonly logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error';
26
readonly httpInstrumentation: boolean;
27
readonly serviceName: string;
28
readonly serviceVersion: string;
29
readonly sessionId: string;
30
readonly resourceAttributes: Record<string, string>;
31
}
32
33
/**
34
* Parse `OTEL_RESOURCE_ATTRIBUTES` format: "key1=val1,key2=val2"
35
*/
36
function parseResourceAttributes(raw: string | undefined): Record<string, string> {
37
if (!raw) {
38
return {};
39
}
40
const result: Record<string, string> = {};
41
for (const pair of raw.split(',')) {
42
const eqIdx = pair.indexOf('=');
43
if (eqIdx > 0) {
44
const key = pair.substring(0, eqIdx).trim();
45
const value = pair.substring(eqIdx + 1).trim();
46
if (key) {
47
result[key] = value;
48
}
49
}
50
}
51
return result;
52
}
53
54
/**
55
* Parse and validate an OTLP endpoint URL.
56
* For gRPC: returns origin (scheme://host:port).
57
* For HTTP: returns full href.
58
*/
59
function parseOtlpEndpoint(raw: string | undefined, protocol: 'grpc' | 'http'): string | undefined {
60
if (!raw) {
61
return undefined;
62
}
63
const trimmed = raw.replace(/^["']|["']$/g, '');
64
try {
65
const url = new URL(trimmed);
66
return protocol === 'grpc' ? url.origin : url.href;
67
} catch {
68
return undefined;
69
}
70
}
71
72
export interface OTelConfigInput {
73
env: Record<string, string | undefined>;
74
settingEnabled?: boolean;
75
settingExporterType?: OTelExporterType;
76
settingOtlpEndpoint?: string;
77
settingCaptureContent?: boolean;
78
settingOutfile?: string;
79
settingDbSpanExporter?: boolean;
80
extensionVersion: string;
81
sessionId: string;
82
vscodeTelemetryLevel?: string;
83
}
84
85
/**
86
* Resolve OTel configuration with layered precedence:
87
* 1. COPILOT_OTEL_* env vars (highest)
88
* 2. OTEL_EXPORTER_OTLP_* standard env vars
89
* 3. VS Code settings
90
* 4. Defaults (lowest)
91
*/
92
export function resolveOTelConfig(input: OTelConfigInput): OTelConfig {
93
const { env } = input;
94
95
// Kill switch: respect VS Code telemetry level
96
if (input.vscodeTelemetryLevel === 'off') {
97
return createDisabledConfig(input);
98
}
99
100
// SQLite DB span exporter: setting > default(false)
101
const dbSpanExporter = input.settingDbSpanExporter ?? false;
102
103
// Determine if enabled: env > setting > dbSpanExporter > default(false)
104
// When dbSpanExporter is on, OTel must be enabled for the SDK pipeline to work.
105
const enabled = (envBool(env['COPILOT_OTEL_ENABLED'])
106
?? input.settingEnabled
107
?? (!!env['OTEL_EXPORTER_OTLP_ENDPOINT']))
108
|| dbSpanExporter;
109
110
// OTel was explicitly enabled if the user/env turned it on, not just dbSpanExporter
111
const enabledExplicitly = (envBool(env['COPILOT_OTEL_ENABLED'])
112
?? input.settingEnabled
113
?? (!!env['OTEL_EXPORTER_OTLP_ENDPOINT'])) === true;
114
115
if (!enabled) {
116
return createDisabledConfig(input);
117
}
118
119
// Determine how OTel was enabled for telemetry tracking
120
let enabledVia: OTelEnabledVia;
121
if (envBool(env['COPILOT_OTEL_ENABLED']) === true) {
122
enabledVia = 'envVar';
123
} else if (input.settingEnabled === true) {
124
enabledVia = 'setting';
125
} else if (!!env['OTEL_EXPORTER_OTLP_ENDPOINT']) {
126
enabledVia = 'otlpEndpointEnvVar';
127
} else {
128
enabledVia = 'dbSpanExporterOnly';
129
}
130
131
// Protocol: env > inferred from exporter type > default
132
const rawProtocol = env['OTEL_EXPORTER_OTLP_PROTOCOL'] ?? env['COPILOT_OTEL_PROTOCOL'];
133
const protocol: 'grpc' | 'http' = rawProtocol === 'grpc' ? 'grpc' : 'http';
134
135
// Endpoint: COPILOT_OTEL env > OTEL env > setting > default
136
const rawEndpoint = env['COPILOT_OTEL_ENDPOINT']
137
?? env['OTEL_EXPORTER_OTLP_ENDPOINT']
138
?? input.settingOtlpEndpoint
139
?? DEFAULT_OTLP_ENDPOINT;
140
const otlpEndpoint = parseOtlpEndpoint(rawEndpoint, protocol) ?? DEFAULT_OTLP_ENDPOINT;
141
142
// File exporter path
143
const fileExporterPath = env['COPILOT_OTEL_FILE_EXPORTER_PATH'] ?? input.settingOutfile;
144
145
// Exporter type
146
let exporterType: OTelExporterType;
147
if (fileExporterPath) {
148
exporterType = 'file';
149
} else if (input.settingExporterType) {
150
exporterType = input.settingExporterType;
151
} else {
152
exporterType = protocol === 'grpc' ? 'otlp-grpc' : 'otlp-http';
153
}
154
155
// Content capture
156
const captureContent = envBool(env['COPILOT_OTEL_CAPTURE_CONTENT'])
157
?? input.settingCaptureContent
158
?? false;
159
160
// Log level
161
const validLogLevels = new Set<OTelConfig['logLevel']>(['trace', 'debug', 'info', 'warn', 'error']);
162
const rawLogLevel = env['COPILOT_OTEL_LOG_LEVEL'];
163
const logLevel: OTelConfig['logLevel'] = rawLogLevel && validLogLevels.has(rawLogLevel as OTelConfig['logLevel'])
164
? rawLogLevel as OTelConfig['logLevel']
165
: 'info';
166
167
// HTTP instrumentation
168
const httpInstrumentation = envBool(env['COPILOT_OTEL_HTTP_INSTRUMENTATION']) ?? false;
169
170
// Service name
171
const serviceName = env['OTEL_SERVICE_NAME'] ?? 'copilot-chat';
172
173
// Resource attributes
174
const resourceAttributes = parseResourceAttributes(env['OTEL_RESOURCE_ATTRIBUTES']);
175
176
return Object.freeze({
177
enabled: true,
178
enabledExplicitly,
179
enabledVia,
180
exporterType,
181
otlpEndpoint,
182
otlpProtocol: protocol,
183
captureContent,
184
fileExporterPath,
185
dbSpanExporter,
186
logLevel,
187
httpInstrumentation,
188
serviceName,
189
serviceVersion: input.extensionVersion,
190
sessionId: input.sessionId,
191
resourceAttributes,
192
});
193
}
194
195
function createDisabledConfig(input: OTelConfigInput): OTelConfig {
196
return Object.freeze({
197
enabled: false,
198
enabledExplicitly: false,
199
enabledVia: 'disabled' as const,
200
exporterType: 'otlp-http' as const,
201
otlpEndpoint: '',
202
otlpProtocol: 'http' as const,
203
captureContent: false,
204
dbSpanExporter: false,
205
logLevel: 'info' as const,
206
httpInstrumentation: false,
207
serviceName: 'copilot-chat',
208
serviceVersion: input.extensionVersion,
209
sessionId: input.sessionId,
210
resourceAttributes: {},
211
});
212
}
213
214
function envBool(val: string | undefined): boolean | undefined {
215
if (val === undefined) {
216
return undefined;
217
}
218
return val === 'true' || val === '1';
219
}
220
221