diff --git a/src/appInsights.spec.ts b/src/appInsights.spec.ts index 5fe35ad259c..5e0cace7f5c 100644 --- a/src/appInsights.spec.ts +++ b/src/appInsights.spec.ts @@ -36,10 +36,4 @@ describe('appInsights', () => { const i: any = require('./appInsights'); assert(i.default.commonProperties.env === 'docker'); }); - - it(`sets shell to empty string if couldn't resolve name from pid`, () => { - sinon.stub(pid, 'getProcessName').callsFake(() => undefined); - const i: any = require('./appInsights'); - assert.strictEqual(i.default.commonProperties.shell, ''); - }); }); \ No newline at end of file diff --git a/src/appInsights.ts b/src/appInsights.ts index 9d006b2fe0c..9a0383c26c4 100644 --- a/src/appInsights.ts +++ b/src/appInsights.ts @@ -8,8 +8,6 @@ import * as appInsights from 'applicationinsights'; import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; -import { pid } from './utils/pid'; -import { session } from './utils/session'; const config = appInsights.setup('6b908c80-d09f-4cf6-8274-e54349a0149a'); config.setInternalLogging(false, false); @@ -21,12 +19,10 @@ const env: string = process.env.CLIMICROSOFT365_ENV !== undefined ? process.env. appInsights.defaultClient.commonProperties = { version: version, node: process.version, - shell: pid.getProcessName(process.ppid) || '', env: env, ci: Boolean(process.env.CI).toString() }; -appInsights.defaultClient.context.tags['ai.session.id'] = session.getId(process.ppid); appInsights.defaultClient.context.tags['ai.cloud.roleInstance'] = crypto.createHash('sha256').update(appInsights.defaultClient.context.tags['ai.cloud.roleInstance']).digest('hex'); delete appInsights.defaultClient.context.tags['ai.cloud.role']; diff --git a/src/m365/base/AppCommand.spec.ts b/src/m365/base/AppCommand.spec.ts index fdae2a1d574..4752daa9117 100644 --- a/src/m365/base/AppCommand.spec.ts +++ b/src/m365/base/AppCommand.spec.ts @@ -7,6 +7,7 @@ import { sinonUtil } from '../../utils/sinonUtil'; import AppCommand from './AppCommand'; import sinon = require('sinon'); import Command, { CommandError } from '../../Command'; +import { telemetry } from '../../telemetry'; class MockCommand extends AppCommand { public get name(): string { @@ -32,6 +33,7 @@ describe('AppCommand', () => { before(() => { commandInfo = Cli.getCommandInfo(new MockCommand()); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); }); beforeEach(() => { @@ -58,6 +60,10 @@ describe('AppCommand', () => { ]); }); + after(() => { + sinon.restore(); + }); + it('defines correct resource', () => { assert.strictEqual((cmd as any).resource, 'https://graph.microsoft.com'); }); diff --git a/src/m365/cli/commands/config/config-reset.spec.ts b/src/m365/cli/commands/config/config-reset.spec.ts index ce6d1d651fd..fc412b7d2bc 100644 --- a/src/m365/cli/commands/config/config-reset.spec.ts +++ b/src/m365/cli/commands/config/config-reset.spec.ts @@ -50,13 +50,12 @@ describe(commands.CONFIG_RESET, () => { assert.notStrictEqual(command.description, null); }); - it(`Resets a specific configuration option to its default value`, async () => { + it('resets a specific configuration option to its default value', async () => { const output = undefined; const config = Cli.getInstance().config; let actualKey: string = '', actualValue: any; - sinon.restore(); sinon.stub(config, 'delete').callsFake(((key: string) => { actualKey = key; actualValue = undefined; @@ -67,15 +66,13 @@ describe(commands.CONFIG_RESET, () => { assert.strictEqual(actualValue, undefined, 'Invalid value'); }); - it(`Resets all configuration settings to default`, async () => { + it('resets all configuration settings to default', async () => { const config = Cli.getInstance().config; let errorOutputKey: string = '', errorOutputValue: any , outputKey: string = '', outputValue: any , printErrorsAsPlainTextKey: string = '', printErrorsAsPlainTextValue: any , showHelpOnFailureKey: string = '', showHelpOnFailureValue: any; - sinon.restore(); - sinon.stub(config, 'clear').callsFake((() => { errorOutputKey = settingsNames.errorOutput; errorOutputValue = undefined; diff --git a/src/m365/cli/commands/config/config-set.spec.ts b/src/m365/cli/commands/config/config-set.spec.ts index 440a9928608..510b5bfc25c 100644 --- a/src/m365/cli/commands/config/config-set.spec.ts +++ b/src/m365/cli/commands/config/config-set.spec.ts @@ -83,7 +83,6 @@ describe(commands.CONFIG_SET, () => { const output = "text"; const config = Cli.getInstance().config; let actualKey: string = '', actualValue: any; - sinon.restore(); sinon.stub(config, 'set').callsFake(((key: string, value: any) => { actualKey = key; actualValue = value; @@ -98,7 +97,6 @@ describe(commands.CONFIG_SET, () => { const output = "json"; const config = Cli.getInstance().config; let actualKey: string = '', actualValue: any; - sinon.restore(); sinon.stub(config, 'set').callsFake(((key: string, value: any) => { actualKey = key; actualValue = value; @@ -113,7 +111,6 @@ describe(commands.CONFIG_SET, () => { const output = "csv"; const config = Cli.getInstance().config; let actualKey: string = '', actualValue: any; - sinon.restore(); sinon.stub(config, 'set').callsFake(((key: string, value: any) => { actualKey = key; actualValue = value; diff --git a/src/m365/context/commands/context-init.spec.ts b/src/m365/context/commands/context-init.spec.ts index d05e6384efd..66f75695533 100644 --- a/src/m365/context/commands/context-init.spec.ts +++ b/src/m365/context/commands/context-init.spec.ts @@ -1,9 +1,9 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as sinon from 'sinon'; -import appInsights from '../../../appInsights'; -import { Logger } from '../../../cli/Logger'; import Command, { CommandError } from '../../../Command'; +import { Logger } from '../../../cli/Logger'; +import { telemetry } from '../../../telemetry'; import { sinonUtil } from '../../../utils/sinonUtil'; import commands from '../commands'; const command: Command = require('./context-init'); @@ -13,7 +13,7 @@ describe(commands.INIT, () => { let logger: Logger; before(() => { - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); }); beforeEach(() => { diff --git a/src/m365/purview/commands/retentionevent/retentionevent-remove.spec.ts b/src/m365/purview/commands/retentionevent/retentionevent-remove.spec.ts index b64bfb2dbdd..fdb3d8dd21e 100644 --- a/src/m365/purview/commands/retentionevent/retentionevent-remove.spec.ts +++ b/src/m365/purview/commands/retentionevent/retentionevent-remove.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; @@ -23,7 +23,7 @@ describe(commands.RETENTIONEVENT_REMOVE, () => { before(() => { sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.service.connected = true; diff --git a/src/m365/spo/commands/listitem/listitem-record-lock.spec.ts b/src/m365/spo/commands/listitem/listitem-record-lock.spec.ts index 8520dc9f16e..9ae9549d370 100644 --- a/src/m365/spo/commands/listitem/listitem-record-lock.spec.ts +++ b/src/m365/spo/commands/listitem/listitem-record-lock.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; @@ -40,7 +40,7 @@ describe(commands.LISTITEM_RECORD_LOCK, () => { before(() => { cli = Cli.getInstance(); sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.service.connected = true; diff --git a/src/m365/spo/commands/listitem/listitem-record-unlock.spec.ts b/src/m365/spo/commands/listitem/listitem-record-unlock.spec.ts index fa6a1bae11d..119aeb2bb3b 100644 --- a/src/m365/spo/commands/listitem/listitem-record-unlock.spec.ts +++ b/src/m365/spo/commands/listitem/listitem-record-unlock.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; @@ -40,7 +40,7 @@ describe(commands.LISTITEM_RECORD_UNLOCK, () => { before(() => { cli = Cli.getInstance(); sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.service.connected = true; diff --git a/src/m365/spo/commands/listitem/listitem-retentionlabel-ensure.spec.ts b/src/m365/spo/commands/listitem/listitem-retentionlabel-ensure.spec.ts index 02bf1c7e4a6..d7fe48ff6fa 100644 --- a/src/m365/spo/commands/listitem/listitem-retentionlabel-ensure.spec.ts +++ b/src/m365/spo/commands/listitem/listitem-retentionlabel-ensure.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { formatting } from '../../../../utils/formatting'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; @@ -64,7 +64,7 @@ describe(commands.LISTITEM_RETENTIONLABEL_ENSURE, () => { before(() => { cli = Cli.getInstance(); sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.service.connected = true; diff --git a/src/m365/spo/commands/tenant/tenant-applicationcustomizer-add.spec.ts b/src/m365/spo/commands/tenant/tenant-applicationcustomizer-add.spec.ts index cc59e2759c3..9e523b61e75 100644 --- a/src/m365/spo/commands/tenant/tenant-applicationcustomizer-add.spec.ts +++ b/src/m365/spo/commands/tenant/tenant-applicationcustomizer-add.spec.ts @@ -1,19 +1,19 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; +import { urlUtil } from '../../../../utils/urlUtil'; import commands from '../../commands'; -import * as spoTenantAppCatalogUrlGetCommand from '../tenant/tenant-appcatalogurl-get'; -import * as spoListItemListCommand from '../listitem/listitem-list'; import * as spoListItemAddCommand from '../listitem/listitem-add'; -import { urlUtil } from '../../../../utils/urlUtil'; +import * as spoListItemListCommand from '../listitem/listitem-list'; +import * as spoTenantAppCatalogUrlGetCommand from '../tenant/tenant-appcatalogurl-get'; const command: Command = require('./tenant-applicationcustomizer-add'); describe(commands.TENANT_APPLICATIONCUSTOMIZER_ADD, () => { @@ -36,7 +36,7 @@ describe(commands.TENANT_APPLICATIONCUSTOMIZER_ADD, () => { before(() => { sinon.stub(auth, 'restoreAuth').resolves(); - sinon.stub(appInsights, 'trackEvent').returns(); + sinon.stub(telemetry, 'trackEvent').returns(); sinon.stub(pid, 'getProcessName').returns(''); sinon.stub(session, 'getId').returns(''); auth.service.connected = true; diff --git a/src/m365/spo/commands/tenant/tenant-commandset-add.spec.ts b/src/m365/spo/commands/tenant/tenant-commandset-add.spec.ts index 9e877305c2e..1d61106803c 100644 --- a/src/m365/spo/commands/tenant/tenant-commandset-add.spec.ts +++ b/src/m365/spo/commands/tenant/tenant-commandset-add.spec.ts @@ -1,19 +1,19 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; +import { urlUtil } from '../../../../utils/urlUtil'; import commands from '../../commands'; -import * as spoTenantAppCatalogUrlGetCommand from './tenant-appcatalogurl-get'; -import * as spoListItemListCommand from '../listitem/listitem-list'; import * as spoListItemAddCommand from '../listitem/listitem-add'; -import { urlUtil } from '../../../../utils/urlUtil'; +import * as spoListItemListCommand from '../listitem/listitem-list'; +import * as spoTenantAppCatalogUrlGetCommand from './tenant-appcatalogurl-get'; const command: Command = require('./tenant-commandset-add'); describe(commands.TENANT_COMMANDSET_ADD, () => { @@ -36,7 +36,7 @@ describe(commands.TENANT_COMMANDSET_ADD, () => { before(() => { sinon.stub(auth, 'restoreAuth').resolves(); - sinon.stub(appInsights, 'trackEvent').returns(); + sinon.stub(telemetry, 'trackEvent').returns(); sinon.stub(pid, 'getProcessName').returns(''); sinon.stub(session, 'getId').returns(''); auth.service.connected = true; diff --git a/src/m365/spo/commands/tenant/tenant-commandset-set.spec.ts b/src/m365/spo/commands/tenant/tenant-commandset-set.spec.ts index 09fc97cea79..a706ea3e3dc 100644 --- a/src/m365/spo/commands/tenant/tenant-commandset-set.spec.ts +++ b/src/m365/spo/commands/tenant/tenant-commandset-set.spec.ts @@ -1,16 +1,16 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; +import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; import { sinonUtil } from '../../../../utils/sinonUtil'; import commands from '../../commands'; -import request from '../../../../request'; const command: Command = require('./tenant-commandset-set'); describe(commands.TENANT_COMMANDSET_SET, () => { @@ -52,7 +52,7 @@ describe(commands.TENANT_COMMANDSET_SET, () => { before(() => { sinon.stub(auth, 'restoreAuth').resolves(); - sinon.stub(appInsights, 'trackEvent').returns(); + sinon.stub(telemetry, 'trackEvent').returns(); sinon.stub(pid, 'getProcessName').returns(''); sinon.stub(session, 'getId').returns(''); auth.service.connected = true; diff --git a/src/m365/spo/commands/web/web-retentionlabel-list.spec.ts b/src/m365/spo/commands/web/web-retentionlabel-list.spec.ts index e886cb9c30b..518af6633ec 100644 --- a/src/m365/spo/commands/web/web-retentionlabel-list.spec.ts +++ b/src/m365/spo/commands/web/web-retentionlabel-list.spec.ts @@ -1,12 +1,12 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import appInsights from '../../../../appInsights'; import auth from '../../../../Auth'; +import Command, { CommandError } from '../../../../Command'; import { Cli } from '../../../../cli/Cli'; import { CommandInfo } from '../../../../cli/CommandInfo'; import { Logger } from '../../../../cli/Logger'; -import Command, { CommandError } from '../../../../Command'; import request from '../../../../request'; +import { telemetry } from '../../../../telemetry'; import { formatting } from '../../../../utils/formatting'; import { pid } from '../../../../utils/pid'; import { session } from '../../../../utils/session'; @@ -59,7 +59,7 @@ describe(commands.WEB_RETENTIONLABEL_LIST, () => { before(() => { sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); - sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); + sinon.stub(telemetry, 'trackEvent').callsFake(() => { }); sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.service.connected = true; diff --git a/src/telemetry.spec.ts b/src/telemetry.spec.ts index 120bd5f0c87..5a68ca13366 100644 --- a/src/telemetry.spec.ts +++ b/src/telemetry.spec.ts @@ -1,21 +1,47 @@ -import * as sinon from 'sinon'; import * as assert from 'assert'; -import appInsights from './appInsights'; +import * as child_process from 'child_process'; +import * as sinon from 'sinon'; import { Cli } from "./cli/Cli"; import { settingsNames } from './settingsNames'; -import { sinonUtil } from './utils/sinonUtil'; import { telemetry } from './telemetry'; +import { pid } from './utils/pid'; +import { sinonUtil } from './utils/sinonUtil'; +import { session } from './utils/session'; describe('Telemetry', () => { + let spawnStub: sinon.SinonStub; + let stdin: string = ''; + + before(() => { + spawnStub = sinon.stub(child_process, 'spawn').callsFake(() => { + return { + stdin: { + write: (s: string) => { + stdin += s; + }, + end: () => { } + }, + unref: () => { } + } as any; + }); + sinon.stub(pid, 'getProcessName').callsFake(() => ''); + sinon.stub(session, 'getId').callsFake(() => 'abc123'); + }); + afterEach(() => { sinonUtil.restore([ Cli.getInstance().getSettingWithDefaultValue, - appInsights.trackEvent, - appInsights.trackException + (telemetry as any).trackTelemetry ]); + spawnStub.resetHistory(); + stdin = ''; + }); + + after(() => { + sinon.restore(); }); - it(`doesn't log an event when disableTelemetry is set`, async () => { + it(`doesn't log an event when disableTelemetry is set`, () => { sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.disableTelemetry) { return true; @@ -23,14 +49,11 @@ describe('Telemetry', () => { return defaultValue; }); - const trackEventStub = sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); - telemetry.trackEvent('foo bar', {}); - - assert(trackEventStub.notCalled); + assert(spawnStub.notCalled); }); - it('logs an event when disableTelemetry is not set', async () => { + it('logs an event when disableTelemetry is not set', () => { sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.disableTelemetry) { return false; @@ -38,14 +61,11 @@ describe('Telemetry', () => { return defaultValue; }); - const trackEventStub = sinon.stub(appInsights, 'trackEvent').callsFake(() => { }); - telemetry.trackEvent('foo bar', {}); - - assert(trackEventStub.called); + assert(spawnStub.called); }); - it(`doesn't log an exception when disableTelemetry is set`, async () => { + it(`doesn't log an exception when disableTelemetry is set`, () => { sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.disableTelemetry) { return true; @@ -53,14 +73,23 @@ describe('Telemetry', () => { return defaultValue; }); - const exceptionStub = sinon.stub(appInsights, 'trackException').callsFake(() => { }); - telemetry.trackException('Error!'); + assert(spawnStub.notCalled); + }); - assert(exceptionStub.notCalled); + it('logs an exception when disableTelemetry is not set', () => { + sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.disableTelemetry) { + return false; + } + + return defaultValue; + }); + telemetry.trackException('Error!'); + assert(spawnStub.called); }); - it('logs an exception when disableTelemetry is not set', async () => { + it(`logs an empty string for shell if it couldn't resolve shell process name`, () => { sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.disableTelemetry) { return false; @@ -68,10 +97,29 @@ describe('Telemetry', () => { return defaultValue; }); - const trackExceptionStub = sinon.stub(appInsights, 'trackException').callsFake(() => { }); + sinonUtil.restore(pid.getProcessName); + sinon.stub(pid, 'getProcessName').callsFake(() => undefined); - telemetry.trackException('Error!'); + telemetry.trackEvent('foo bar', {}); + assert.strictEqual(JSON.parse(stdin).shell, ''); + }); + + it(`silently handles exception if an error occurs while spawning telemetry runner`, (done) => { + sinon.stub(Cli.getInstance(), 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.disableTelemetry) { + return false; + } - assert(trackExceptionStub.called); + return defaultValue; + }); + sinonUtil.restore(child_process.spawn); + sinon.stub(child_process, 'spawn').throws(); + try { + telemetry.trackEvent('foo bar', {}); + done(); + } + catch (e) { + done(e); + } }); }); \ No newline at end of file diff --git a/src/telemetry.ts b/src/telemetry.ts index 6ebc227c598..209287c970d 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -1,30 +1,46 @@ -import appInsights from "./appInsights"; -import { Cli } from "./cli/Cli"; -import { settingsNames } from "./settingsNames"; +import * as child_process from 'child_process'; +import * as path from 'path'; +import { Cli } from './cli/Cli'; +import { settingsNames } from './settingsNames'; +import { pid } from './utils/pid'; +import { session } from './utils/session'; -class Telemetry { - public trackEvent(commandName: string, properties: any): void { +function trackTelemetry(object: any): void { + try { + const child = child_process.spawn('node', [path.join(__dirname, 'telemetryRunner.js')], { + stdio: ['pipe', 'ignore', 'ignore'], + detached: true + }); + child.unref(); + + object.shell = pid.getProcessName(process.ppid) || ''; + object.session = session.getId(process.ppid); + + child.stdin.write(JSON.stringify(object)); + child.stdin.end(); + } + catch { } +} + +export const telemetry = { + trackEvent: (commandName: string, properties: any): void => { if (Cli.getInstance().getSettingWithDefaultValue(settingsNames.disableTelemetry, false)) { return; } - appInsights.trackEvent({ - name: commandName, + trackTelemetry({ + commandName, properties }); - appInsights.flush(); - } + }, - public trackException(exception: any): void { + trackException: (exception: any): void => { if (Cli.getInstance().getSettingWithDefaultValue(settingsNames.disableTelemetry, false)) { return; } - appInsights.trackException({ + trackTelemetry({ exception }); - appInsights.flush(); } -} - -export const telemetry = new Telemetry(); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/telemetryRunner.ts b/src/telemetryRunner.ts new file mode 100644 index 00000000000..4f1f0337fda --- /dev/null +++ b/src/telemetryRunner.ts @@ -0,0 +1,29 @@ +import appInsights from './appInsights.js'; +import * as process from 'process'; +import * as fs from 'fs'; + +process.stdin.setEncoding('utf8'); + +try { + // read from stdin + const input = fs.readFileSync(0, 'utf-8'); + const data = JSON.parse(input); + const { commandName, properties, exception, shell, session } = data; + + appInsights.commonProperties.shell = shell; + appInsights.context.tags['ai.session.id'] = session; + + if (exception) { + appInsights.trackException({ + exception + }); + } + else { + appInsights.trackEvent({ + name: commandName, + properties + }); + } + appInsights.flush(); +} +catch { } diff --git a/src/utils/session.spec.ts b/src/utils/session.spec.ts index f26ae12fe9d..034ec5967d0 100644 --- a/src/utils/session.spec.ts +++ b/src/utils/session.spec.ts @@ -15,4 +15,9 @@ describe('utils/session', () => { sinon.stub(cache, 'getValue').callsFake(() => '123'); assert.strictEqual(session.getId(1), '123'); }); + + it('generates new session ID if not available', () => { + sinon.stub(cache, 'getValue').returns(undefined); + assert(typeof session.getId(1) !== 'undefined'); + }); }); \ No newline at end of file