Path: blob/main/extensions/copilot/src/extension/log/vscode-node/loggingActions.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dns from 'dns';6import * as http from 'http';7import * as https from 'https';8import * as os from 'os';9import * as path from 'path';10import * as tls from 'tls';11import * as util from 'util';12import * as vscode from 'vscode';1314import { IAuthenticationService } from '../../../platform/authentication/common/authentication';15import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';16import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient';17import { IEnvService, isScenarioAutomation } from '../../../platform/env/common/envService';18import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';19import { collectErrorMessages, collectSingleLineErrorMessage, ILogService, sanitizeNetworkErrorForTelemetry } from '../../../platform/log/common/logService';20import { outputChannel } from '../../../platform/log/vscode/outputChannelLogTarget';21import { FetchEvent, IFetcherService } from '../../../platform/networking/common/fetcherService';22import { IFetcher, userAgentLibraryHeader } from '../../../platform/networking/common/networking';23import { NodeFetcher } from '../../../platform/networking/node/nodeFetcher';24import { NodeFetchFetcher } from '../../../platform/networking/node/nodeFetchFetcher';25import { ElectronFetcher } from '../../../platform/networking/vscode-node/electronFetcher';26import { getShadowedConfig } from '../../../platform/networking/vscode-node/fetcherServiceImpl';27import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';2829import { shuffle } from '../../../util/vs/base/common/arrays';30import { timeout } from '../../../util/vs/base/common/async';31import { Disposable, MutableDisposable } from '../../../util/vs/base/common/lifecycle';32import { generateUuid } from '../../../util/vs/base/common/uuid';33import { IInstantiationService, ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';34import { EXTENSION_ID } from '../../common/constants';3536interface ProxyAgentLog {37trace(message: string, ...args: any[]): void;38debug(message: string, ...args: any[]): void;39info(message: string, ...args: any[]): void;40warn(message: string, ...args: any[]): void;41error(message: string | Error, ...args: any[]): void;42}4344interface ProxyAgentParams {45log: ProxyAgentLog;46loadSystemCertificatesFromNode: () => boolean | undefined;47}4849interface ProxyAgent {50loadSystemCertificates?(params: ProxyAgentParams): Promise<string[]>;51resolveProxyURL?(url: string): Promise<string | undefined>;52}5354export class LoggingActionsContrib {55constructor(56@IVSCodeExtensionContext private readonly _context: IVSCodeExtensionContext,57@IEnvService private envService: IEnvService,58@IConfigurationService private readonly configurationService: IConfigurationService,59@IExperimentationService private readonly experimentationService: IExperimentationService,60@IAuthenticationService private readonly authService: IAuthenticationService,61@ICAPIClientService private readonly capiClientService: ICAPIClientService,62@IFetcherService private readonly fetcherService: IFetcherService,63@ILogService private logService: ILogService,64) {65const collectDiagnostics = async () => {66const document = await vscode.workspace.openTextDocument({ language: 'markdown' });67const editor = await vscode.window.showTextDocument(document);68const electronConfig = getShadowedConfig<boolean>(this.configurationService, this.experimentationService, ConfigKey.Shared.DebugUseElectronFetcher, ConfigKey.TeamInternal.DebugExpUseElectronFetcher);69const nodeConfig = getShadowedConfig<boolean>(this.configurationService, this.experimentationService, ConfigKey.Shared.DebugUseNodeFetcher, ConfigKey.TeamInternal.DebugExpUseNodeFetcher);70const nodeFetchConfig = getShadowedConfig<boolean>(this.configurationService, this.experimentationService, ConfigKey.Shared.DebugUseNodeFetchFetcher, ConfigKey.TeamInternal.DebugExpUseNodeFetchFetcher);71const ext = vscode.extensions.getExtension(EXTENSION_ID);72const product = require(path.join(vscode.env.appRoot, 'product.json'));73await appendText(editor, `## GitHub Copilot Chat7475- Extension: ${this.envService.getVersion()} (${this.envService.getBuildType()})76- VS Code: ${vscode.version} (${product.commit || 'out-of-source'})77- OS: ${os.platform()} ${os.release()} ${os.arch()}${vscode.env.remoteName ? `78- Remote Name: ${vscode.env.remoteName}` : ''}${vscode.env.remoteName && ext ? `79- Extension Kind: ${vscode.ExtensionKind[ext.extensionKind]}` : ''}80- GitHub Account: ${this.authService.anyGitHubSession?.account.label || 'Signed Out'}8182## Network8384User Settings:85\`\`\`json${getNetworkSettings()}86"github.copilot.advanced.debug.useElectronFetcher": ${electronConfig},87"github.copilot.advanced.debug.useNodeFetcher": ${nodeConfig},88"github.copilot.advanced.debug.useNodeFetchFetcher": ${nodeFetchConfig}89\`\`\`${getProxyEnvVariables()}90`);91const proxyAgent = loadVSCodeModule<ProxyAgent>('@vscode/proxy-agent');92const loadSystemCertificatesFromNode = this.configurationService.getNonExtensionConfig<boolean>('http.systemCertificatesNode');93const osCertificates = proxyAgent?.loadSystemCertificates ? await loadSystemCertificates(proxyAgent.loadSystemCertificates, loadSystemCertificatesFromNode, this.logService) : undefined;94const urls = [95this.capiClientService.dotcomAPIURL,96this.capiClientService.capiPingURL,97this.capiClientService.proxyBaseURL + '/_ping',98];99const isGHEnterprise = this.capiClientService.dotcomAPIURL !== 'https://api.github.com';100const timeoutSeconds = 10;101const electronFetcher = ElectronFetcher.create(this.envService);102const electronCurrent = !!electronFetcher && electronConfig;103const nodeCurrent = !electronCurrent && nodeConfig;104const nodeFetchCurrent = !electronCurrent && !nodeCurrent && nodeFetchConfig;105const nodeCurrentFallback = !electronCurrent && !nodeFetchCurrent;106const activeFetcher = this.fetcherService.getUserAgentLibrary();107const nodeFetcher = new NodeFetcher(this.envService);108const fetchers = {109['Electron fetch']: {110fetcher: electronFetcher,111current: electronCurrent,112},113['Node.js https']: {114fetcher: nodeFetcher,115current: nodeCurrent || nodeCurrentFallback,116},117['Node.js fetch']: {118fetcher: new NodeFetchFetcher(this.envService),119current: nodeFetchCurrent,120},121};122const dnsLookup = util.promisify(dns.lookup);123for (const url of urls) {124const authHeaders = await this.getAuthHeaders(isGHEnterprise, url);125const host = new URL(url).hostname;126await appendText(editor, `\nConnecting to ${url}:\n`);127for (const family of [4, 6]) {128await appendText(editor, `- DNS ipv${family} Lookup: `);129const start = Date.now();130try {131const dnsResult = await Promise.race([dnsLookup(host, { family }), timeout(timeoutSeconds * 1000)]);132if (dnsResult) {133await appendText(editor, `${dnsResult.address} (${Date.now() - start} ms)\n`);134} else {135await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);136}137} catch (err) {138await appendText(editor, `Error (${Date.now() - start} ms): ${err?.message}\n`);139}140}141let probeProxyURL: string | undefined;142if (proxyAgent?.resolveProxyURL) {143await appendText(editor, `- Proxy URL: `);144const start = Date.now();145try {146const proxyURL = await Promise.race([proxyAgent.resolveProxyURL(url), timeoutAfter(timeoutSeconds * 1000)]);147if (proxyURL === 'timeout') {148await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);149} else {150await appendText(editor, `${proxyURL || 'None'} (${Date.now() - start} ms)\n`);151probeProxyURL = proxyURL;152}153} catch (err) {154await appendText(editor, `Error (${Date.now() - start} ms): ${err?.message}\n`);155}156}157if (proxyAgent?.loadSystemCertificates && probeProxyURL?.startsWith('https:')) {158const tlsOrig: typeof tls | undefined = (tls as any).__vscodeOriginal;159if (tlsOrig) {160await appendText(editor, `- Proxy TLS: `);161if (!osCertificates) {162await appendText(editor, `(failed to load system certificates) `);163}164const start = Date.now();165try {166const result = await Promise.race([tlsConnect(tlsOrig, probeProxyURL, [...tls.rootCertificates, ...(osCertificates || [])]), timeout(timeoutSeconds * 1000)]);167if (result) {168await appendText(editor, `${result} (${Date.now() - start} ms)\n`);169} else {170await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);171}172} catch (err) {173await appendText(editor, `Error (${Date.now() - start} ms): ${err?.message}\n`);174}175}176}177if (probeProxyURL) {178const httpx: typeof https | typeof http | undefined = probeProxyURL.startsWith('https:') ? (https as any).__vscodeOriginal : (http as any).__vscodeOriginal;179if (httpx) {180await appendText(editor, `- Proxy Connection: `);181const start = Date.now();182try {183const result = await Promise.race([proxyConnect(httpx, probeProxyURL, url), timeout(timeoutSeconds * 1000)]);184if (result) {185const headers = Object.keys(result.headers).map(header => `\n ${header}: ${result.headers[header]}`);186const text = `${result.statusCode} ${result.statusMessage}${headers.join('')}`;187await appendText(editor, `${text} (${Date.now() - start} ms)\n`);188} else {189await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);190}191} catch (err) {192await appendText(editor, `Error (${Date.now() - start} ms): ${err?.message}\n`);193}194}195}196for (const [name, fetcher] of Object.entries(fetchers)) {197await appendText(editor, `- ${name}${fetcher.current ? ' (configured)' : fetcher.fetcher?.getUserAgentLibrary() === activeFetcher ? ' (active)' : ''}: `);198if (fetcher.fetcher) {199const start = Date.now();200try {201const response = await Promise.race([fetcher.fetcher.fetch(url, { headers: authHeaders, callSite: 'diagnostics-fetcher-probe' }), timeout(timeoutSeconds * 1000)]);202if (response) {203await appendText(editor, `HTTP ${response.status} (${Date.now() - start} ms)\n`);204} else {205await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);206}207} catch (err) {208await appendText(editor, `Error (${Date.now() - start} ms): ${collectErrorMessages(err)}\n`);209}210} else {211await appendText(editor, 'Unavailable\n');212}213}214}215216const currentFetcher = Object.values(fetchers).find(fetcher => fetcher.current)?.fetcher || nodeFetcher;217const secondaryUrls = [218{ url: 'https://mobile.events.data.microsoft.com', fetcher: currentFetcher },219{ url: 'https://dc.services.visualstudio.com', fetcher: currentFetcher },220{ url: 'https://copilot-telemetry.githubusercontent.com/_ping', fetcher: nodeFetcher },221{ url: vscode.Uri.parse(this.capiClientService.copilotTelemetryURL).with({ path: '/_ping' }).toString(), fetcher: nodeFetcher },222{ url: 'https://default.exp-tas.com', fetcher: nodeFetcher },223];224await appendText(editor, `\n`);225for (const { url, fetcher } of secondaryUrls) {226const authHeaders = await this.getAuthHeaders(isGHEnterprise, url);227await appendText(editor, `Connecting to ${url}: `);228const start = Date.now();229try {230const response = await Promise.race([fetcher.fetch(url, { headers: authHeaders, callSite: 'diagnostics-secondary-probe' }), timeout(timeoutSeconds * 1000)]);231if (response) {232await appendText(editor, `HTTP ${response.status} (${Date.now() - start} ms)\n`);233} else {234await appendText(editor, `timed out after ${timeoutSeconds} seconds\n`);235}236} catch (err) {237await appendText(editor, `Error (${Date.now() - start} ms): ${collectErrorMessages(err)}\n`);238}239}240await appendText(editor, `\nNumber of system certificates: ${osCertificates?.length ?? 'failed to load'}\n`);241await appendText(editor, `242## Documentation243244In corporate networks: [Troubleshooting firewall settings for GitHub Copilot](https://docs.github.com/en/copilot/troubleshooting-github-copilot/troubleshooting-firewall-settings-for-github-copilot).`);245246return document.getText();247};248this._context.subscriptions.push(vscode.commands.registerCommand('github.copilot.debug.collectDiagnostics', collectDiagnostics));249// Internal command is not declared in package.json so it can be used from the welcome views while the extension is being activated.250this._context.subscriptions.push(vscode.commands.registerCommand('github.copilot.debug.collectDiagnostics.internal', collectDiagnostics));251this._context.subscriptions.push(vscode.commands.registerCommand('github.copilot.debug.showOutputChannel.internal', () => outputChannel.show()));252this._context.subscriptions.push(new NetworkStatus(this.fetcherService, this.configurationService, this.experimentationService));253}254255private async getAuthHeaders(isGHEnterprise: boolean, url: string) {256const authHeaders: Record<string, string> = {};257if (isGHEnterprise) {258let token = '';259if (url === this.capiClientService.dotcomAPIURL) {260token = this.authService.anyGitHubSession?.accessToken || '';261} else {262try {263token = (await this.authService.getCopilotToken()).token;264} catch (_err) {265// Ignore error266token = '';267}268}269authHeaders['Authorization'] = `Bearer ${token}`;270}271return authHeaders;272}273}274275async function appendText(editor: vscode.TextEditor, string: string) {276await editor.edit(builder => {277builder.insert(editor.document.lineAt(editor.document.lineCount - 1).range.end, string);278});279}280281function timeoutAfter(ms: number) {282return new Promise<'timeout'>(resolve => setTimeout(() => resolve('timeout'), ms));283}284285function loadVSCodeModule<T>(moduleName: string): T | undefined {286const appRoot = vscode.env.appRoot;287try {288return require(`${appRoot}/node_modules.asar/${moduleName}`);289} catch (err) {290// Not in ASAR.291}292try {293return require(`${appRoot}/node_modules/${moduleName}`);294} catch (err) {295// Not available.296}297return undefined;298}299300async function loadSystemCertificates(load: NonNullable<ProxyAgent['loadSystemCertificates']>, loadSystemCertificatesFromNode: boolean | undefined, logService: ILogService): Promise<(string | Buffer)[] | undefined> {301try {302const certificates = await load({303log: {304trace(message: string, ..._args: any[]) {305logService.trace(message);306},307debug(message: string, ..._args: any[]) {308logService.debug(message);309},310info(message: string, ..._args: any[]) {311logService.info(message);312},313warn(message: string, ..._args: any[]) {314logService.warn(message);315},316error(message: string | Error, ..._args: any[]) {317logService.error(typeof message === 'string' ? message : String(message));318},319} satisfies ProxyAgentLog,320loadSystemCertificatesFromNode: () => loadSystemCertificatesFromNode,321});322return Array.isArray(certificates) ? certificates : undefined;323} catch (err) {324logService.error(err);325return undefined;326}327}328329async function tlsConnect(tlsOrig: typeof tls, proxyURL: string, ca: (string | Buffer)[]) {330return new Promise<string>((resolve, reject) => {331const proxyUrlObj = new URL(proxyURL);332const socket = tlsOrig.connect({333host: proxyUrlObj.hostname,334port: parseInt(proxyUrlObj.port, 10),335servername: proxyUrlObj.hostname,336ca,337}, () => {338socket.end();339resolve('Succeeded');340});341socket.on('error', reject);342});343}344345async function proxyConnect(httpx: typeof https | typeof http, proxyUrl: string, targetUrl: string, sanitize = false) {346return new Promise<{ statusCode: number | undefined; statusMessage: string | undefined; headers: Record<string, string | string[]> }>((resolve, reject) => {347const proxyUrlObj = new URL(proxyUrl);348const targetUrlObj = new URL(targetUrl);349const targetHost = `${targetUrlObj.hostname}:${targetUrlObj.port || (targetUrlObj.protocol === 'https:' ? 443 : 80)}`;350const options = {351method: 'CONNECT',352host: proxyUrlObj.hostname,353port: proxyUrlObj.port,354path: targetHost,355headers: {356Host: targetHost,357},358rejectUnauthorized: false,359};360const req = httpx.request(options);361req.on('connect', (res, socket, head) => {362const headers = ['proxy-authenticate', 'proxy-agent', 'server', 'via'].reduce((acc, header) => {363const value = res.headers[header];364if (value) {365const doSanitize = sanitize && !['proxy-agent', 'server'].includes(header);366acc[header] = doSanitize ? Array.isArray(value) ? value.map(sanitizeValue) : sanitizeValue(value) : value;367}368return acc;369}, {} as Record<string, string | string[]>);370socket.end();371resolve({ statusCode: res.statusCode, statusMessage: res.statusMessage, headers });372});373req.on('error', reject);374req.end();375});376}377378const networkSettingsIds = [379'http.proxy',380'http.noProxy',381'http.proxyAuthorization',382'http.proxyStrictSSL',383'http.proxySupport',384'http.electronFetch',385'http.fetchAdditionalSupport',386'http.proxyKerberosServicePrincipal',387'http.systemCertificates',388'http.systemCertificatesNode',389'http.experimental.systemCertificatesV2',390'http.useLocalProxyConfiguration',391];392const alwaysShowSettingsIds = [393'http.systemCertificatesNode',394];395396function getNetworkSettings() {397const configuration = vscode.workspace.getConfiguration();398return networkSettingsIds.map(key => {399const i = configuration.inspect(key);400const v = configuration.get(key, i?.defaultValue);401if (alwaysShowSettingsIds.includes(key) || v !== i?.defaultValue && !(Array.isArray(v) && Array.isArray(i?.defaultValue) && v.length === 0 && i?.defaultValue.length === 0)) {402return `\n "${key}": ${JSON.stringify(v)},`;403}404return '';405}).join('');406}407408function getProxyEnvVariables() {409const res = [];410const envVars = ['http_proxy', 'https_proxy', 'ftp_proxy', 'all_proxy', 'no_proxy'];411for (const env in process.env) {412if (envVars.includes(env.toLowerCase())) {413res.push(`\n- ${env}=${process.env[env]}`);414}415}416return res.length ? `\n\nEnvironment Variables:${res.join('')}` : '';417}418419export class FetcherTelemetryContribution {420constructor(421@IInstantiationService instantiationService: IInstantiationService,422) {423instantiationService.invokeFunction(collectFetcherTelemetry);424}425}426427function collectFetcherTelemetry(accessor: ServicesAccessor): void {428const extensionContext = accessor.get(IVSCodeExtensionContext);429const envService = accessor.get(IEnvService);430const logService = accessor.get(ILogService);431const configurationService = accessor.get(IConfigurationService);432const expService = accessor.get(IExperimentationService);433434if (!vscode.env.isTelemetryEnabled || extensionContext.extensionMode !== vscode.ExtensionMode.Production || isScenarioAutomation) {435return;436}437438if (!configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.DebugCollectFetcherTelemetry, expService)) {439return;440}441442const now = Date.now();443const previous = extensionContext.globalState.get<number>('lastCollectFetcherTelemetryTime', 0);444if (now - previous < 5 * 60 * 1000) {445logService.debug(`Send fetcher telemetry: Skipped.`);446return;447}448449(async () => {450await extensionContext.globalState.update('lastCollectFetcherTelemetryTime', now);451452logService.debug(`Send fetcher telemetry: Exclude other windows.`);453const windowUUID = generateUuid();454await extensionContext.globalState.update('lastCollectFetcherTelemetryUUID', windowUUID);455await timeout(5000);456if (extensionContext.globalState.get<string>('lastCollectFetcherTelemetryUUID') !== windowUUID) {457logService.debug(`Send fetcher telemetry: Other window won.`);458return;459}460logService.debug(`Send fetcher telemetry: This window won.`);461462const fetchers = [463ElectronFetcher.create(envService),464new NodeFetchFetcher(envService),465new NodeFetcher(envService),466].filter(fetcher => fetcher) as IFetcher[];467468// Randomize to offset any order dependency in telemetry.469shuffle(fetchers);470471// First loop: probe each fetcher with an empty body to collect connectivity results.472const probeResults: Record<string, string> = {};473for (const fetcher of fetchers) {474const library = fetcher.getUserAgentLibrary();475const key = library.replace(/-/g, '');476const requestStartTime = Date.now();477try {478const response = await sendRawTelemetry(fetcher, envService, extensionContext, 'GitHub.copilot-chat/fetcherTelemetryProbe', {});479probeResults[key] = `Status: ${response.status}`;480logService.debug(`Fetcher telemetry probe: ${library} ${probeResults[key]} (${Date.now() - requestStartTime}ms)`);481} catch (e) {482probeResults[key] = `Error: ${sanitizeNetworkErrorForTelemetry(collectSingleLineErrorMessage(e, true))}`;483logService.debug(`Fetcher telemetry probe: ${library} ${probeResults[key]} (${Date.now() - requestStartTime}ms)`);484}485}486487// Second loop: send the actual telemetry event including probe results.488const requestGroupId = generateUuid();489const extensionKind = extensionContext.extension.extensionKind === vscode.ExtensionKind.UI ? 'local' : 'remote';490for (const fetcher of fetchers) {491const requestStartTime = Date.now();492try {493/* __GDPR__494"fetcherTelemetry" : {495"owner": "chrmarti",496"comment": "Telemetry event to test connectivity of different fetcher implementations.",497"requestGroupId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Id to group requests from the same run." },498"clientLibrary": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The fetcher library used for this request." },499"extensionKind": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Whether the extension runs locally or remotely." },500"remoteName": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The remote name, if any." },501"electronfetch": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Probe result for the electron-fetch fetcher." },502"nodefetch": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Probe result for the node-fetch fetcher." },503"nodehttp": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Probe result for the node-http fetcher." }504}505*/506const properties: Record<string, string> = {507requestGroupId,508clientLibrary: fetcher.getUserAgentLibrary(),509extensionKind,510remoteName: vscode.env.remoteName ?? 'none',511...probeResults,512};513const response = await sendRawTelemetry(fetcher, envService, extensionContext, 'GitHub.copilot-chat/fetcherTelemetry', properties);514515logService.debug(`Fetcher telemetry: Succeeded in ${Date.now() - requestStartTime}ms using ${fetcher.getUserAgentLibrary()} with status ${response.status} (${response.statusText}).`);516} catch (e) {517logService.debug(`Fetcher telemetry: Failed in ${Date.now() - requestStartTime}ms using ${fetcher.getUserAgentLibrary()}.`);518}519}520})().catch(err => {521logService.error(err);522});523}524525async function sendRawTelemetry(fetcher: IFetcher, envService: IEnvService, extensionContext: IVSCodeExtensionContext, eventName: string, properties: Record<string, string>) {526const url = 'https://mobile.events.data.microsoft.com/OneCollector/1.0?cors=true&content-type=application/x-json-stream';527const product = require(path.join(vscode.env.appRoot, 'product.json'));528const vscodeCommitHash: string = product.commit || '';529const ariaKey = (extensionContext.extension.packageJSON as { ariaKey?: string }).ariaKey ?? '';530const iKey = `o:${ariaKey.split('-')[0]}`;531const sdkVer = '1DS-Web-JS-4.3.10';532const eventTime = new Date(Date.now() - 10).toISOString();533const event = {534name: eventName,535time: eventTime,536ver: '4.0',537iKey,538ext: {539sdk: { ver: sdkVer },540web: { consentDetails: '{"GPC_DataSharingOptIn":false}' },541},542data: {543baseData: {544name: eventName,545properties: {546...properties,547'abexp.assignmentcontext': '',548'common.os': os.platform(),549'common.nodeArch': os.arch(),550'common.platformversion': os.release(),551'common.telemetryclientversion': '1.5.0',552'common.extname': EXTENSION_ID,553'common.extversion': envService.getVersion(),554'common.vscodemachineid': envService.machineId,555'common.vscodesessionid': envService.sessionId,556'common.vscodecommithash': vscodeCommitHash,557'common.sqmid': '',558'common.devDeviceId': envService.devDeviceId,559'common.vscodeversion': envService.vscodeVersion,560'common.vscodereleasedate': product.date || 'unknown',561'common.isnewappinstall': vscode.env.isNewAppInstall,562'common.product': envService.uiKind,563'common.uikind': envService.uiKind,564'common.remotename': envService.remoteName ?? 'none',565'version': 'PostChannel=4.3.10',566},567},568},569};570const body = JSON.stringify(event);571const headers: Record<string, string> = {572'Client-Id': 'NO_AUTH',573'client-version': sdkVer,574'apikey': ariaKey,575'upload-time': String(Date.now()),576'time-delta-to-apply-millis': 'use-collector-delta',577'cache-control': 'no-cache, no-store',578'content-type': 'application/x-json-stream',579'User-Agent': `GitHubCopilotChat/${envService.getVersion()}`,580[userAgentLibraryHeader]: fetcher.getUserAgentLibrary(),581};582if (fetcher.getUserAgentLibrary() === NodeFetcher.ID) {583headers['content-length'] = String(Buffer.byteLength(body));584}585const response = await fetcher.fetch(url, {586method: 'POST',587headers,588body,589callSite: 'diagnostics-telemetry-probe',590});591await response.text();592return response;593}594595const ids_paths = /(^|\b)[\p{L}\p{Nd}]+((=""?[^"]+""?)|(([.:=/"_-]+[\p{L}\p{Nd}]+)+))(\b|$)/giu;596export function sanitizeValue(input: string | undefined): string {597return (input || '').replace(ids_paths, (m) => maskByClass(m));598}599600function maskByClass(s: string): string {601if (/^net::[A-Z_]+$/.test(s) || ['dev-container', 'attached-container', 'k8s-container', 'ssh-remote'].includes(s)) {602return s;603}604return s.replace(/\p{Lu}|\p{Ll}|\p{Nd}/gu, (ch) => {605if (/\p{Lu}/u.test(ch)) {606return 'A';607}608if (/\p{Ll}/u.test(ch)) {609return 'a';610}611return '0';612});613}614615class NetworkStatus extends Disposable {616617private readonly _statusBarItem: vscode.StatusBarItem;618private readonly _events: FetchEvent[] = [];619private readonly _fetchSubscription = this._register(new MutableDisposable());620621constructor(private readonly _fetcherService: IFetcherService, private readonly _configurationService: IConfigurationService, private readonly _experimentationService: IExperimentationService) {622super();623this._statusBarItem = this._register(vscode.window.createStatusBarItem('copilot.networkStatus', vscode.StatusBarAlignment.Right, -1000));624this._statusBarItem.name = 'Copilot Network Status';625this._register(_configurationService.onDidChangeConfiguration(e => {626if (e.affectsConfiguration(ConfigKey.TeamInternal.DebugShowNetworkStatus.fullyQualifiedId)) {627this._update();628}629}));630this._update();631}632633private _isEnabled(): boolean {634return this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.DebugShowNetworkStatus, this._experimentationService);635}636637private _onEvent(event: FetchEvent): void {638this._events.push(event);639const cutoff = Date.now() - 5 * 60 * 1000;640while (this._events.length > 0 && this._events[0].timestamp < cutoff) {641this._events.shift();642}643this._update();644}645646private _update(): void {647const enabled = this._isEnabled();648if (enabled && !this._fetchSubscription.value) {649this._fetchSubscription.value = this._fetcherService.onDidFetch(e => this._onEvent(e));650} else if (!enabled) {651this._fetchSubscription.value = undefined;652this._events.length = 0;653this._statusBarItem.hide();654return;655}656const latestById = new Map<string, FetchEvent>();657for (const e of this._events) {658latestById.set(e.internalId, e);659}660const latest = [...latestById.values()];661const errors = latest.filter(e => e.outcome === 'error');662this._statusBarItem.text = `Copilot Network: ${errors.length} errors / ${latest.length} total`;663664const byHostname = new Map<string, { total: number; errors: number; cancellations: number }>();665for (const e of latest) {666let entry = byHostname.get(e.hostname);667if (!entry) {668entry = { total: 0, errors: 0, cancellations: 0 };669byHostname.set(e.hostname, entry);670}671entry.total++;672if (e.outcome === 'error') {673entry.errors++;674} else if (e.outcome === 'cancel') {675entry.cancellations++;676}677}678const tooltip = new vscode.MarkdownString();679tooltip.appendMarkdown(`| Hostname | Errors | Cancellations | Total |\n`);680tooltip.appendMarkdown(`|:--|--:|--:|--:|\n`);681for (const [hostname, { total, errors, cancellations }] of [...byHostname].sort((a, b) => b[1].total - a[1].total)) {682tooltip.appendMarkdown(`| ${hostname} | ${errors} | ${cancellations} | ${total} |\n`);683}684tooltip.appendMarkdown(`\n**${errors.length}** of **${latest.length}** network requests failed in the last 5 minutes`);685this._statusBarItem.tooltip = tooltip;686687this._statusBarItem.show();688}689}690691692