@salesforce/plugin-telemetry
Version:
Command usage and error telemetry for the Salesforce CLI
187 lines • 8.79 kB
JavaScript
/*
* 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