Add extensions logging (#11261)

This commit is contained in:
christine betts
2025-10-21 16:55:16 -04:00
committed by GitHub
parent e9e80b054d
commit c6a59896f3
16 changed files with 230 additions and 65 deletions
+65 -41
View File
@@ -199,28 +199,6 @@ export function loadExtension(
)
.filter((contextFilePath) => fs.existsSync(contextFilePath));
// IDs are created by hashing details of the installation source in order to
// deduplicate extensions with conflicting names and also obfuscate any
// potentially sensitive information such as private git urls, system paths,
// or project names.
const hash = createHash('sha256');
const githubUrlParts =
installMetadata &&
(installMetadata.type === 'git' ||
installMetadata.type === 'github-release')
? tryParseGithubUrl(installMetadata.source)
: null;
if (githubUrlParts) {
// For github repos, we use the https URI to the repo as the ID.
hash.update(
`https://github.com/${githubUrlParts.owner}/${githubUrlParts.repo}`,
);
} else {
hash.update(installMetadata?.source ?? config.name);
}
const id = hash.digest('hex');
return {
name: config.name,
version: config.version,
@@ -230,7 +208,7 @@ export function loadExtension(
mcpServers: config.mcpServers,
excludeTools: config.excludeTools,
isActive: extensionEnablementManager.isEnabled(config.name, workspaceDir),
id,
id: getExtensionId(config, installMetadata),
};
} catch (e) {
debugLogger.error(
@@ -384,6 +362,10 @@ async function promptForConsentInteractive(
});
}
export function hashValue(value: string): string {
return createHash('sha256').update(value).digest('hex');
}
export async function installOrUpdateExtension(
installMetadata: ExtensionInstallMetadata,
requestConsent: (consent: string) => Promise<boolean>,
@@ -520,15 +502,15 @@ export async function installOrUpdateExtension(
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
}
if (isUpdate) {
logExtensionUpdateEvent(
telemetryConfig,
new ExtensionUpdateEvent(
newExtensionConfig.name,
hashValue(newExtensionConfig.name),
getExtensionId(newExtensionConfig, installMetadata),
newExtensionConfig.version,
previousExtensionConfig.version,
installMetadata.source,
installMetadata.type,
'success',
),
);
@@ -536,9 +518,10 @@ export async function installOrUpdateExtension(
logExtensionInstallEvent(
telemetryConfig,
new ExtensionInstallEvent(
newExtensionConfig.name,
hashValue(newExtensionConfig.name),
getExtensionId(newExtensionConfig, installMetadata),
newExtensionConfig.version,
installMetadata.source,
installMetadata.type,
'success',
),
);
@@ -564,14 +547,19 @@ export async function installOrUpdateExtension(
// Ignore error, this is just for logging.
}
}
const config = newExtensionConfig ?? previousExtensionConfig;
const extensionId = config
? getExtensionId(config, installMetadata)
: undefined;
if (isUpdate) {
logExtensionUpdateEvent(
telemetryConfig,
new ExtensionUpdateEvent(
newExtensionConfig?.name ?? previousExtensionConfig.name,
hashValue(config?.name ?? ''),
extensionId ?? '',
newExtensionConfig?.version ?? '',
previousExtensionConfig.version,
installMetadata.source,
installMetadata.type,
'error',
),
);
@@ -579,9 +567,10 @@ export async function installOrUpdateExtension(
logExtensionInstallEvent(
telemetryConfig,
new ExtensionInstallEvent(
newExtensionConfig?.name ?? '',
hashValue(newExtensionConfig?.name ?? ''),
extensionId ?? '',
newExtensionConfig?.version ?? '',
installMetadata.source,
installMetadata.type,
'error',
),
);
@@ -707,16 +696,16 @@ export async function uninstallExtension(
new ExtensionEnablementManager(),
cwd,
);
const extensionName = installedExtensions.find(
const extension = installedExtensions.find(
(installed) =>
installed.name.toLowerCase() === extensionIdentifier.toLowerCase() ||
installed.installMetadata?.source.toLowerCase() ===
extensionIdentifier.toLowerCase(),
)?.name;
if (!extensionName) {
);
if (!extension) {
throw new Error(`Extension not found.`);
}
const storage = new ExtensionStorage(extensionName);
const storage = new ExtensionStorage(extension.name);
await fs.promises.rm(storage.getExtensionDir(), {
recursive: true,
@@ -727,13 +716,17 @@ export async function uninstallExtension(
// uninstalls related to updates.
if (isUpdate) return;
const manager = new ExtensionEnablementManager([extensionName]);
manager.remove(extensionName);
const manager = new ExtensionEnablementManager([extension.name]);
manager.remove(extension.name);
const telemetryConfig = getTelemetryConfig(cwd);
logExtensionUninstall(
telemetryConfig,
new ExtensionUninstallEvent(extensionName, 'success'),
new ExtensionUninstallEvent(
hashValue(extension.name),
extension.id,
'success',
),
);
}
@@ -747,6 +740,7 @@ export function toOutputString(
const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
let output = `${status} ${extension.name} (${extension.version})`;
output += `\n ID: ${extension.id}`;
output += `\n Path: ${extension.path}`;
if (extension.installMetadata) {
output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`;
@@ -797,7 +791,10 @@ export function disableExtension(
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
extensionEnablementManager.disable(name, true, scopePath);
logExtensionDisable(config, new ExtensionDisableEvent(name, scope));
logExtensionDisable(
config,
new ExtensionDisableEvent(hashValue(name), extension.id, scope),
);
}
export function enableExtension(
@@ -816,5 +813,32 @@ export function enableExtension(
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
extensionEnablementManager.enable(name, true, scopePath);
const config = getTelemetryConfig(cwd);
logExtensionEnable(config, new ExtensionEnableEvent(name, scope));
logExtensionEnable(
config,
new ExtensionEnableEvent(hashValue(name), extension.id, scope),
);
}
function getExtensionId(
config: ExtensionConfig,
installMetadata?: ExtensionInstallMetadata,
): string {
// IDs are created by hashing details of the installation source in order to
// deduplicate extensions with conflicting names and also obfuscate any
// potentially sensitive information such as private git urls, system paths,
// or project names.
let idValue = config.name;
const githubUrlParts =
installMetadata &&
(installMetadata.type === 'git' ||
installMetadata.type === 'github-release')
? tryParseGithubUrl(installMetadata.source)
: null;
if (githubUrlParts) {
// For github repos, we use the https URI to the repo as the ID.
idValue = `https://github.com/${githubUrlParts.owner}/${githubUrlParts.repo}`;
} else {
idValue = installMetadata?.source ?? config.name;
}
return hashValue(idValue);
}