@microsoft/power-apps-native-common 0.1.8
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/LICENSE +112 -0
- package/README.md +15 -0
- package/lib/NOTICE.txt +179 -0
- package/lib/commonjs/TelemetryTypes.js +29 -0
- package/lib/commonjs/index.js +112 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/telemetry/AppLogger.js +49 -0
- package/lib/commonjs/telemetry/ConsoleAdapter.js +64 -0
- package/lib/commonjs/telemetry/ILogger.js +5 -0
- package/lib/commonjs/telemetry/Logger.js +151 -0
- package/lib/commonjs/telemetry/OneDsTelemetryProvider.js +276 -0
- package/lib/commonjs/telemetry/TelemetrySanitizer.js +100 -0
- package/lib/commonjs/telemetry/TelemetryTypes.js +29 -0
- package/lib/commonjs/telemetry/resolveTelemetryLocation.js +42 -0
- package/lib/commonjs/telemetry/stringifyError.js +31 -0
- package/lib/commonjs/telemetry/suppressKnownWarnings.js +28 -0
- package/lib/commonjs/types/ClusterCategory.js +54 -0
- package/lib/commonjs/utils/DeviceIdUtils.js +66 -0
- package/lib/commonjs/utils/GuidUtils.js +23 -0
- package/lib/commonjs/utils/PlatformInfoUtils.js +60 -0
- package/lib/module/TelemetryTypes.js +25 -0
- package/lib/module/index.js +18 -0
- package/lib/module/package.json +1 -0
- package/lib/module/telemetry/AppLogger.js +44 -0
- package/lib/module/telemetry/ConsoleAdapter.js +59 -0
- package/lib/module/telemetry/ILogger.js +3 -0
- package/lib/module/telemetry/Logger.js +146 -0
- package/lib/module/telemetry/OneDsTelemetryProvider.js +271 -0
- package/lib/module/telemetry/TelemetrySanitizer.js +94 -0
- package/lib/module/telemetry/TelemetryTypes.js +25 -0
- package/lib/module/telemetry/resolveTelemetryLocation.js +38 -0
- package/lib/module/telemetry/stringifyError.js +28 -0
- package/lib/module/telemetry/suppressKnownWarnings.js +25 -0
- package/lib/module/types/ClusterCategory.js +49 -0
- package/lib/module/utils/DeviceIdUtils.js +62 -0
- package/lib/module/utils/GuidUtils.js +19 -0
- package/lib/module/utils/PlatformInfoUtils.js +55 -0
- package/lib/typescript/commonjs/TelemetryTypes.d.ts +88 -0
- package/lib/typescript/commonjs/index.d.ts +19 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/telemetry/AppLogger.d.ts +35 -0
- package/lib/typescript/commonjs/telemetry/ConsoleAdapter.d.ts +24 -0
- package/lib/typescript/commonjs/telemetry/ILogger.d.ts +45 -0
- package/lib/typescript/commonjs/telemetry/Logger.d.ts +28 -0
- package/lib/typescript/commonjs/telemetry/OneDsTelemetryProvider.d.ts +39 -0
- package/lib/typescript/commonjs/telemetry/TelemetrySanitizer.d.ts +21 -0
- package/lib/typescript/commonjs/telemetry/TelemetryTypes.d.ts +88 -0
- package/lib/typescript/commonjs/telemetry/resolveTelemetryLocation.d.ts +26 -0
- package/lib/typescript/commonjs/telemetry/stringifyError.d.ts +10 -0
- package/lib/typescript/commonjs/telemetry/suppressKnownWarnings.d.ts +19 -0
- package/lib/typescript/commonjs/types/ClusterCategory.d.ts +37 -0
- package/lib/typescript/commonjs/utils/DeviceIdUtils.d.ts +29 -0
- package/lib/typescript/commonjs/utils/GuidUtils.d.ts +11 -0
- package/lib/typescript/commonjs/utils/PlatformInfoUtils.d.ts +33 -0
- package/lib/typescript/module/TelemetryTypes.d.ts +88 -0
- package/lib/typescript/module/index.d.ts +19 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/telemetry/AppLogger.d.ts +35 -0
- package/lib/typescript/module/telemetry/ConsoleAdapter.d.ts +24 -0
- package/lib/typescript/module/telemetry/ILogger.d.ts +45 -0
- package/lib/typescript/module/telemetry/Logger.d.ts +28 -0
- package/lib/typescript/module/telemetry/OneDsTelemetryProvider.d.ts +39 -0
- package/lib/typescript/module/telemetry/TelemetrySanitizer.d.ts +21 -0
- package/lib/typescript/module/telemetry/TelemetryTypes.d.ts +88 -0
- package/lib/typescript/module/telemetry/resolveTelemetryLocation.d.ts +26 -0
- package/lib/typescript/module/telemetry/stringifyError.d.ts +10 -0
- package/lib/typescript/module/telemetry/suppressKnownWarnings.d.ts +19 -0
- package/lib/typescript/module/types/ClusterCategory.d.ts +37 -0
- package/lib/typescript/module/utils/DeviceIdUtils.d.ts +29 -0
- package/lib/typescript/module/utils/GuidUtils.d.ts +11 -0
- package/lib/typescript/module/utils/PlatformInfoUtils.d.ts +33 -0
- package/package.json +83 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Regions = void 0;
|
|
7
|
+
exports.regionToClusterCategory = regionToClusterCategory;
|
|
8
|
+
/*!
|
|
9
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Identifies the Power Platform cluster environment category.
|
|
14
|
+
* Used to resolve telemetry endpoints, instrumentation keys, and collector URLs.
|
|
15
|
+
*
|
|
16
|
+
* This is the **normalised** internal form used by telemetry providers.
|
|
17
|
+
* The raw `region` string from power.config.json may differ — use
|
|
18
|
+
* `regionToClusterCategory()` to convert before passing here.
|
|
19
|
+
*
|
|
20
|
+
* Canonical power.config.json `region` values → ClusterCategory mapping:
|
|
21
|
+
* preview → prod
|
|
22
|
+
* gccmoderate → gov
|
|
23
|
+
* gcchigh → high
|
|
24
|
+
* (all others are identity mappings)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* All valid `region` strings accepted in power.config.json.
|
|
29
|
+
* Mirrors the `Region` type from `@microsoft/power-apps-common/services`.
|
|
30
|
+
*/
|
|
31
|
+
const Regions = exports.Regions = ['local', 'dev', 'test', 'preprod', 'preview', 'prod', 'firstrelease', 'gccmoderate', 'gcchigh', 'dod', 'mooncake', 'ex', 'rx'];
|
|
32
|
+
/**
|
|
33
|
+
* Normalise a power.config.json `region` string to the internal
|
|
34
|
+
* `ClusterCategory` used by telemetry providers.
|
|
35
|
+
*
|
|
36
|
+
* Mirrors the CLI's `regionToClusterCategory` in OneDSWriter.ts:
|
|
37
|
+
* preview → prod (preview ring uses production telemetry)
|
|
38
|
+
* gccmoderate → gov (GCC Moderate sovereign cloud)
|
|
39
|
+
* gcchigh → high (GCC High sovereign cloud)
|
|
40
|
+
* firstrelease → firstrelease (first-release prod ring)
|
|
41
|
+
* all others → identity
|
|
42
|
+
*/
|
|
43
|
+
function regionToClusterCategory(region) {
|
|
44
|
+
switch (region) {
|
|
45
|
+
case 'preview':
|
|
46
|
+
return 'prod';
|
|
47
|
+
case 'gccmoderate':
|
|
48
|
+
return 'gov';
|
|
49
|
+
case 'gcchigh':
|
|
50
|
+
return 'high';
|
|
51
|
+
default:
|
|
52
|
+
return region;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolveDeviceId = resolveDeviceId;
|
|
7
|
+
var Application = _interopRequireWildcard(require("expo-application"));
|
|
8
|
+
var SecureStore = _interopRequireWildcard(require("expo-secure-store"));
|
|
9
|
+
var _reactNative = require("react-native");
|
|
10
|
+
var _GuidUtils = require("./GuidUtils.js");
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
|
+
/*!
|
|
13
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Fallback key used when the platform native ID is unavailable.
|
|
17
|
+
// Only alphanumeric, dots, hyphens, and underscores are valid for SecureStore.
|
|
18
|
+
const DEVICE_ID_FALLBACK_KEY = 'microsoft.power-apps-native-common.device_id';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns a stable device identifier, preferring platform-native IDs that
|
|
22
|
+
* survive app reinstalls:
|
|
23
|
+
*
|
|
24
|
+
* - **iOS**: `getIosIdForVendorAsync()` (IDFV) — stable while ≥1 app from the
|
|
25
|
+
* same vendor bundle prefix is installed. Resets only when ALL vendor apps
|
|
26
|
+
* are removed.
|
|
27
|
+
* - **Android**: `getAndroidId()` (SSAID) — scoped to the app signing key on
|
|
28
|
+
* Android 8+; survives reinstalls as long as the signing key is unchanged.
|
|
29
|
+
*
|
|
30
|
+
* If the platform API returns null (rare edge cases: first boot, some emulators)
|
|
31
|
+
* the function falls back to a UUID persisted in `expo-secure-store`, which on
|
|
32
|
+
* iOS survives reinstalls via the Keychain and on Android is per-install only.
|
|
33
|
+
*
|
|
34
|
+
* Pass the resolved value to `OneDsTelemetryProviderConfig.deviceId` at startup.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { resolveDeviceId, OneDsTelemetryProvider } from '@microsoft/power-apps-native-common';
|
|
39
|
+
*
|
|
40
|
+
* const deviceId = await resolveDeviceId();
|
|
41
|
+
* const telemetry = new OneDsTelemetryProvider({ deviceId, ... });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
async function resolveDeviceId() {
|
|
45
|
+
if (_reactNative.Platform.OS === 'ios') {
|
|
46
|
+
const idfv = await Application.getIosIdForVendorAsync();
|
|
47
|
+
if (idfv) {
|
|
48
|
+
return idfv;
|
|
49
|
+
}
|
|
50
|
+
} else if (_reactNative.Platform.OS === 'android') {
|
|
51
|
+
const androidId = Application.getAndroidId();
|
|
52
|
+
if (androidId) {
|
|
53
|
+
return androidId;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback: stable UUID in SecureStore (iOS: survives reinstall via Keychain;
|
|
58
|
+
// Android: per-install only, but only reached when SSAID is unavailable).
|
|
59
|
+
const existing = await SecureStore.getItemAsync(DEVICE_ID_FALLBACK_KEY);
|
|
60
|
+
if (existing) {
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
const id = (0, _GuidUtils.generateGuid)();
|
|
64
|
+
await SecureStore.setItemAsync(DEVICE_ID_FALLBACK_KEY, id);
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.generateGuid = generateGuid;
|
|
7
|
+
require("react-native-get-random-values");
|
|
8
|
+
var _uuid = require("uuid");
|
|
9
|
+
/*!
|
|
10
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Must be imported before 'uuid' so that the crypto.getRandomValues polyfill is
|
|
14
|
+
// in place when uuid initialises on Hermes/JSC (React Native).
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates a RFC-4122 v4 GUID/UUID string.
|
|
18
|
+
* Uses the `uuid` package (backed by `react-native-get-random-values` on RN)
|
|
19
|
+
* instead of `crypto.randomUUID()` which is unavailable in Hermes/JSC.
|
|
20
|
+
*/
|
|
21
|
+
function generateGuid() {
|
|
22
|
+
return (0, _uuid.v4)();
|
|
23
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolvePlatformInfo = resolvePlatformInfo;
|
|
7
|
+
var Device = _interopRequireWildcard(require("expo-device"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
|
+
/*!
|
|
11
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Maps expo-device DeviceType numeric enum to a readable string.
|
|
15
|
+
const DEVICE_TYPE_LABEL = {
|
|
16
|
+
0: 'Unknown',
|
|
17
|
+
1: 'Phone',
|
|
18
|
+
2: 'Tablet',
|
|
19
|
+
3: 'Desktop',
|
|
20
|
+
4: 'TV'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolves 1DS Part A Common Schema device/OS/locale values using `expo-device`
|
|
25
|
+
* and the Intl API. Provides more accurate data than raw Platform.constants:
|
|
26
|
+
*
|
|
27
|
+
* - Correctly returns `'iPadOS'` for iPad (not `'iOS'`).
|
|
28
|
+
* - Returns human-friendly `modelName` (e.g. `'iPhone 16 Pro'`, `'Pixel 9'`).
|
|
29
|
+
* - Returns proper `manufacturer` (e.g. `'Apple'`, `'Google'`, `'Samsung'`).
|
|
30
|
+
* - Includes form-factor `deviceType` (Phone / Tablet / Desktop / TV).
|
|
31
|
+
*
|
|
32
|
+
* Safe to call once at provider construction time — all values are stable for
|
|
33
|
+
* the lifetime of the process.
|
|
34
|
+
*/
|
|
35
|
+
function resolvePlatformInfo() {
|
|
36
|
+
// expo-device returns 'iOS' or 'iPadOS' on Apple platforms.
|
|
37
|
+
// On Android, osName maps to Build.VERSION.BASE_OS which can be a build
|
|
38
|
+
// fingerprint on some OEMs, so we always use the literal 'Android' there.
|
|
39
|
+
const osName = _reactNative.Platform.OS === 'ios' ? Device.osName ?? 'iOS' : 'Android';
|
|
40
|
+
const osVersion = Device.osVersion ?? String(_reactNative.Platform.Version);
|
|
41
|
+
const deviceMake = Device.manufacturer ?? (_reactNative.Platform.OS === 'ios' ? 'Apple' : '');
|
|
42
|
+
const deviceModel = Device.modelName ?? '';
|
|
43
|
+
const deviceType = DEVICE_TYPE_LABEL[Device.deviceType ?? 0] ?? 'Unknown';
|
|
44
|
+
let userLocale = '';
|
|
45
|
+
let userTimeZone = '';
|
|
46
|
+
try {
|
|
47
|
+
const dtf = Intl.DateTimeFormat().resolvedOptions();
|
|
48
|
+
userLocale = dtf.locale;
|
|
49
|
+
userTimeZone = dtf.timeZone;
|
|
50
|
+
} catch {/* Intl unavailable — leave empty */}
|
|
51
|
+
return {
|
|
52
|
+
osName,
|
|
53
|
+
osVersion,
|
|
54
|
+
deviceMake,
|
|
55
|
+
deviceModel,
|
|
56
|
+
deviceType,
|
|
57
|
+
userLocale,
|
|
58
|
+
userTimeZone
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Canonical 1DS event names matching webplayer conventions. */
|
|
8
|
+
export const TelemetryEventNames = {
|
|
9
|
+
Activity: 'PowerAppsClient.Activity',
|
|
10
|
+
Error: 'PowerAppsClient.Error',
|
|
11
|
+
StartScenario: 'PowerAppsClient.StartScenario',
|
|
12
|
+
EndScenario: 'PowerAppsClient.EndScenario'
|
|
13
|
+
};
|
|
14
|
+
export const ScenarioResults = {
|
|
15
|
+
Success: 'Success',
|
|
16
|
+
Failed: 'Failed',
|
|
17
|
+
SuccessWithErrors: 'SuccessWithErrors'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Telemetry data residency location. EU routes to eu-mobile collector. */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Structural type for the 1DS telemetry envelope passed to telemetry initializers.
|
|
24
|
+
* Decouples consumers from the internal `ITelemetryItem` type.
|
|
25
|
+
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* power-apps-native-common
|
|
5
|
+
* Shared utilities and types for Power Apps native packages
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { ConsoleAdapter } from "./telemetry/ConsoleAdapter.js";
|
|
9
|
+
export { Logger } from "./telemetry/Logger.js";
|
|
10
|
+
export { OneDsTelemetryProvider } from "./telemetry/OneDsTelemetryProvider.js";
|
|
11
|
+
export { resolveTelemetryLocation } from "./telemetry/resolveTelemetryLocation.js";
|
|
12
|
+
export { stringifyError } from "./telemetry/stringifyError.js";
|
|
13
|
+
export { sanitizeErrorString, sanitizeUrl, sanitizeProperties } from "./telemetry/TelemetrySanitizer.js";
|
|
14
|
+
export { suppressKnownWarnings } from "./telemetry/suppressKnownWarnings.js";
|
|
15
|
+
export { resolveDeviceId } from "./utils/DeviceIdUtils.js";
|
|
16
|
+
export { configureAppLogger, getAppLogger } from "./telemetry/AppLogger.js";
|
|
17
|
+
export { ScenarioResults, TelemetryEventNames } from "./telemetry/TelemetryTypes.js";
|
|
18
|
+
export { Regions, regionToClusterCategory } from "./types/ClusterCategory.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* power-apps-native-common — app-level logger singleton
|
|
7
|
+
*
|
|
8
|
+
* Provides a single shared `ILogger` instance for the entire app process.
|
|
9
|
+
* The logger starts as a no-op (no providers) and is configured once at
|
|
10
|
+
* startup by the hosting infrastructure (e.g. `PowerAppsProvider`).
|
|
11
|
+
*
|
|
12
|
+
* App code and library packages call `getAppLogger()` to emit telemetry
|
|
13
|
+
* without coupling to a specific provider or needing to pass a logger around.
|
|
14
|
+
*
|
|
15
|
+
* Usage in a library:
|
|
16
|
+
*
|
|
17
|
+
* import { getAppLogger } from '@microsoft/power-apps-native-common';
|
|
18
|
+
* getAppLogger().trackActivityEvent('MyLib.SomeOperation', { … });
|
|
19
|
+
*
|
|
20
|
+
* Usage in the host / root layout (once, at startup):
|
|
21
|
+
*
|
|
22
|
+
* import { configureAppLogger } from '@microsoft/power-apps-native-common';
|
|
23
|
+
* configureAppLogger(new Logger({ providers: [myProvider] }));
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { Logger } from "./Logger.js";
|
|
27
|
+
let _logger = new Logger(); // no-op until configured
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Replace the shared app logger with a configured instance.
|
|
31
|
+
* All subsequent `getAppLogger()` calls return the new instance.
|
|
32
|
+
* Idempotent — calling again replaces the previous logger.
|
|
33
|
+
*/
|
|
34
|
+
export function configureAppLogger(logger) {
|
|
35
|
+
_logger = logger;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the shared app-level logger.
|
|
40
|
+
* No-op (no providers) until `configureAppLogger` is called.
|
|
41
|
+
*/
|
|
42
|
+
export function getAppLogger() {
|
|
43
|
+
return _logger;
|
|
44
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { generateGuid } from "../utils/GuidUtils.js";
|
|
8
|
+
/**
|
|
9
|
+
* A telemetry adapter that writes events to the JavaScript console.
|
|
10
|
+
* Useful during development and for unit-testing telemetry flows.
|
|
11
|
+
* Can be replaced in production by the native telemetry adapter.
|
|
12
|
+
*/
|
|
13
|
+
export class ConsoleAdapter {
|
|
14
|
+
defaultProperties = {};
|
|
15
|
+
constructor(minSeverity = 'verbose') {
|
|
16
|
+
this.minSeverity = minSeverity;
|
|
17
|
+
this._sessionId = generateGuid();
|
|
18
|
+
}
|
|
19
|
+
track(event) {
|
|
20
|
+
const payload = {
|
|
21
|
+
...event.properties,
|
|
22
|
+
sessionId: this._sessionId,
|
|
23
|
+
...this.defaultProperties,
|
|
24
|
+
...(this.userIdentity ?? {})
|
|
25
|
+
};
|
|
26
|
+
const label = `[${event.severity.toUpperCase()}] ${event.eventName} | ${event.properties['operationName'] ?? ''} (#${event.sequenceNumber})`;
|
|
27
|
+
switch (event.severity) {
|
|
28
|
+
case 'error':
|
|
29
|
+
console.error(label, payload);
|
|
30
|
+
break;
|
|
31
|
+
case 'warning':
|
|
32
|
+
console.warn(label, payload);
|
|
33
|
+
break;
|
|
34
|
+
case 'verbose':
|
|
35
|
+
console.debug(label, payload);
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
console.log(label, payload);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
flush() {
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
teardown() {
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
get sessionId() {
|
|
48
|
+
return this._sessionId;
|
|
49
|
+
}
|
|
50
|
+
set sessionId(value) {
|
|
51
|
+
this._sessionId = value;
|
|
52
|
+
}
|
|
53
|
+
identifyUser(identity) {
|
|
54
|
+
this.userIdentity = identity;
|
|
55
|
+
}
|
|
56
|
+
setDefaultProperty(name, value) {
|
|
57
|
+
this.defaultProperties[name] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*!
|
|
4
|
+
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { generateGuid } from "../utils/GuidUtils.js";
|
|
8
|
+
import { stringifyError } from "./stringifyError.js";
|
|
9
|
+
import { sanitizeProperties } from "./TelemetrySanitizer.js";
|
|
10
|
+
import { ScenarioResults, TelemetryEventNames } from "./TelemetryTypes.js";
|
|
11
|
+
const severityOrder = {
|
|
12
|
+
verbose: 0,
|
|
13
|
+
info: 1,
|
|
14
|
+
warning: 2,
|
|
15
|
+
error: 3
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Structured logger with canonical event names, severity filtering,
|
|
20
|
+
* sequence numbering, and scenario tracking.
|
|
21
|
+
* Supports multiple telemetry adapters; no-op safe when none are given.
|
|
22
|
+
*/
|
|
23
|
+
export class Logger {
|
|
24
|
+
sequence = 0;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.providers = config?.providers ?? [];
|
|
27
|
+
// Use the least-restrictive minSeverity among all providers, falling back to 'verbose'.
|
|
28
|
+
if (this.providers.length === 0) {
|
|
29
|
+
this.minSeverity = 'verbose';
|
|
30
|
+
} else {
|
|
31
|
+
const order = severityOrder;
|
|
32
|
+
this.minSeverity = this.providers.reduce((min, p) => {
|
|
33
|
+
const pSev = p.minSeverity ?? 'verbose';
|
|
34
|
+
return order[pSev] < order[min] ? pSev : min;
|
|
35
|
+
}, 'error');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
trackActivityEvent(name, properties, eventName) {
|
|
39
|
+
this.emit('info', eventName ?? TelemetryEventNames.Activity, name, properties);
|
|
40
|
+
}
|
|
41
|
+
trackVerboseEvent(name, properties, eventName) {
|
|
42
|
+
this.emit('verbose', eventName ?? TelemetryEventNames.Activity, name, properties);
|
|
43
|
+
}
|
|
44
|
+
trackErrorEvent(name, properties, eventName) {
|
|
45
|
+
this.emit('error', eventName ?? TelemetryEventNames.Error, name, properties);
|
|
46
|
+
}
|
|
47
|
+
trackScenario(name, properties) {
|
|
48
|
+
const startTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
49
|
+
const scenarioGuid = generateGuid();
|
|
50
|
+
const scenarioProps = {
|
|
51
|
+
...properties,
|
|
52
|
+
scenarioName: name,
|
|
53
|
+
scenarioGuid
|
|
54
|
+
};
|
|
55
|
+
this.emit('info', TelemetryEventNames.StartScenario, name, scenarioProps);
|
|
56
|
+
return {
|
|
57
|
+
complete: extraProps => {
|
|
58
|
+
const duration = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - startTime;
|
|
59
|
+
this.emit('info', TelemetryEventNames.EndScenario, name, {
|
|
60
|
+
...scenarioProps,
|
|
61
|
+
...extraProps,
|
|
62
|
+
scenarioResult: ScenarioResults.Success,
|
|
63
|
+
duration
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
completeWithError: extraProps => {
|
|
67
|
+
const duration = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - startTime;
|
|
68
|
+
this.emit('warning', TelemetryEventNames.EndScenario, name, {
|
|
69
|
+
...scenarioProps,
|
|
70
|
+
...extraProps,
|
|
71
|
+
scenarioResult: ScenarioResults.SuccessWithErrors,
|
|
72
|
+
duration
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
fail: (error, extraProps) => {
|
|
76
|
+
const duration = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - startTime;
|
|
77
|
+
this.emit('error', TelemetryEventNames.EndScenario, name, {
|
|
78
|
+
...scenarioProps,
|
|
79
|
+
...extraProps,
|
|
80
|
+
scenarioResult: ScenarioResults.Failed,
|
|
81
|
+
error: error !== undefined ? stringifyError(error) : undefined,
|
|
82
|
+
duration
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
setDefaultProperty(name, value) {
|
|
88
|
+
for (const p of this.providers) p.setDefaultProperty(name, value);
|
|
89
|
+
}
|
|
90
|
+
identifyUser(identity) {
|
|
91
|
+
for (const p of this.providers) p.identifyUser(identity);
|
|
92
|
+
}
|
|
93
|
+
get sessionId() {
|
|
94
|
+
return this.providers[0]?.sessionId;
|
|
95
|
+
}
|
|
96
|
+
set sessionId(value) {
|
|
97
|
+
for (const p of this.providers) p.sessionId = value;
|
|
98
|
+
}
|
|
99
|
+
async flush() {
|
|
100
|
+
await Promise.all(this.providers.map(p => p.flush()));
|
|
101
|
+
}
|
|
102
|
+
shouldLog(severity) {
|
|
103
|
+
return severityOrder[severity] >= severityOrder[this.minSeverity];
|
|
104
|
+
}
|
|
105
|
+
emit(severity, eventName, operationName, properties) {
|
|
106
|
+
if (this.providers.length === 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!this.shouldLog(severity)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const flatProps = flattenProperties(properties);
|
|
113
|
+
flatProps.operationName = operationName;
|
|
114
|
+
const event = {
|
|
115
|
+
eventName,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
severity,
|
|
118
|
+
sequenceNumber: ++this.sequence,
|
|
119
|
+
duration: typeof flatProps.duration === 'number' ? flatProps.duration : undefined,
|
|
120
|
+
properties: flatProps
|
|
121
|
+
};
|
|
122
|
+
for (const p of this.providers) {
|
|
123
|
+
// Per-adapter severity filtering
|
|
124
|
+
const adapterMin = p.minSeverity ?? 'verbose';
|
|
125
|
+
if (severityOrder[severity] >= severityOrder[adapterMin]) {
|
|
126
|
+
p.track(event);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Convert Record<string, unknown> to Record<string, string | number | boolean> by JSON-stringifying non-primitives. Applies PII sanitization before flattening. */
|
|
133
|
+
function flattenProperties(properties) {
|
|
134
|
+
const sanitized = properties ? sanitizeProperties(properties) : undefined;
|
|
135
|
+
const result = {};
|
|
136
|
+
if (!sanitized) return result;
|
|
137
|
+
for (const [key, value] of Object.entries(sanitized)) {
|
|
138
|
+
if (value === undefined) continue;
|
|
139
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
140
|
+
result[key] = value;
|
|
141
|
+
} else {
|
|
142
|
+
result[key] = JSON.stringify(value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|