UNPKG

@salesforce/plugin-telemetry

Version:

Command usage and error telemetry for the Salesforce CLI

187 lines 8.79 kB
/* * Copyright 2026, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { SfError } from '@salesforce/core/sfError'; import { asBoolean, asString } from '@salesforce/ts-types'; import { Org } from '@salesforce/core'; import Telemetry from './telemetry.js'; import { debug } from './debugger.js'; const PROJECT = 'salesforce-cli'; const APP_INSIGHTS_KEY = 'InstrumentationKey=2ca64abb-6123-4c7b-bd9e-4fe73e71fe9c;IngestionEndpoint=https://eastus-1.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=ecd8fa7a-0e0d-4109-94db-4d7878ada862'; export class Uploader { telemetry; version; o11yUploadEndpoint = ''; constructor(telemetry, version) { this.telemetry = telemetry; this.version = version; } /** * Sends events from telemetry. */ static async upload(cacheDir, telemetryFilePath, version) { const telemetry = (global.cliTelemetry = await Telemetry.create({ cacheDir, telemetryFilePath })); const uploader = new Uploader(telemetry, version); await uploader.sendToTelemetry(); } /** * Reads the telemetry events from file and sends them to the telemetry service. */ async sendToTelemetry() { const { TelemetryReporter } = await import('@salesforce/telemetry'); let appInsightsReporter; const o11yReporterMap = new Map(); try { appInsightsReporter = await TelemetryReporter.create({ project: PROJECT, key: APP_INSIGHTS_KEY, userId: this.telemetry.getCLIId(), waitForConnection: true, enableO11y: false, enableAppInsights: true, }); } catch (err) { const error = SfError.wrap(err); debug(`Error creating app insightsreporter: ${error.message}`); } try { const events = await this.telemetry.read(); const { appInsightsEvents, appInsightsErrors, o11yEvents } = this.parseEvents(events); // Send AppInsights events if (appInsightsReporter && appInsightsEvents.length > 0) { appInsightsEvents.forEach((event) => { const eventName = asString(event.eventName) ?? 'UNKNOWN'; delete event.eventName; appInsightsReporter.sendTelemetryEvent(eventName, event); }); } // Send AppInsights errors if (appInsightsReporter && appInsightsErrors.length > 0) { appInsightsErrors.forEach((event) => { const error = new Error(); // We know this is an object because it is logged as such const errorObject = event.error; delete event.error; delete event.eventName; Object.assign(error, errorObject); error.name = asString(errorObject.name) ?? 'Unknown'; error.message = asString(errorObject.message) ?? 'Unknown'; error.stack = asString(errorObject.stack) ?? 'Unknown'; appInsightsReporter.sendTelemetryException(error, event); }); } // Send PDP events via O11y if (o11yEvents.length > 0) { for (const event of o11yEvents) { let o11yReporter; const o11yReporterKey = event.targetOrgUsername ?? 'default'; if (!o11yReporterMap.has(o11yReporterKey)) { try { const telemetryReporterOptions = { project: PROJECT, key: 'not-used', userId: this.telemetry.getCLIId(), waitForConnection: true, enableO11y: true, enableAppInsights: false, o11yUploadEndpoint: this.o11yUploadEndpoint, o11yBatching: { enableAutoBatching: true, enableShutdownHook: true, }, }; if (event.targetOrgUsername) { telemetryReporterOptions.getConnectionFn = async () => (await Org.create({ aliasOrUsername: event.targetOrgUsername })).getConnection(); } // eslint-disable-next-line no-await-in-loop o11yReporter = await TelemetryReporter.create(telemetryReporterOptions); o11yReporterMap.set(o11yReporterKey, o11yReporter); } catch (err) { const error = SfError.wrap(err); debug(`Error creating o11y reporter for PDP event: ${error.message}`); } } else { o11yReporter = o11yReporterMap.get(o11yReporterKey); } delete event.targetOrgUsername; o11yReporter?.sendPdpEvent(event); } } } catch (err) { const error = SfError.wrap(err); debug(`Error reading or sending telemetry events: ${error.message}`); } finally { try { // We are done sending events appInsightsReporter?.stop(); if (o11yReporterMap.size > 0) { await Promise.all(Array.from(o11yReporterMap.values(), (reporter) => reporter.stopAsync())); } } catch (err) { const error = SfError.wrap(err); debug(`Error stopping telemetry reporter: ${error.message}`); } finally { // We always want to clear the file. await this.telemetry.clear(); } } } parseEvents(events) { const appInsightsEvents = []; const appInsightsErrors = []; const o11yEvents = []; for (const event of events) { event.telemetryVersion = this.version; const eventType = asString(event.type) ?? Telemetry.EVENT; const eventName = asString(event.eventName) ?? 'UNKNOWN'; const enableO11y = asBoolean(event.enableO11y) ?? false; const productFeatureId = asString(event.productFeatureId) ?? 'aJCEE0000000mHP4AY'; const targetOrgUsername = asString(event.targetOrgUsername) ?? undefined; this.o11yUploadEndpoint = asString(event.o11yUploadEndpoint) ?? ''; delete event.type; delete event.enableO11y; delete event.o11yUploadEndpoint; delete event.productFeatureId; delete event.targetOrgUsername; if (eventType === Telemetry.EVENT) { appInsightsEvents.push(event); if (enableO11y && this.o11yUploadEndpoint.length > 0 && eventName === 'COMMAND_EXECUTION') { const pluginName = `${asString(event.plugin) ?? 'unknownPlugin'}`; const commandName = `${asString(event.command) ?? 'unknownCommand'}`; o11yEvents.push({ eventName: 'salesforceCli.executed', targetOrgUsername, productFeatureId: productFeatureId, componentId: `${pluginName}.${commandName}`, contextName: 'orgId::devhubId', // Delimited string of keys contextValue: `${event.orgId ?? ''}::${event.devhubId ?? ''}`, // Delimited string of values }); } } else if (eventType === Telemetry.EXCEPTION) { appInsightsErrors.push(event); } } return { appInsightsEvents, appInsightsErrors, o11yEvents }; } } //# sourceMappingURL=uploader.js.map