@microsoft/managed-apps-cli 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ArgumentProvider.d.ts +25 -1
- package/dist/ArgumentProvider.d.ts.map +1 -1
- package/dist/ArgumentProvider.js +80 -23
- package/dist/ArgumentProvider.js.map +1 -1
- package/dist/Authentication/ActiveAccountStore.d.ts +1 -1
- package/dist/Authentication/ActiveAccountStore.d.ts.map +1 -1
- package/dist/Authentication/ActiveAccountStore.js +3 -3
- package/dist/Authentication/ActiveAccountStore.js.map +1 -1
- package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts +16 -1
- package/dist/Authentication/NodeMsalAuthenticationProvider.d.ts.map +1 -1
- package/dist/Authentication/NodeMsalAuthenticationProvider.js +72 -11
- package/dist/Authentication/NodeMsalAuthenticationProvider.js.map +1 -1
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +15 -7
- package/dist/Cli.js.map +1 -1
- package/dist/CliUtils.d.ts +4 -7
- package/dist/CliUtils.d.ts.map +1 -1
- package/dist/CliUtils.js +2 -12
- package/dist/CliUtils.js.map +1 -1
- package/dist/Constants/HelpStrings.d.ts +1 -0
- package/dist/Constants/HelpStrings.d.ts.map +1 -1
- package/dist/Constants/HelpStrings.js +2 -0
- package/dist/Constants/HelpStrings.js.map +1 -1
- package/dist/Constants.d.ts +3 -0
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Constants.js +6 -0
- package/dist/Constants.js.map +1 -1
- package/dist/Errors/GitHubErrorMap.d.ts.map +1 -1
- package/dist/Errors/GitHubErrorMap.js +2 -0
- package/dist/Errors/GitHubErrorMap.js.map +1 -1
- package/dist/HttpClient/CliHttpClient.d.ts +0 -13
- package/dist/HttpClient/CliHttpClient.d.ts.map +1 -1
- package/dist/HttpClient/CliHttpClient.js +0 -24
- package/dist/HttpClient/CliHttpClient.js.map +1 -1
- package/dist/Logger/BufferedTelemetryWriter.d.ts +20 -0
- package/dist/Logger/BufferedTelemetryWriter.d.ts.map +1 -0
- package/dist/Logger/BufferedTelemetryWriter.js +163 -0
- package/dist/Logger/BufferedTelemetryWriter.js.map +1 -0
- package/dist/Logger/CliLogger.d.ts +28 -50
- package/dist/Logger/CliLogger.d.ts.map +1 -1
- package/dist/Logger/CliLogger.js +151 -122
- package/dist/Logger/CliLogger.js.map +1 -1
- package/dist/Logger/OneDSWriter.d.ts +8 -8
- package/dist/Logger/OneDSWriter.d.ts.map +1 -1
- package/dist/Logger/OneDSWriter.js +19 -17
- package/dist/Logger/OneDSWriter.js.map +1 -1
- package/dist/Types/Cli.types.d.ts +3 -2
- package/dist/Types/Cli.types.d.ts.map +1 -1
- package/dist/Types/LoggerTypes.types.d.ts +7 -7
- package/dist/Types/LoggerTypes.types.d.ts.map +1 -1
- package/dist/Utils/EnvironmentRoutingUtils.d.ts +1 -1
- package/dist/Utils/EnvironmentRoutingUtils.js +3 -3
- package/dist/Utils/EnvironmentRoutingUtils.js.map +1 -1
- package/dist/Utils/FindAvailablePort.d.ts +39 -0
- package/dist/Utils/FindAvailablePort.d.ts.map +1 -0
- package/dist/Utils/FindAvailablePort.js +105 -0
- package/dist/Utils/FindAvailablePort.js.map +1 -0
- package/dist/Verbs/CreateMaafApp.d.ts.map +1 -1
- package/dist/Verbs/CreateMaafApp.js +5 -6
- package/dist/Verbs/CreateMaafApp.js.map +1 -1
- package/dist/Verbs/DeleteMaafApp.d.ts.map +1 -1
- package/dist/Verbs/DeleteMaafApp.js +6 -10
- package/dist/Verbs/DeleteMaafApp.js.map +1 -1
- package/dist/Verbs/Dev.d.ts.map +1 -1
- package/dist/Verbs/Dev.js +37 -8
- package/dist/Verbs/Dev.js.map +1 -1
- package/dist/Verbs/GetMaafApp.js +2 -3
- package/dist/Verbs/GetMaafApp.js.map +1 -1
- package/dist/Verbs/Init.d.ts.map +1 -1
- package/dist/Verbs/Init.js +2 -2
- package/dist/Verbs/Init.js.map +1 -1
- package/dist/Verbs/ListMaafApps.d.ts.map +1 -1
- package/dist/Verbs/ListMaafApps.js +5 -5
- package/dist/Verbs/ListMaafApps.js.map +1 -1
- package/dist/Verbs/Login.d.ts.map +1 -1
- package/dist/Verbs/Login.js +9 -1
- package/dist/Verbs/Login.js.map +1 -1
- package/dist/Verbs/MaafAppPermissions.d.ts +1 -1
- package/dist/Verbs/MaafAppPermissions.js +15 -14
- package/dist/Verbs/MaafAppPermissions.js.map +1 -1
- package/dist/Verbs/RefreshGitAuth.d.ts.map +1 -1
- package/dist/Verbs/RefreshGitAuth.js +2 -3
- package/dist/Verbs/RefreshGitAuth.js.map +1 -1
- package/dist/Verbs/VerbConstants.d.ts +4 -0
- package/dist/Verbs/VerbConstants.d.ts.map +1 -1
- package/dist/Verbs/VerbConstants.js +9 -0
- package/dist/Verbs/VerbConstants.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { stripSensitiveDataFromObject, TelemetryEventNames, } from '@microsoft/managed-apps-common/telemetry';
|
|
8
|
+
import { TELEMETRY_DIRECTORY } from '../Constants.js';
|
|
9
|
+
import { getScrubbedCustomDimensions } from './Scrubber/ScrubCustomDimensions.js';
|
|
10
|
+
const telemetryJournalFileName = /^telemetry-\d+-[0-9a-f-]+\.json$/i;
|
|
11
|
+
const telemetryJournalRetentionMs = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
const maxTelemetryJournals = 20;
|
|
13
|
+
// Captures remote telemetry emitted before the command resolves its environment and cloud,
|
|
14
|
+
// so it can be replayed through the correctly routed OneDS writer afterward.
|
|
15
|
+
export class BufferedTelemetryWriter {
|
|
16
|
+
_events = [];
|
|
17
|
+
trackActivityEvent(eventName, eventData, timestamp) {
|
|
18
|
+
this._events.push({
|
|
19
|
+
method: 'trackActivityEvent',
|
|
20
|
+
args: [eventName, eventData],
|
|
21
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
trackErrorEvent(eventName, eventData, timestamp) {
|
|
25
|
+
this._events.push({
|
|
26
|
+
method: 'trackErrorEvent',
|
|
27
|
+
args: [eventName, eventData],
|
|
28
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
startScenario(scenarioName, startData, timestamp) {
|
|
32
|
+
this._events.push({
|
|
33
|
+
method: 'startScenario',
|
|
34
|
+
args: [scenarioName, startData],
|
|
35
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
progressScenario(scenarioName, progressData, timestamp) {
|
|
39
|
+
this._events.push({
|
|
40
|
+
method: 'progressScenario',
|
|
41
|
+
args: [scenarioName, progressData],
|
|
42
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
completeScenario(scenarioName, completeData, timestamp) {
|
|
46
|
+
this._events.push({
|
|
47
|
+
method: 'completeScenario',
|
|
48
|
+
args: [scenarioName, completeData],
|
|
49
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
scenarioFailed(scenarioName, failureData, timestamp) {
|
|
53
|
+
this._events.push({
|
|
54
|
+
method: 'scenarioFailed',
|
|
55
|
+
args: [scenarioName, failureData],
|
|
56
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
scenarioCompletedWithError(scenarioName, completeWithErrorData, timestamp) {
|
|
60
|
+
this._events.push({
|
|
61
|
+
method: 'scenarioCompletedWithError',
|
|
62
|
+
args: [scenarioName, completeWithErrorData],
|
|
63
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
flush() {
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
}
|
|
69
|
+
setCustomDimension(_key, _value) { }
|
|
70
|
+
replay(writer, transformEventData) {
|
|
71
|
+
for (const event of this._events) {
|
|
72
|
+
const [name, eventData] = event.args;
|
|
73
|
+
writer[event.method](name, transformEventData ? transformEventData(eventData) : eventData, event.timestamp);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
clear() {
|
|
77
|
+
this._events.length = 0;
|
|
78
|
+
}
|
|
79
|
+
writeToTemporaryFile(directory = TELEMETRY_DIRECTORY) {
|
|
80
|
+
if (this._events.length === 0) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
85
|
+
try {
|
|
86
|
+
cleanupTelemetryJournals(directory);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Retention is best effort and must not prevent persisting the current journal.
|
|
90
|
+
}
|
|
91
|
+
const filePath = path.join(directory, `telemetry-${Date.now()}-${randomUUID()}.json`);
|
|
92
|
+
const temporaryFilePath = `${filePath}.${process.pid}.tmp`;
|
|
93
|
+
fs.writeFileSync(temporaryFilePath, stripSensitiveDataFromObject({
|
|
94
|
+
events: this._events.map((event) => ({
|
|
95
|
+
...event,
|
|
96
|
+
args: [event.args[0], getScrubbedEventData(event)],
|
|
97
|
+
})),
|
|
98
|
+
}), 'utf-8');
|
|
99
|
+
fs.renameSync(temporaryFilePath, filePath);
|
|
100
|
+
return filePath;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function cleanupTelemetryJournals(directory) {
|
|
108
|
+
const expirationTime = Date.now() - telemetryJournalRetentionMs;
|
|
109
|
+
const journals = fs
|
|
110
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
111
|
+
.filter((entry) => entry.isFile() && telemetryJournalFileName.test(entry.name))
|
|
112
|
+
.map((entry) => {
|
|
113
|
+
const filePath = path.join(directory, entry.name);
|
|
114
|
+
return { filePath, modifiedTime: fs.statSync(filePath).mtimeMs };
|
|
115
|
+
});
|
|
116
|
+
const retainedJournals = journals
|
|
117
|
+
.filter((journal) => {
|
|
118
|
+
if (journal.modifiedTime >= expirationTime) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
fs.rmSync(journal.filePath);
|
|
122
|
+
return false;
|
|
123
|
+
})
|
|
124
|
+
.sort((left, right) => left.modifiedTime - right.modifiedTime);
|
|
125
|
+
const journalsToRemove = retainedJournals.slice(0, Math.max(0, retainedJournals.length - (maxTelemetryJournals - 1)));
|
|
126
|
+
for (const journal of journalsToRemove) {
|
|
127
|
+
fs.rmSync(journal.filePath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getScrubbedEventData(event) {
|
|
131
|
+
const [name, eventData] = event.args;
|
|
132
|
+
const eventName = getOneDsEventName(event.method);
|
|
133
|
+
const dimensions = event.method === 'trackActivityEvent' || event.method === 'trackErrorEvent'
|
|
134
|
+
? { ...eventData, operationName: name }
|
|
135
|
+
: {
|
|
136
|
+
operationName: name,
|
|
137
|
+
...eventData,
|
|
138
|
+
scenarioName: name,
|
|
139
|
+
...(event.method === 'completeScenario' && { scenarioResult: 'Success' }),
|
|
140
|
+
...(event.method === 'scenarioFailed' && { scenarioResult: 'Failed' }),
|
|
141
|
+
...(event.method === 'scenarioCompletedWithError' && {
|
|
142
|
+
scenarioResult: 'SuccessWithErrors',
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
return getScrubbedCustomDimensions(eventName, name, dimensions);
|
|
146
|
+
}
|
|
147
|
+
function getOneDsEventName(method) {
|
|
148
|
+
switch (method) {
|
|
149
|
+
case 'trackActivityEvent':
|
|
150
|
+
return TelemetryEventNames.Activity;
|
|
151
|
+
case 'trackErrorEvent':
|
|
152
|
+
return TelemetryEventNames.Error;
|
|
153
|
+
case 'startScenario':
|
|
154
|
+
return TelemetryEventNames.StartScenario;
|
|
155
|
+
case 'progressScenario':
|
|
156
|
+
return TelemetryEventNames.ProgressScenario;
|
|
157
|
+
case 'completeScenario':
|
|
158
|
+
case 'scenarioFailed':
|
|
159
|
+
case 'scenarioCompletedWithError':
|
|
160
|
+
return TelemetryEventNames.EndScenario;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=BufferedTelemetryWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BufferedTelemetryWriter.js","sourceRoot":"","sources":["../../src/Logger/BufferedTelemetryWriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,69 +1,47 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
+
import { type Region } from '@microsoft/managed-apps-common/services';
|
|
4
5
|
import type { ILogger, ScenarioTracker } from '@microsoft/managed-apps-common/telemetry';
|
|
5
|
-
import type { CliHttpClient } from '../HttpClient/CliHttpClient.js';
|
|
6
6
|
import type { CliSettings, IAuthenticationProvider } from '../Types/Cli.types.js';
|
|
7
7
|
import { type OneDsHttpForwardOptions } from './OneDSWriter.js';
|
|
8
|
+
export interface TelemetryContext {
|
|
9
|
+
region: Region;
|
|
10
|
+
environmentId?: string;
|
|
11
|
+
}
|
|
8
12
|
/**
|
|
9
13
|
* Logger implementation for the CLI.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* start flowing immediately without any auth dependency. The writer is then upgraded to
|
|
14
|
-
* the EU-aware authed version via one of two paths:
|
|
15
|
-
*
|
|
16
|
-
* - `tryEagerUpgradeAsync` — called from `runVerb` after MSAL is initialized. If a
|
|
17
|
-
* cached account already exists, the upgrade runs synchronously before the first event
|
|
18
|
-
* so signed-in users get authed telemetry for the entire session.
|
|
19
|
-
* - `CliHttpClient.onFirstAuth` callback — registered in `create()`, fires (background)
|
|
20
|
-
* after the verb's first successful authed HTTP call. Handles users who sign in
|
|
21
|
-
* mid-session via MSAL.
|
|
22
|
-
*
|
|
23
|
-
* Verbs that never make an authed HTTP call and have no cached account stay on the
|
|
24
|
-
* unauthenticated writer for their whole lifetime — events still reach 1DS, just on
|
|
25
|
-
* the global endpoint with no `geoLocation` or `tenantId`.
|
|
26
|
-
*
|
|
27
|
-
* 1DS event submission uses its own ingestion credentials (not the Entra bearer token),
|
|
28
|
-
* so events can be sent regardless of sign-in state.
|
|
15
|
+
* Remote telemetry is buffered until the command resolves its target environment and region.
|
|
16
|
+
* Console telemetry is initialized immediately when enabled.
|
|
29
17
|
*/
|
|
30
18
|
export declare class CliLogger implements ILogger {
|
|
31
19
|
private _telemetryWriters;
|
|
32
|
-
private
|
|
20
|
+
private _bufferedWriter?;
|
|
21
|
+
private _remoteWriter?;
|
|
33
22
|
private readonly _customDimensions;
|
|
34
|
-
private _upgradeFired;
|
|
35
23
|
private readonly _sessionId;
|
|
36
|
-
private
|
|
24
|
+
private readonly _origin;
|
|
25
|
+
private readonly _authenticationProvider;
|
|
26
|
+
private readonly _appId;
|
|
27
|
+
private readonly _httpForwardOptions?;
|
|
28
|
+
private _telemetryContext?;
|
|
29
|
+
private _environmentMetadataPromise?;
|
|
30
|
+
private _isRemoteTelemetrySuppressed;
|
|
31
|
+
private _isEnvironmentlessTelemetry;
|
|
37
32
|
private constructor();
|
|
38
|
-
static create(cliSettings: CliSettings,
|
|
39
|
-
|
|
40
|
-
* Eagerly run the upgrade when a cached account already exists at verb-start time,
|
|
41
|
-
* so even the first telemetry event of the session lands on the authenticated writer.
|
|
42
|
-
* Called from `ArgumentProvider.runVerb` after `authProvider.initAsync(region)` but
|
|
43
|
-
* before any event is emitted.
|
|
44
|
-
*
|
|
45
|
-
* No-op if no cached account is present (we don't want to block CLI startup on a
|
|
46
|
-
* network call when the user isn't even signed in), or if the upgrade has already
|
|
47
|
-
* fired. For not-signed-in users the lazy `onFirstAuth` path still handles things
|
|
48
|
-
* later if the verb itself triggers a sign-in.
|
|
49
|
-
*
|
|
50
|
-
* Cost: ~100-500ms for signed-in users — the time `getEnvironmentByName` takes.
|
|
51
|
-
* Signed-in-but-offline users will block on the default fetch timeout before
|
|
52
|
-
* falling through; events still flow to the unauthenticated writer in that case.
|
|
53
|
-
*/
|
|
54
|
-
tryEagerUpgradeAsync(authenticationProvider: IAuthenticationProvider): Promise<void>;
|
|
33
|
+
static create(cliSettings: CliSettings, authenticationProvider: IAuthenticationProvider, httpForwardOptions?: OneDsHttpForwardOptions): Promise<CliLogger>;
|
|
34
|
+
setCustomDimension(_key: string, _value: string): void;
|
|
55
35
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* - `tryEagerUpgradeAsync` (blocking, before the first event) when a cached account
|
|
59
|
-
* is already present at verb-start time.
|
|
60
|
-
* - `CliHttpClient.onFirstAuth` (fire-and-forget) after the verb's first successful
|
|
61
|
-
* authed HTTP call, when the user signed in mid-session.
|
|
62
|
-
* Best-effort — on any failure the unauthenticated writer stays in place and events
|
|
63
|
-
* keep flowing to the global endpoint.
|
|
36
|
+
* Records the command's resolved routing context and activates the remote writer once the
|
|
37
|
+
* environment metadata is available.
|
|
64
38
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
39
|
+
setTelemetryContext(context: TelemetryContext): Promise<void>;
|
|
40
|
+
private _getEnvironmentMetadataAsync;
|
|
41
|
+
private _activateRemoteWriter;
|
|
42
|
+
private _activateEnvironmentlessRemoteWriter;
|
|
43
|
+
private _createRemoteWriterAndReplay;
|
|
44
|
+
writeBufferedTelemetryToFile(directory?: string): string | undefined;
|
|
67
45
|
trackActivityEvent(eventName: string, eventData?: object): void;
|
|
68
46
|
trackErrorEvent(eventName: string, eventData?: object): void;
|
|
69
47
|
trackScenario(eventName: string, eventData?: object): ScenarioTracker;
|
|
@@ -74,7 +52,7 @@ export declare class CliLogger implements ILogger {
|
|
|
74
52
|
}): void;
|
|
75
53
|
get sessionId(): string | undefined;
|
|
76
54
|
set sessionId(_value: string);
|
|
77
|
-
flush(): Promise<void>;
|
|
55
|
+
flush(retainBufferedTelemetry?: boolean): Promise<void>;
|
|
78
56
|
withDefaults(_extras: Record<string, string | number | boolean>): ILogger;
|
|
79
57
|
addDefaults(_extras: Record<string, string | number | boolean>): void;
|
|
80
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CliLogger.d.ts","sourceRoot":"","sources":["../../src/Logger/CliLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"CliLogger.d.ts","sourceRoot":"","sources":["../../src/Logger/CliLogger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,KAAK,MAAM,EAA2B,MAAM,yCAAyC,CAAC;AAC/F,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAGzF,OAAO,KAAK,EACV,WAAW,EACX,uBAAuB,EAExB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,KAAK,uBAAuB,EAAe,MAAM,eAAe,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwID;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,eAAe,CAAC,CAA0B;IAClD,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA8B;IAChE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAA0B;IAC/D,OAAO,CAAC,iBAAiB,CAAC,CAAmB;IAC7C,OAAO,CAAC,2BAA2B,CAAC,CAElC;IACF,OAAO,CAAC,4BAA4B,CAAS;IAC7C,OAAO,CAAC,2BAA2B,CAAS;IAE5C,OAAO;WAgBa,MAAM,CACxB,WAAW,EAAE,WAAW,EACxB,sBAAsB,EAAE,uBAAuB,EAC/C,kBAAkB,CAAC,EAAE,uBAAuB,GAC3C,OAAO,CAAC,SAAS,CAAC;IA4Cd,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAiB7D;;;OAGG;IACU,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAuB5D,4BAA4B;YAoB5B,qBAAqB;IAuBnC,OAAO,CAAC,oCAAoC;IAc5C,OAAO,CAAC,4BAA4B;IA6C7B,4BAA4B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAGpE,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW/D,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAW5D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe;IAuBrE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAI5D,YAAY,CAAC,SAAS,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5E,IAAW,SAAS,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED,IAAW,SAAS,CAAC,MAAM,EAAE,MAAM,EAElC;IAEY,KAAK,CAAC,uBAAuB,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3D,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO;IAOzE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;CAG7E"}
|
package/dist/Logger/CliLogger.js
CHANGED
|
@@ -3,13 +3,39 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { getEnvironmentByName } from '@microsoft/managed-apps-actions';
|
|
6
|
-
import {
|
|
6
|
+
import { regionToClusterCategory } from '@microsoft/managed-apps-common/services';
|
|
7
7
|
import packageJson from '../../package.json' with { type: 'json' };
|
|
8
8
|
import { getCliEnvVarBool } from '../Utils/EnvVars.js';
|
|
9
|
+
import { BufferedTelemetryWriter } from './BufferedTelemetryWriter.js';
|
|
9
10
|
import { ConsoleWriter } from './ConsoleWriter.js';
|
|
10
11
|
import { EUGeoRegions } from './Constants.js';
|
|
11
12
|
import { resolveUserTelemetrySettings } from './LoggerSettings.js';
|
|
12
13
|
import { OneDSWriter } from './OneDSWriter.js';
|
|
14
|
+
const environmentlessIdentifierDimensions = new Set(['environmentid', 'tenantid', 'objectid']);
|
|
15
|
+
function isEnvironmentlessIdentifierDimension(key) {
|
|
16
|
+
return environmentlessIdentifierDimensions.has(key.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
function removeEnvironmentlessIdentifierDimensions(eventData) {
|
|
19
|
+
if (!eventData) {
|
|
20
|
+
return eventData;
|
|
21
|
+
}
|
|
22
|
+
return Object.fromEntries(Object.entries(eventData).filter(([key]) => !isEnvironmentlessIdentifierDimension(key)));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if the environment's cluster category is compatible with the region's cluster
|
|
26
|
+
* category. First Release environments are part of the public production cloud.
|
|
27
|
+
* If the environment has no cluster category, it is considered a match for all regions.
|
|
28
|
+
*
|
|
29
|
+
* This is required to handle the edge case when a command is run against a non-prod environment
|
|
30
|
+
* without providing the --cloud param.
|
|
31
|
+
*/
|
|
32
|
+
function doesEnvironmentMatchRegion(metadata, region) {
|
|
33
|
+
const environmentClusterCategory = metadata?.properties.cluster?.category?.toLowerCase();
|
|
34
|
+
const regionClusterCategory = regionToClusterCategory(region);
|
|
35
|
+
return (!environmentClusterCategory ||
|
|
36
|
+
environmentClusterCategory === regionClusterCategory ||
|
|
37
|
+
(regionClusterCategory === 'prod' && environmentClusterCategory === 'firstrelease'));
|
|
38
|
+
}
|
|
13
39
|
function stringifyErrorWithStack(error) {
|
|
14
40
|
if (!error)
|
|
15
41
|
return undefined;
|
|
@@ -99,169 +125,161 @@ function createScenarioLogger(telemetryWriters, name, data) {
|
|
|
99
125
|
/**
|
|
100
126
|
* Logger implementation for the CLI.
|
|
101
127
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* start flowing immediately without any auth dependency. The writer is then upgraded to
|
|
105
|
-
* the EU-aware authed version via one of two paths:
|
|
106
|
-
*
|
|
107
|
-
* - `tryEagerUpgradeAsync` — called from `runVerb` after MSAL is initialized. If a
|
|
108
|
-
* cached account already exists, the upgrade runs synchronously before the first event
|
|
109
|
-
* so signed-in users get authed telemetry for the entire session.
|
|
110
|
-
* - `CliHttpClient.onFirstAuth` callback — registered in `create()`, fires (background)
|
|
111
|
-
* after the verb's first successful authed HTTP call. Handles users who sign in
|
|
112
|
-
* mid-session via MSAL.
|
|
113
|
-
*
|
|
114
|
-
* Verbs that never make an authed HTTP call and have no cached account stay on the
|
|
115
|
-
* unauthenticated writer for their whole lifetime — events still reach 1DS, just on
|
|
116
|
-
* the global endpoint with no `geoLocation` or `tenantId`.
|
|
117
|
-
*
|
|
118
|
-
* 1DS event submission uses its own ingestion credentials (not the Entra bearer token),
|
|
119
|
-
* so events can be sent regardless of sign-in state.
|
|
128
|
+
* Remote telemetry is buffered until the command resolves its target environment and region.
|
|
129
|
+
* Console telemetry is initialized immediately when enabled.
|
|
120
130
|
*/
|
|
121
131
|
export class CliLogger {
|
|
122
132
|
_telemetryWriters = [];
|
|
123
|
-
|
|
133
|
+
_bufferedWriter;
|
|
134
|
+
_remoteWriter;
|
|
124
135
|
_customDimensions = {};
|
|
125
|
-
_upgradeFired = false;
|
|
126
136
|
_sessionId;
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
_origin;
|
|
138
|
+
_authenticationProvider;
|
|
139
|
+
_appId;
|
|
140
|
+
_httpForwardOptions;
|
|
141
|
+
_telemetryContext;
|
|
142
|
+
_environmentMetadataPromise;
|
|
143
|
+
_isRemoteTelemetrySuppressed = false;
|
|
144
|
+
_isEnvironmentlessTelemetry = false;
|
|
145
|
+
constructor(telemetryWriters, sessionId, origin, authenticationProvider, appId, httpForwardOptions) {
|
|
129
146
|
this._telemetryWriters = telemetryWriters;
|
|
130
147
|
this._sessionId = sessionId;
|
|
148
|
+
this._origin = origin;
|
|
149
|
+
this._authenticationProvider = authenticationProvider;
|
|
150
|
+
this._appId = appId ?? '';
|
|
151
|
+
this._httpForwardOptions = httpForwardOptions;
|
|
131
152
|
}
|
|
132
|
-
static async create(cliSettings,
|
|
133
|
-
const environmentId = cliSettings.appConfig?.environmentId;
|
|
134
|
-
const region = cloudToRegionSafe(cliSettings.appConfig?.cloud);
|
|
135
|
-
const appId = cliSettings.appConfig?.appId;
|
|
153
|
+
static async create(cliSettings, authenticationProvider, httpForwardOptions) {
|
|
136
154
|
const sessionId = randomUUID();
|
|
137
155
|
const telemetryWriters = [];
|
|
138
156
|
const userTelemetrySettings = resolveUserTelemetrySettings();
|
|
139
157
|
if (userTelemetrySettings.console) {
|
|
140
158
|
telemetryWriters.push(new ConsoleWriter(sessionId, cliSettings.origin));
|
|
141
159
|
}
|
|
142
|
-
const instance = new CliLogger(telemetryWriters, sessionId);
|
|
160
|
+
const instance = new CliLogger(telemetryWriters, sessionId, cliSettings.origin, authenticationProvider, cliSettings.appConfig?.appId, httpForwardOptions);
|
|
143
161
|
// In mocked-backend mode (E2E) never open a real 1DS/remote telemetry channel: it bypasses
|
|
144
162
|
// the injected mock HttpClient and would attempt live network egress, hanging the CLI on
|
|
145
163
|
// flush at exit. This keeps "mock all backend calls" complete for E2E runs.
|
|
146
164
|
const suppressRemoteTelemetry = getCliEnvVarBool('MOCK_BACKEND');
|
|
147
|
-
if (userTelemetrySettings.remote &&
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// writer below.
|
|
151
|
-
const unauth = new OneDSWriter(sessionId, region, 'global', environmentId, appId ?? '', undefined, packageJson.version, cliSettings.origin, httpForwardOptions);
|
|
152
|
-
instance._unauthWriter = unauth;
|
|
153
|
-
instance._telemetryWriters.push(unauth);
|
|
154
|
-
instance._upgradeConfig = {
|
|
155
|
-
sessionId,
|
|
156
|
-
region,
|
|
157
|
-
environmentId,
|
|
158
|
-
appId,
|
|
159
|
-
cliSettings,
|
|
160
|
-
httpForwardOptions,
|
|
161
|
-
};
|
|
162
|
-
// Register the lazy upgrade: fires once when the verb makes its first successful
|
|
163
|
-
// authed HTTP call. By that point we know auth works, so the metadata fetch can't
|
|
164
|
-
// pop a browser.
|
|
165
|
-
httpClient.onFirstAuth(() => instance._upgradeToAuthedWriterAsync(authenticationProvider));
|
|
165
|
+
if (userTelemetrySettings.remote && !suppressRemoteTelemetry) {
|
|
166
|
+
instance._bufferedWriter = new BufferedTelemetryWriter();
|
|
167
|
+
instance._telemetryWriters.push(instance._bufferedWriter);
|
|
166
168
|
}
|
|
167
169
|
// Emit the first-party embedding host correlation id (if supplied via
|
|
168
170
|
// MS_CLI_EMBEDDING_HOST_CORRELATION_ID) on every event. It is injected as a default
|
|
169
171
|
// property here and promoted to a dedicated top-level telemetry column
|
|
170
|
-
// (`embeddingHostCorrelationId`) by the OneDS writer's propertyColumnMap.
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
// `sessionId` is intentionally left untouched.
|
|
172
|
+
// (`embeddingHostCorrelationId`) by the OneDS writer's propertyColumnMap. Preserve it until
|
|
173
|
+
// the delayed remote writer is created, while applying it to the console writer immediately.
|
|
174
|
+
// The internal per-invocation `sessionId` is intentionally left untouched.
|
|
174
175
|
if (cliSettings.embeddingHostCorrelationId) {
|
|
175
176
|
instance.setCustomDimension('embeddingHostCorrelationId', cliSettings.embeddingHostCorrelationId);
|
|
176
177
|
}
|
|
177
178
|
return instance;
|
|
178
179
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
* Signed-in-but-offline users will block on the default fetch timeout before
|
|
192
|
-
* falling through; events still flow to the unauthenticated writer in that case.
|
|
193
|
-
*/
|
|
194
|
-
async tryEagerUpgradeAsync(authenticationProvider) {
|
|
195
|
-
if (this._upgradeFired || !this._upgradeConfig) {
|
|
196
|
-
return;
|
|
180
|
+
setCustomDimension(_key, _value) {
|
|
181
|
+
try {
|
|
182
|
+
if (!_key || !_value) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (this._isEnvironmentlessTelemetry && isEnvironmentlessIdentifierDimension(_key)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this._customDimensions[_key] = _value;
|
|
189
|
+
this._telemetryWriters.forEach((writer) => {
|
|
190
|
+
writer.setCustomDimension(_key, _value);
|
|
191
|
+
});
|
|
197
192
|
}
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
catch {
|
|
194
|
+
// No-op. Telemetry failures should not block CLI operations
|
|
200
195
|
}
|
|
201
196
|
}
|
|
202
197
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* - `tryEagerUpgradeAsync` (blocking, before the first event) when a cached account
|
|
206
|
-
* is already present at verb-start time.
|
|
207
|
-
* - `CliHttpClient.onFirstAuth` (fire-and-forget) after the verb's first successful
|
|
208
|
-
* authed HTTP call, when the user signed in mid-session.
|
|
209
|
-
* Best-effort — on any failure the unauthenticated writer stays in place and events
|
|
210
|
-
* keep flowing to the global endpoint.
|
|
198
|
+
* Records the command's resolved routing context and activates the remote writer once the
|
|
199
|
+
* environment metadata is available.
|
|
211
200
|
*/
|
|
212
|
-
async
|
|
213
|
-
if (
|
|
201
|
+
async setTelemetryContext(context) {
|
|
202
|
+
if (!this._telemetryContext) {
|
|
203
|
+
this._telemetryContext = context;
|
|
204
|
+
}
|
|
205
|
+
else if (!this._telemetryContext.environmentId && context.environmentId) {
|
|
206
|
+
// The initial region is authoritative. Environment resolution may happen later.
|
|
207
|
+
this._telemetryContext = {
|
|
208
|
+
...this._telemetryContext,
|
|
209
|
+
environmentId: context.environmentId,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Context already complete; avoid redundant metadata lookups.
|
|
214
214
|
return;
|
|
215
215
|
}
|
|
216
|
-
this.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const idx = this._telemetryWriters.indexOf(this._unauthWriter);
|
|
228
|
-
if (idx >= 0) {
|
|
229
|
-
this._telemetryWriters[idx] = authed;
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
this._telemetryWriters.push(authed);
|
|
233
|
-
}
|
|
234
|
-
this._unauthWriter = authed;
|
|
235
|
-
// Re-apply previously set dimensions to the replacement writer.
|
|
236
|
-
for (const [key, value] of Object.entries(this._customDimensions)) {
|
|
237
|
-
authed.setCustomDimension(key, value);
|
|
238
|
-
}
|
|
239
|
-
// Set the tenantId dimension AFTER the swap so it lands on the authed writer
|
|
240
|
-
// (and any ConsoleWriter that's still in the list), not the discarded unauth one.
|
|
241
|
-
const tenantId = authenticationProvider.getUserTenantId();
|
|
242
|
-
if (tenantId) {
|
|
243
|
-
this.setCustomDimension('tenantId', tenantId);
|
|
216
|
+
if (this._telemetryContext.environmentId) {
|
|
217
|
+
const metadata = await this._getEnvironmentMetadataAsync(this._telemetryContext.environmentId, this._telemetryContext.region);
|
|
218
|
+
await this._activateRemoteWriter(metadata);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async _getEnvironmentMetadataAsync(environmentId, region) {
|
|
222
|
+
this._environmentMetadataPromise ??= getEnvironmentByName(environmentId)
|
|
223
|
+
.then((metadata) => {
|
|
224
|
+
if (!metadata.location || !doesEnvironmentMatchRegion(metadata, region)) {
|
|
225
|
+
this._isRemoteTelemetrySuppressed = true;
|
|
226
|
+
return undefined;
|
|
244
227
|
}
|
|
228
|
+
return metadata;
|
|
229
|
+
})
|
|
230
|
+
.catch(() => {
|
|
231
|
+
this._isRemoteTelemetrySuppressed = true;
|
|
232
|
+
return undefined;
|
|
233
|
+
});
|
|
234
|
+
return this._environmentMetadataPromise;
|
|
235
|
+
}
|
|
236
|
+
async _activateRemoteWriter(environmentMetadata) {
|
|
237
|
+
if (!this._bufferedWriter ||
|
|
238
|
+
!this._telemetryContext?.environmentId ||
|
|
239
|
+
!environmentMetadata ||
|
|
240
|
+
!environmentMetadata.location ||
|
|
241
|
+
this._isRemoteTelemetrySuppressed) {
|
|
242
|
+
// Keep unresolved or mismatched-environment events buffered for CLI teardown to persist.
|
|
243
|
+
return;
|
|
245
244
|
}
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
const telemetryLocation = EUGeoRegions.has(environmentMetadata.location) ? 'eu' : 'global';
|
|
246
|
+
this._createRemoteWriterAndReplay(this._telemetryContext.region, telemetryLocation, this._telemetryContext.environmentId, environmentMetadata.properties.cluster?.geoShortName);
|
|
247
|
+
}
|
|
248
|
+
_activateEnvironmentlessRemoteWriter() {
|
|
249
|
+
if (!this._bufferedWriter || this._telemetryContext?.environmentId) {
|
|
250
|
+
return;
|
|
248
251
|
}
|
|
252
|
+
this._isEnvironmentlessTelemetry = true;
|
|
253
|
+
this._createRemoteWriterAndReplay(this._telemetryContext?.region ?? 'prod', 'global', undefined, undefined);
|
|
249
254
|
}
|
|
250
|
-
|
|
255
|
+
_createRemoteWriterAndReplay(region, telemetryLocation, environmentId, geoLocation) {
|
|
256
|
+
if (!this._bufferedWriter || this._remoteWriter) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
251
259
|
try {
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
const remoteWriter = new OneDSWriter(this._sessionId, region, telemetryLocation, environmentId, this._appId, geoLocation, packageJson.version, this._origin, this._httpForwardOptions);
|
|
261
|
+
this._remoteWriter = remoteWriter;
|
|
262
|
+
for (const [key, value] of Object.entries(this._customDimensions)) {
|
|
263
|
+
if (!this._isEnvironmentlessTelemetry || !isEnvironmentlessIdentifierDimension(key)) {
|
|
264
|
+
remoteWriter.setCustomDimension(key, value);
|
|
265
|
+
}
|
|
254
266
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
if (!this._isEnvironmentlessTelemetry) {
|
|
268
|
+
const tenantId = this._authenticationProvider.getUserTenantId();
|
|
269
|
+
if (tenantId) {
|
|
270
|
+
remoteWriter.setCustomDimension('tenantId', tenantId);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this._bufferedWriter.replay(remoteWriter, this._isEnvironmentlessTelemetry ? removeEnvironmentlessIdentifierDimensions : undefined);
|
|
274
|
+
this._telemetryWriters.push(remoteWriter);
|
|
260
275
|
}
|
|
261
276
|
catch {
|
|
262
|
-
//
|
|
277
|
+
// Keep the buffer intact; CLI teardown persists it after the command session ends.
|
|
263
278
|
}
|
|
264
279
|
}
|
|
280
|
+
writeBufferedTelemetryToFile(directory) {
|
|
281
|
+
return this._bufferedWriter?.writeToTemporaryFile(directory);
|
|
282
|
+
}
|
|
265
283
|
trackActivityEvent(eventName, eventData) {
|
|
266
284
|
try {
|
|
267
285
|
if (this._telemetryWriters.length === 0) {
|
|
@@ -319,8 +337,19 @@ export class CliLogger {
|
|
|
319
337
|
set sessionId(_value) {
|
|
320
338
|
// No-op
|
|
321
339
|
}
|
|
322
|
-
flush() {
|
|
323
|
-
|
|
340
|
+
async flush(retainBufferedTelemetry = false) {
|
|
341
|
+
if (this._environmentMetadataPromise) {
|
|
342
|
+
// Ensure any in-flight metadata lookup can activate and replay buffered remote telemetry.
|
|
343
|
+
const metadata = await this._environmentMetadataPromise;
|
|
344
|
+
await this._activateRemoteWriter(metadata);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
this._activateEnvironmentlessRemoteWriter();
|
|
348
|
+
}
|
|
349
|
+
await Promise.all(this._telemetryWriters.map((writer) => writer.flush()));
|
|
350
|
+
if (this._remoteWriter && !retainBufferedTelemetry) {
|
|
351
|
+
this._bufferedWriter?.clear();
|
|
352
|
+
}
|
|
324
353
|
}
|
|
325
354
|
withDefaults(_extras) {
|
|
326
355
|
// No-op — CLI logger does not implement local scope defaults today.
|