@fluidframework/telemetry-utils 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.224419
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/.eslintrc.js +12 -13
- package/.mocharc.js +12 -0
- package/CHANGELOG.md +249 -0
- package/README.md +68 -1
- package/api-extractor-lint.json +4 -0
- package/api-extractor.json +2 -2
- package/api-report/telemetry-utils.api.md +444 -0
- package/dist/config.d.ts +47 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +88 -38
- package/dist/config.js.map +1 -1
- package/dist/error.d.ts +112 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +159 -0
- package/dist/error.js.map +1 -0
- package/dist/errorLogging.d.ts +86 -20
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +190 -60
- package/dist/errorLogging.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts +9 -3
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js +16 -3
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/events.d.ts +27 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +26 -2
- package/dist/events.js.map +1 -1
- package/dist/fluidErrorBase.d.ts +57 -16
- package/dist/fluidErrorBase.d.ts.map +1 -1
- package/dist/fluidErrorBase.js +27 -14
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -21
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +267 -51
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +423 -132
- package/dist/logger.js.map +1 -1
- package/dist/mockLogger.d.ts +39 -12
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +105 -22
- package/dist/mockLogger.js.map +1 -1
- package/dist/sampledTelemetryHelper.d.ts +18 -12
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js +28 -19
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetry-utils-alpha.d.ts +290 -0
- package/dist/telemetry-utils-beta.d.ts +264 -0
- package/dist/telemetry-utils-public.d.ts +264 -0
- package/dist/telemetry-utils-untrimmed.d.ts +1102 -0
- package/dist/telemetryTypes.d.ts +115 -0
- package/dist/telemetryTypes.d.ts.map +1 -0
- package/dist/telemetryTypes.js +7 -0
- package/dist/telemetryTypes.js.map +1 -0
- package/dist/thresholdCounter.d.ts +6 -5
- package/dist/thresholdCounter.d.ts.map +1 -1
- package/dist/thresholdCounter.js +4 -3
- package/dist/thresholdCounter.js.map +1 -1
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/utils.d.ts +54 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +58 -3
- package/dist/utils.js.map +1 -1
- package/lib/config.d.ts +47 -16
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +85 -36
- package/lib/config.js.map +1 -1
- package/lib/error.d.ts +112 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +150 -0
- package/lib/error.js.map +1 -0
- package/lib/errorLogging.d.ts +86 -20
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +189 -60
- package/lib/errorLogging.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts +9 -3
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js +15 -2
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/events.d.ts +27 -3
- package/lib/events.d.ts.map +1 -1
- package/lib/events.js +26 -2
- package/lib/events.js.map +1 -1
- package/lib/fluidErrorBase.d.ts +57 -16
- package/lib/fluidErrorBase.d.ts.map +1 -1
- package/lib/fluidErrorBase.js +27 -14
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/index.d.ts +12 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -11
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts +267 -51
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +415 -131
- package/lib/logger.js.map +1 -1
- package/lib/mockLogger.d.ts +39 -12
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +106 -23
- package/lib/mockLogger.js.map +1 -1
- package/lib/sampledTelemetryHelper.d.ts +18 -12
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js +26 -17
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetry-utils-alpha.d.ts +290 -0
- package/lib/telemetry-utils-beta.d.ts +264 -0
- package/lib/telemetry-utils-public.d.ts +264 -0
- package/lib/telemetry-utils-untrimmed.d.ts +1102 -0
- package/lib/telemetryTypes.d.ts +115 -0
- package/lib/telemetryTypes.d.ts.map +1 -0
- package/lib/telemetryTypes.js +6 -0
- package/lib/telemetryTypes.js.map +1 -0
- package/lib/thresholdCounter.d.ts +6 -5
- package/lib/thresholdCounter.d.ts.map +1 -1
- package/lib/thresholdCounter.js +4 -3
- package/lib/thresholdCounter.js.map +1 -1
- package/lib/utils.d.ts +54 -3
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +56 -2
- package/lib/utils.js.map +1 -1
- package/package.json +86 -57
- package/prettier.config.cjs +8 -0
- package/src/config.ts +254 -189
- package/src/error.ts +235 -0
- package/src/errorLogging.ts +440 -290
- package/src/eventEmitterWithErrorHandling.ts +26 -14
- package/src/events.ts +54 -25
- package/src/fluidErrorBase.ts +94 -46
- package/src/index.ts +76 -17
- package/src/logger.ts +966 -505
- package/src/mockLogger.ts +225 -83
- package/src/sampledTelemetryHelper.ts +136 -128
- package/src/telemetryTypes.ts +140 -0
- package/src/thresholdCounter.ts +38 -37
- package/src/utils.ts +108 -17
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +9 -13
- package/dist/debugLogger.d.ts +0 -39
- package/dist/debugLogger.d.ts.map +0 -1
- package/dist/debugLogger.js +0 -101
- package/dist/debugLogger.js.map +0 -1
- package/dist/packageVersion.d.ts +0 -9
- package/dist/packageVersion.d.ts.map +0 -1
- package/dist/packageVersion.js +0 -12
- package/dist/packageVersion.js.map +0 -1
- package/lib/debugLogger.d.ts +0 -39
- package/lib/debugLogger.d.ts.map +0 -1
- package/lib/debugLogger.js +0 -97
- package/lib/debugLogger.js.map +0 -1
- package/lib/packageVersion.d.ts +0 -9
- package/lib/packageVersion.d.ts.map +0 -1
- package/lib/packageVersion.js +0 -9
- package/lib/packageVersion.js.map +0 -1
- package/src/debugLogger.ts +0 -126
- package/src/packageVersion.ts +0 -9
package/src/logger.ts
CHANGED
|
@@ -4,564 +4,1025 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
ITelemetryBaseEvent,
|
|
8
|
+
ITelemetryBaseLogger,
|
|
9
|
+
ITelemetryErrorEvent,
|
|
10
|
+
ITelemetryGenericEvent,
|
|
11
|
+
ITelemetryPerformanceEvent,
|
|
12
|
+
ITelemetryProperties,
|
|
13
|
+
TelemetryBaseEventPropertyType as TelemetryEventPropertyType,
|
|
14
|
+
LogLevel,
|
|
15
|
+
Tagged,
|
|
16
|
+
ITelemetryBaseProperties,
|
|
17
|
+
TelemetryBaseEventPropertyType,
|
|
18
|
+
} from "@fluidframework/core-interfaces";
|
|
19
|
+
import { IsomorphicPerformance, performance } from "@fluid-internal/client-utils";
|
|
20
|
+
import { CachedConfigProvider, loggerIsMonitoringContext, mixinMonitoringContext } from "./config";
|
|
19
21
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
isILoggingError,
|
|
26
|
-
extractLogSafeErrorProperties,
|
|
27
|
-
generateStack,
|
|
22
|
+
isILoggingError,
|
|
23
|
+
extractLogSafeErrorProperties,
|
|
24
|
+
generateStack,
|
|
25
|
+
isTaggedTelemetryPropertyValue,
|
|
28
26
|
} from "./errorLogging";
|
|
27
|
+
import {
|
|
28
|
+
ITelemetryEventExt,
|
|
29
|
+
ITelemetryGenericEventExt,
|
|
30
|
+
ITelemetryLoggerExt,
|
|
31
|
+
ITelemetryPerformanceEventExt,
|
|
32
|
+
TelemetryEventPropertyTypeExt,
|
|
33
|
+
TelemetryEventCategory,
|
|
34
|
+
} from "./telemetryTypes";
|
|
35
|
+
|
|
36
|
+
export interface Memory {
|
|
37
|
+
usedJSHeapSize: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PerformanceWithMemory extends IsomorphicPerformance {
|
|
41
|
+
readonly memory: Memory;
|
|
42
|
+
}
|
|
29
43
|
|
|
30
44
|
/**
|
|
31
45
|
* Broad classifications to be applied to individual properties as they're prepared to be logged to telemetry.
|
|
32
|
-
*
|
|
46
|
+
*
|
|
47
|
+
* @privateRemarks Please do not modify existing entries, to maintain backwards compatibility.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
33
50
|
*/
|
|
34
51
|
export enum TelemetryDataTag {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
UserData = "UserData",
|
|
52
|
+
/**
|
|
53
|
+
* Data containing terms or IDs from code packages that may have been dynamically loaded
|
|
54
|
+
*/
|
|
55
|
+
CodeArtifact = "CodeArtifact",
|
|
56
|
+
/**
|
|
57
|
+
* Personal data of a variety of classifications that pertains to the user
|
|
58
|
+
*/
|
|
59
|
+
UserData = "UserData",
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
/**
|
|
63
|
+
* @alpha
|
|
64
|
+
*/
|
|
65
|
+
export type TelemetryEventPropertyTypes = ITelemetryBaseProperties[string];
|
|
47
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @alpha
|
|
69
|
+
*/
|
|
48
70
|
export interface ITelemetryLoggerPropertyBag {
|
|
49
|
-
|
|
71
|
+
[index: string]: TelemetryEventPropertyTypes | (() => TelemetryEventPropertyTypes);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @alpha
|
|
76
|
+
*/
|
|
77
|
+
export interface ITelemetryLoggerPropertyBags {
|
|
78
|
+
all?: ITelemetryLoggerPropertyBag;
|
|
79
|
+
error?: ITelemetryLoggerPropertyBag;
|
|
50
80
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Attempts to parse number from string.
|
|
84
|
+
* If it fails, it will return the original string.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Used to make telemetry data typed (and support math operations, like comparison),
|
|
88
|
+
* in places where we do expect numbers (like contentsize/duration property in http header).
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
// eslint-disable-next-line @rushstack/no-new-null
|
|
93
|
+
export function numberFromString(str: string | null | undefined): string | number | undefined {
|
|
94
|
+
if (str === undefined || str === null) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const num = Number(str);
|
|
98
|
+
return Number.isNaN(num) ? str : num;
|
|
54
99
|
}
|
|
55
100
|
|
|
101
|
+
// TODO: add docs
|
|
102
|
+
// eslint-disable-next-line jsdoc/require-description
|
|
103
|
+
/**
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
export function formatTick(tick: number): number {
|
|
107
|
+
return Math.floor(tick);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* String used to concatenate the namespace of parent loggers and their child loggers.
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export const eventNamespaceSeparator = ":" as const;
|
|
115
|
+
|
|
56
116
|
/**
|
|
57
117
|
* TelemetryLogger class contains various helper telemetry methods,
|
|
58
118
|
* encoding in one place schemas for various types of Fluid telemetry events.
|
|
59
119
|
* Creates sub-logger that appends properties to all events
|
|
60
120
|
*/
|
|
61
|
-
export abstract class TelemetryLogger implements
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
121
|
+
export abstract class TelemetryLogger implements ITelemetryLoggerExt {
|
|
122
|
+
/**
|
|
123
|
+
* {@inheritDoc eventNamespaceSeparator}
|
|
124
|
+
*/
|
|
125
|
+
public static readonly eventNamespaceSeparator = eventNamespaceSeparator;
|
|
126
|
+
|
|
127
|
+
public static sanitizePkgName(name: string): string {
|
|
128
|
+
return name.replace("@", "").replace("/", "-");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Take an unknown error object and add the appropriate info from it to the event. Message and stack will be copied
|
|
133
|
+
* over from the error object, along with other telemetry properties if it's an ILoggingError.
|
|
134
|
+
* @param event - Event being logged
|
|
135
|
+
* @param error - Error to extract info from
|
|
136
|
+
* @param fetchStack - Whether to fetch the current callstack if error.stack is undefined
|
|
137
|
+
*/
|
|
138
|
+
public static prepareErrorObject(
|
|
139
|
+
event: ITelemetryBaseEvent,
|
|
140
|
+
error: unknown,
|
|
141
|
+
fetchStack: boolean,
|
|
142
|
+
): void {
|
|
143
|
+
const { message, errorType, stack } = extractLogSafeErrorProperties(
|
|
144
|
+
error,
|
|
145
|
+
true /* sanitizeStack */,
|
|
146
|
+
);
|
|
147
|
+
// First, copy over error message, stack, and errorType directly (overwrite if present on event)
|
|
148
|
+
event.stack = stack;
|
|
149
|
+
event.error = message; // Note that the error message goes on the 'error' field
|
|
150
|
+
event.errorType = errorType;
|
|
151
|
+
|
|
152
|
+
if (isILoggingError(error)) {
|
|
153
|
+
// Add any other telemetry properties from the LoggingError
|
|
154
|
+
const telemetryProp = error.getTelemetryProperties();
|
|
155
|
+
for (const key of Object.keys(telemetryProp)) {
|
|
156
|
+
if (event[key] !== undefined) {
|
|
157
|
+
// Don't overwrite existing properties on the event
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
event[key] = telemetryProp[key];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Collect stack if we were not able to extract it from error
|
|
165
|
+
if (event.stack === undefined && fetchStack) {
|
|
166
|
+
event.stack = generateStack();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public constructor(
|
|
171
|
+
protected readonly namespace?: string,
|
|
172
|
+
protected readonly properties?: ITelemetryLoggerPropertyBags,
|
|
173
|
+
) {}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Send an event with the logger
|
|
177
|
+
*
|
|
178
|
+
* @param event - the event to send
|
|
179
|
+
*/
|
|
180
|
+
public abstract send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Send a telemetry event with the logger
|
|
184
|
+
*
|
|
185
|
+
* @param event - the event to send
|
|
186
|
+
* @param error - optional error object to log
|
|
187
|
+
* @param logLevel - optional level of the log. It category of event is set as error,
|
|
188
|
+
* then the logLevel will be upgraded to be an error.
|
|
189
|
+
*/
|
|
190
|
+
public sendTelemetryEvent(
|
|
191
|
+
event: ITelemetryGenericEventExt,
|
|
192
|
+
error?: unknown,
|
|
193
|
+
logLevel: typeof LogLevel.verbose | typeof LogLevel.default = LogLevel.default,
|
|
194
|
+
): void {
|
|
195
|
+
this.sendTelemetryEventCore(
|
|
196
|
+
{ ...event, category: event.category ?? "generic" },
|
|
197
|
+
error,
|
|
198
|
+
event.category === "error" ? LogLevel.error : logLevel,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Send a telemetry event with the logger
|
|
204
|
+
*
|
|
205
|
+
* @param event - the event to send
|
|
206
|
+
* @param error - optional error object to log
|
|
207
|
+
* @param logLevel - optional level of the log.
|
|
208
|
+
*/
|
|
209
|
+
protected sendTelemetryEventCore(
|
|
210
|
+
event: ITelemetryGenericEventExt & { category: TelemetryEventCategory },
|
|
211
|
+
error?: unknown,
|
|
212
|
+
logLevel?: LogLevel,
|
|
213
|
+
): void {
|
|
214
|
+
const newEvent = convertToBaseEvent(event);
|
|
215
|
+
if (error !== undefined) {
|
|
216
|
+
TelemetryLogger.prepareErrorObject(newEvent, error, false);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Will include Nan & Infinity, but probably we do not care
|
|
220
|
+
if (typeof newEvent.duration === "number") {
|
|
221
|
+
newEvent.duration = formatTick(newEvent.duration);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.send(newEvent, logLevel);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Send an error telemetry event with the logger
|
|
229
|
+
*
|
|
230
|
+
* @param event - the event to send
|
|
231
|
+
* @param error - optional error object to log
|
|
232
|
+
*/
|
|
233
|
+
public sendErrorEvent(event: ITelemetryErrorEvent, error?: unknown): void {
|
|
234
|
+
this.sendTelemetryEventCore(
|
|
235
|
+
{
|
|
236
|
+
// ensure the error field has some value,
|
|
237
|
+
// this can and will be overridden by event, or error
|
|
238
|
+
error: event.eventName,
|
|
239
|
+
...event,
|
|
240
|
+
category: "error",
|
|
241
|
+
},
|
|
242
|
+
error,
|
|
243
|
+
LogLevel.error,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Send a performance telemetry event with the logger
|
|
249
|
+
*
|
|
250
|
+
* @param event - Event to send
|
|
251
|
+
* @param error - optional error object to log
|
|
252
|
+
* @param logLevel - optional level of the log. It category of event is set as error,
|
|
253
|
+
* then the logLevel will be upgraded to be an error.
|
|
254
|
+
*/
|
|
255
|
+
public sendPerformanceEvent(
|
|
256
|
+
event: ITelemetryPerformanceEventExt,
|
|
257
|
+
error?: unknown,
|
|
258
|
+
logLevel: typeof LogLevel.verbose | typeof LogLevel.default = LogLevel.default,
|
|
259
|
+
): void {
|
|
260
|
+
const perfEvent = {
|
|
261
|
+
...event,
|
|
262
|
+
category: event.category ?? "performance",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
this.sendTelemetryEventCore(
|
|
266
|
+
perfEvent,
|
|
267
|
+
error,
|
|
268
|
+
perfEvent.category === "error" ? LogLevel.error : logLevel,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected prepareEvent(event: ITelemetryBaseEvent): ITelemetryBaseEvent {
|
|
273
|
+
const includeErrorProps = event.category === "error" || event.error !== undefined;
|
|
274
|
+
const newEvent: ITelemetryBaseEvent = {
|
|
275
|
+
...event,
|
|
276
|
+
};
|
|
277
|
+
if (this.namespace !== undefined) {
|
|
278
|
+
newEvent.eventName = `${this.namespace}${TelemetryLogger.eventNamespaceSeparator}${newEvent.eventName}`;
|
|
279
|
+
}
|
|
280
|
+
return this.extendProperties(newEvent, includeErrorProps);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private extendProperties<T extends ITelemetryLoggerPropertyBag = ITelemetryLoggerPropertyBag>(
|
|
284
|
+
toExtend: T,
|
|
285
|
+
includeErrorProps: boolean,
|
|
286
|
+
): T {
|
|
287
|
+
const eventLike: ITelemetryLoggerPropertyBag = toExtend;
|
|
288
|
+
if (this.properties) {
|
|
289
|
+
const properties: (undefined | ITelemetryLoggerPropertyBag)[] = [];
|
|
290
|
+
properties.push(this.properties.all);
|
|
291
|
+
if (includeErrorProps) {
|
|
292
|
+
properties.push(this.properties.error);
|
|
293
|
+
}
|
|
294
|
+
for (const props of properties) {
|
|
295
|
+
if (props !== undefined) {
|
|
296
|
+
for (const key of Object.keys(props)) {
|
|
297
|
+
if (eventLike[key] !== undefined) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const getterOrValue = props[key];
|
|
301
|
+
// If this throws, hopefully it is handled elsewhere
|
|
302
|
+
const value =
|
|
303
|
+
typeof getterOrValue === "function" ? getterOrValue() : getterOrValue;
|
|
304
|
+
if (value !== undefined) {
|
|
305
|
+
eventLike[key] = value;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return toExtend;
|
|
312
|
+
}
|
|
225
313
|
}
|
|
226
314
|
|
|
227
315
|
/**
|
|
228
316
|
* @deprecated 0.56, remove TaggedLoggerAdapter once its usage is removed from
|
|
229
317
|
* container-runtime. Issue: #8191
|
|
230
318
|
* TaggedLoggerAdapter class can add tag handling to your logger.
|
|
319
|
+
*
|
|
320
|
+
* @internal
|
|
231
321
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
322
|
+
export class TaggedLoggerAdapter implements ITelemetryBaseLogger {
|
|
323
|
+
public constructor(private readonly logger: ITelemetryBaseLogger) {}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}
|
|
327
|
+
*/
|
|
328
|
+
public send(eventWithTagsMaybe: ITelemetryBaseEvent): void {
|
|
329
|
+
const newEvent: ITelemetryBaseEvent = {
|
|
330
|
+
category: eventWithTagsMaybe.category,
|
|
331
|
+
eventName: eventWithTagsMaybe.eventName,
|
|
332
|
+
};
|
|
333
|
+
for (const key of Object.keys(eventWithTagsMaybe)) {
|
|
334
|
+
const taggableProp = eventWithTagsMaybe[key];
|
|
335
|
+
const { value, tag } =
|
|
336
|
+
typeof taggableProp === "object"
|
|
337
|
+
? taggableProp
|
|
338
|
+
: { value: taggableProp, tag: undefined };
|
|
339
|
+
switch (tag) {
|
|
340
|
+
case undefined: {
|
|
341
|
+
// No tag means we can log plainly
|
|
342
|
+
newEvent[key] = value;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case "PackageData": // For back-compat
|
|
346
|
+
case TelemetryDataTag.CodeArtifact: {
|
|
347
|
+
// For Microsoft applications, CodeArtifact is safe for now
|
|
348
|
+
// (we don't load 3P code in 1P apps)
|
|
349
|
+
newEvent[key] = value;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case TelemetryDataTag.UserData: {
|
|
353
|
+
// Strip out anything tagged explicitly as UserData.
|
|
354
|
+
// Alternate strategy would be to hash these props
|
|
355
|
+
newEvent[key] = "REDACTED (UserData)";
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
default: {
|
|
359
|
+
// If we encounter a tag we don't recognize
|
|
360
|
+
// then we must assume we should scrub.
|
|
361
|
+
newEvent[key] = "REDACTED (unknown tag)";
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.logger.send(newEvent);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Create a child logger based on the provided props object.
|
|
372
|
+
*
|
|
373
|
+
* @remarks
|
|
374
|
+
* Passing in no props object (i.e. undefined) will return a logger that is effectively a no-op.
|
|
375
|
+
*
|
|
376
|
+
* @param props - logger is the base logger the child will log to after it's processing, namespace will be prefixed to all event names, properties are default properties that will be applied events.
|
|
377
|
+
*
|
|
378
|
+
* @alpha
|
|
379
|
+
*/
|
|
380
|
+
export function createChildLogger(props?: {
|
|
381
|
+
logger?: ITelemetryBaseLogger;
|
|
382
|
+
namespace?: string;
|
|
383
|
+
properties?: ITelemetryLoggerPropertyBags;
|
|
384
|
+
}): ITelemetryLoggerExt {
|
|
385
|
+
return ChildLogger.create(props?.logger, props?.namespace, props?.properties);
|
|
271
386
|
}
|
|
272
387
|
|
|
273
388
|
/**
|
|
274
389
|
* ChildLogger class contains various helper telemetry methods,
|
|
275
390
|
* encoding in one place schemas for various types of Fluid telemetry events.
|
|
276
|
-
* Creates sub-logger that appends properties to all events
|
|
391
|
+
* Creates sub-logger that appends properties to all events.
|
|
277
392
|
*/
|
|
278
393
|
export class ChildLogger extends TelemetryLogger {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Create child logger
|
|
396
|
+
* @param baseLogger - Base logger to use to output events. If undefined, proper child logger
|
|
397
|
+
* is created, but it does not send telemetry events anywhere.
|
|
398
|
+
* @param namespace - Telemetry event name prefix to add to all events
|
|
399
|
+
* @param properties - Base properties to add to all events
|
|
400
|
+
*/
|
|
401
|
+
public static create(
|
|
402
|
+
baseLogger?: ITelemetryBaseLogger,
|
|
403
|
+
namespace?: string,
|
|
404
|
+
properties?: ITelemetryLoggerPropertyBags,
|
|
405
|
+
): TelemetryLogger {
|
|
406
|
+
// if we are creating a child of a child, rather than nest, which will increase
|
|
407
|
+
// the callstack overhead, just generate a new logger that includes everything from the previous
|
|
408
|
+
if (baseLogger instanceof ChildLogger) {
|
|
409
|
+
const combinedProperties: ITelemetryLoggerPropertyBags = {};
|
|
410
|
+
for (const extendedProps of [baseLogger.properties, properties]) {
|
|
411
|
+
if (extendedProps !== undefined) {
|
|
412
|
+
if (extendedProps.all !== undefined) {
|
|
413
|
+
combinedProperties.all = {
|
|
414
|
+
...combinedProperties.all,
|
|
415
|
+
...extendedProps.all,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (extendedProps.error !== undefined) {
|
|
419
|
+
combinedProperties.error = {
|
|
420
|
+
...combinedProperties.error,
|
|
421
|
+
...extendedProps.error,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const combinedNamespace =
|
|
428
|
+
baseLogger.namespace === undefined
|
|
429
|
+
? namespace
|
|
430
|
+
: namespace === undefined
|
|
431
|
+
? baseLogger.namespace
|
|
432
|
+
: `${baseLogger.namespace}${TelemetryLogger.eventNamespaceSeparator}${namespace}`;
|
|
433
|
+
|
|
434
|
+
const child = new ChildLogger(
|
|
435
|
+
baseLogger.baseLogger,
|
|
436
|
+
combinedNamespace,
|
|
437
|
+
combinedProperties,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
if (!loggerIsMonitoringContext(child) && loggerIsMonitoringContext(baseLogger)) {
|
|
441
|
+
mixinMonitoringContext(child, baseLogger.config);
|
|
442
|
+
}
|
|
443
|
+
return child;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return new ChildLogger(baseLogger ?? { send(): void {} }, namespace, properties);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private constructor(
|
|
450
|
+
protected readonly baseLogger: ITelemetryBaseLogger,
|
|
451
|
+
namespace: string | undefined,
|
|
452
|
+
properties: ITelemetryLoggerPropertyBags | undefined,
|
|
453
|
+
) {
|
|
454
|
+
super(namespace, properties);
|
|
455
|
+
|
|
456
|
+
// propagate the monitoring context
|
|
457
|
+
if (loggerIsMonitoringContext(baseLogger)) {
|
|
458
|
+
mixinMonitoringContext(this, new CachedConfigProvider(this, baseLogger.config));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public get minLogLevel(): LogLevel | undefined {
|
|
463
|
+
return this.baseLogger.minLogLevel;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private shouldFilterOutEvent(event: ITelemetryBaseEvent, logLevel?: LogLevel): boolean {
|
|
467
|
+
const eventLogLevel = logLevel ?? LogLevel.default;
|
|
468
|
+
const configLogLevel = this.baseLogger.minLogLevel ?? LogLevel.default;
|
|
469
|
+
// Filter out in case event log level is below what is wanted in config.
|
|
470
|
+
return eventLogLevel < configLogLevel;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Send an event with the logger
|
|
475
|
+
*
|
|
476
|
+
* @param event - the event to send
|
|
477
|
+
*/
|
|
478
|
+
public send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
|
|
479
|
+
if (this.shouldFilterOutEvent(event, logLevel)) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
this.baseLogger.send(this.prepareEvent(event), logLevel);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Input properties for {@link createMultiSinkLogger}.
|
|
488
|
+
*
|
|
489
|
+
* @internal
|
|
490
|
+
*/
|
|
491
|
+
export interface MultiSinkLoggerProperties {
|
|
492
|
+
/**
|
|
493
|
+
* Will be prefixed to all event names.
|
|
494
|
+
*/
|
|
495
|
+
namespace?: string;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Default properties that will be applied to all events flowing through this logger.
|
|
499
|
+
*/
|
|
500
|
+
properties?: ITelemetryLoggerPropertyBags;
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* The base loggers that this logger will forward the logs to, after it processes them.
|
|
504
|
+
*/
|
|
505
|
+
loggers?: (ITelemetryBaseLogger | undefined)[];
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* If true, the logger will attempt to copy the custom properties (if they are of a known type, i.e. one from this package) of all the base loggers passed to it, to apply them itself to logs that flow through.
|
|
509
|
+
*/
|
|
510
|
+
tryInheritProperties?: true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Create a logger which logs to multiple other loggers based on the provided props object.
|
|
515
|
+
*
|
|
516
|
+
* @internal
|
|
517
|
+
*/
|
|
518
|
+
export function createMultiSinkLogger(props: MultiSinkLoggerProperties): ITelemetryLoggerExt {
|
|
519
|
+
return new MultiSinkLogger(
|
|
520
|
+
props.namespace,
|
|
521
|
+
props.properties,
|
|
522
|
+
props.loggers?.filter((l): l is ITelemetryBaseLogger => l !== undefined),
|
|
523
|
+
props.tryInheritProperties,
|
|
524
|
+
);
|
|
354
525
|
}
|
|
355
526
|
|
|
356
527
|
/**
|
|
357
528
|
* Multi-sink logger
|
|
358
529
|
* Takes multiple ITelemetryBaseLogger objects (sinks) and logs all events into each sink
|
|
359
|
-
* Implements ITelemetryBaseLogger (through static create() method)
|
|
360
530
|
*/
|
|
361
531
|
export class MultiSinkLogger extends TelemetryLogger {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
532
|
+
protected loggers: ITelemetryBaseLogger[];
|
|
533
|
+
// This is minimum of minLlogLevel of all loggers.
|
|
534
|
+
private _minLogLevelOfAllLoggers: LogLevel;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Create multiple sink logger (i.e. logger that sends events to multiple sinks)
|
|
538
|
+
* @param namespace - Telemetry event name prefix to add to all events
|
|
539
|
+
* @param properties - Base properties to add to all events
|
|
540
|
+
* @param loggers - The list of loggers to use as sinks
|
|
541
|
+
* @param tryInheritProperties - Will attempted to copy those loggers properties to this loggers if they are of a known type e.g. one from this package
|
|
542
|
+
*/
|
|
543
|
+
constructor(
|
|
544
|
+
namespace?: string,
|
|
545
|
+
properties?: ITelemetryLoggerPropertyBags,
|
|
546
|
+
loggers: ITelemetryBaseLogger[] = [],
|
|
547
|
+
tryInheritProperties?: true,
|
|
548
|
+
) {
|
|
549
|
+
let realProperties = properties === undefined ? undefined : { ...properties };
|
|
550
|
+
if (tryInheritProperties === true) {
|
|
551
|
+
const merge = (realProperties ??= {});
|
|
552
|
+
loggers
|
|
553
|
+
.filter((l): l is this => l instanceof TelemetryLogger)
|
|
554
|
+
.map((l) => l.properties ?? {})
|
|
555
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
556
|
+
.forEach((cv) => {
|
|
557
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
558
|
+
Object.keys(cv).forEach((k) => {
|
|
559
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
560
|
+
merge[k] = { ...cv[k], ...merge?.[k] };
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
super(namespace, realProperties);
|
|
566
|
+
this.loggers = loggers;
|
|
567
|
+
this._minLogLevelOfAllLoggers = LogLevel.default;
|
|
568
|
+
this.calculateMinLogLevel();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public get minLogLevel(): LogLevel {
|
|
572
|
+
return this._minLogLevelOfAllLoggers;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private calculateMinLogLevel(): void {
|
|
576
|
+
if (this.loggers.length > 0) {
|
|
577
|
+
const logLevels: LogLevel[] = [];
|
|
578
|
+
for (const logger of this.loggers) {
|
|
579
|
+
logLevels.push(logger.minLogLevel ?? LogLevel.default);
|
|
580
|
+
}
|
|
581
|
+
this._minLogLevelOfAllLoggers = Math.min(...logLevels) as LogLevel;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Add logger to send all events to
|
|
587
|
+
* @param logger - Logger to add
|
|
588
|
+
*/
|
|
589
|
+
public addLogger(logger?: ITelemetryBaseLogger): void {
|
|
590
|
+
if (logger !== undefined && logger !== null) {
|
|
591
|
+
this.loggers.push(logger);
|
|
592
|
+
// Update in case the logLevel of added logger is less than the current.
|
|
593
|
+
this.calculateMinLogLevel();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Send an event to the loggers
|
|
599
|
+
*
|
|
600
|
+
* @param event - the event to send to all the registered logger
|
|
601
|
+
*/
|
|
602
|
+
public send(event: ITelemetryBaseEvent): void {
|
|
603
|
+
const newEvent = this.prepareEvent(event);
|
|
604
|
+
for (const logger of this.loggers) {
|
|
605
|
+
logger.send(newEvent);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
397
608
|
}
|
|
398
609
|
|
|
399
610
|
/**
|
|
400
|
-
* Describes what events PerformanceEvent should log
|
|
401
|
-
*
|
|
402
|
-
*
|
|
611
|
+
* Describes what events {@link PerformanceEvent} should log.
|
|
612
|
+
*
|
|
613
|
+
* @remarks
|
|
614
|
+
* By default, all events are logged, but the client can override this behavior.
|
|
615
|
+
*
|
|
616
|
+
* For example, there is rarely a need to record a start event, as we're really after
|
|
403
617
|
* success / failure tracking, including duration (on success).
|
|
618
|
+
*
|
|
619
|
+
* @internal
|
|
404
620
|
*/
|
|
405
621
|
export interface IPerformanceEventMarkers {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
622
|
+
start?: true;
|
|
623
|
+
end?: true;
|
|
624
|
+
cancel?: "generic" | "error"; // tells wether to issue "generic" or "error" category cancel event
|
|
409
625
|
}
|
|
410
626
|
|
|
411
627
|
/**
|
|
412
|
-
* Helper class to log performance events
|
|
628
|
+
* Helper class to log performance events.
|
|
629
|
+
*
|
|
630
|
+
* @internal
|
|
413
631
|
*/
|
|
414
632
|
export class PerformanceEvent {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
633
|
+
/**
|
|
634
|
+
* Creates an instance of {@link PerformanceEvent} and starts measurements
|
|
635
|
+
* @param logger - the logger to be used for publishing events
|
|
636
|
+
* @param event - the logging event details which will be published with the performance measurements
|
|
637
|
+
* @param markers - See {@link IPerformanceEventMarkers}
|
|
638
|
+
* @param recordHeapSize - whether or not to also record memory performance
|
|
639
|
+
* @param emitLogs - should this instance emit logs. If set to false, logs will not be emitted to the logger,
|
|
640
|
+
* but measurements will still be performed and any specified markers will be generated.
|
|
641
|
+
* @returns An instance of {@link PerformanceEvent}
|
|
642
|
+
*/
|
|
643
|
+
public static start(
|
|
644
|
+
logger: ITelemetryLoggerExt,
|
|
645
|
+
event: ITelemetryGenericEvent,
|
|
646
|
+
markers?: IPerformanceEventMarkers,
|
|
647
|
+
recordHeapSize: boolean = false,
|
|
648
|
+
emitLogs: boolean = true,
|
|
649
|
+
): PerformanceEvent {
|
|
650
|
+
return new PerformanceEvent(logger, event, markers, recordHeapSize, emitLogs);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Measure a synchronous task
|
|
655
|
+
* @param logger - the logger to be used for publishing events
|
|
656
|
+
* @param event - the logging event details which will be published with the performance measurements
|
|
657
|
+
* @param callback - the task to be executed and measured
|
|
658
|
+
* @param markers - See {@link IPerformanceEventMarkers}
|
|
659
|
+
* @param sampleThreshold - events with the same name and category will be sent to the logger
|
|
660
|
+
* only when we hit this many executions of the task. If unspecified, all events will be sent.
|
|
661
|
+
* @returns The results of the executed task
|
|
662
|
+
*
|
|
663
|
+
* @remarks Note that if the "same" event (category + eventName) would be emitted by different
|
|
664
|
+
* tasks (`callback`), `sampleThreshold` is still applied only based on the event's category + eventName,
|
|
665
|
+
* so executing either of the tasks will increase the internal counter and they
|
|
666
|
+
* effectively "share" the sampling rate for the event.
|
|
667
|
+
*/
|
|
668
|
+
public static timedExec<T>(
|
|
669
|
+
logger: ITelemetryLoggerExt,
|
|
670
|
+
event: ITelemetryGenericEvent,
|
|
671
|
+
callback: (event: PerformanceEvent) => T,
|
|
672
|
+
markers?: IPerformanceEventMarkers,
|
|
673
|
+
sampleThreshold: number = 1,
|
|
674
|
+
): T {
|
|
675
|
+
const perfEvent = PerformanceEvent.start(
|
|
676
|
+
logger,
|
|
677
|
+
event,
|
|
678
|
+
markers,
|
|
679
|
+
undefined, // recordHeapSize
|
|
680
|
+
PerformanceEvent.shouldReport(event, sampleThreshold),
|
|
681
|
+
);
|
|
682
|
+
try {
|
|
683
|
+
const ret = callback(perfEvent);
|
|
684
|
+
perfEvent.autoEnd();
|
|
685
|
+
return ret;
|
|
686
|
+
} catch (error) {
|
|
687
|
+
perfEvent.cancel(undefined, error);
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Measure an asynchronous task
|
|
694
|
+
* @param logger - the logger to be used for publishing events
|
|
695
|
+
* @param event - the logging event details which will be published with the performance measurements
|
|
696
|
+
* @param callback - the task to be executed and measured
|
|
697
|
+
* @param markers - See {@link IPerformanceEventMarkers}
|
|
698
|
+
* @param recordHeapSize - whether or not to also record memory performance
|
|
699
|
+
* @param sampleThreshold - events with the same name and category will be sent to the logger
|
|
700
|
+
* only when we hit this many executions of the task. If unspecified, all events will be sent.
|
|
701
|
+
* @returns The results of the executed task
|
|
702
|
+
*
|
|
703
|
+
* @remarks Note that if the "same" event (category + eventName) would be emitted by different
|
|
704
|
+
* tasks (`callback`), `sampleThreshold` is still applied only based on the event's category + eventName,
|
|
705
|
+
* so executing either of the tasks will increase the internal counter and they
|
|
706
|
+
* effectively "share" the sampling rate for the event.
|
|
707
|
+
*/
|
|
708
|
+
public static async timedExecAsync<T>(
|
|
709
|
+
logger: ITelemetryLoggerExt,
|
|
710
|
+
event: ITelemetryGenericEvent,
|
|
711
|
+
callback: (event: PerformanceEvent) => Promise<T>,
|
|
712
|
+
markers?: IPerformanceEventMarkers,
|
|
713
|
+
recordHeapSize?: boolean,
|
|
714
|
+
sampleThreshold: number = 1,
|
|
715
|
+
): Promise<T> {
|
|
716
|
+
const perfEvent = PerformanceEvent.start(
|
|
717
|
+
logger,
|
|
718
|
+
event,
|
|
719
|
+
markers,
|
|
720
|
+
recordHeapSize,
|
|
721
|
+
PerformanceEvent.shouldReport(event, sampleThreshold),
|
|
722
|
+
);
|
|
723
|
+
try {
|
|
724
|
+
const ret = await callback(perfEvent);
|
|
725
|
+
perfEvent.autoEnd();
|
|
726
|
+
return ret;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
perfEvent.cancel(undefined, error);
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
public get duration(): number {
|
|
734
|
+
return performance.now() - this.startTime;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private event?: ITelemetryGenericEvent;
|
|
738
|
+
private readonly startTime = performance.now();
|
|
739
|
+
private startMark?: string;
|
|
740
|
+
private startMemoryCollection: number | undefined = 0;
|
|
741
|
+
|
|
742
|
+
protected constructor(
|
|
743
|
+
private readonly logger: ITelemetryLoggerExt,
|
|
744
|
+
event: ITelemetryGenericEvent,
|
|
745
|
+
private readonly markers: IPerformanceEventMarkers = { end: true, cancel: "generic" },
|
|
746
|
+
private readonly recordHeapSize: boolean = false,
|
|
747
|
+
private readonly emitLogs: boolean = true,
|
|
748
|
+
) {
|
|
749
|
+
this.event = { ...event };
|
|
750
|
+
if (this.markers.start) {
|
|
751
|
+
this.reportEvent("start");
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (typeof window === "object" && window?.performance?.mark) {
|
|
755
|
+
this.startMark = `${event.eventName}-start`;
|
|
756
|
+
window.performance.mark(this.startMark);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
public reportProgress(props?: ITelemetryProperties, eventNameSuffix: string = "update"): void {
|
|
761
|
+
this.reportEvent(eventNameSuffix, props);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private autoEnd(): void {
|
|
765
|
+
// Event might have been cancelled or ended in the callback
|
|
766
|
+
if (this.event && this.markers.end) {
|
|
767
|
+
this.reportEvent("end");
|
|
768
|
+
}
|
|
769
|
+
this.performanceEndMark();
|
|
770
|
+
this.event = undefined;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
public end(props?: ITelemetryProperties): void {
|
|
774
|
+
this.reportEvent("end", props);
|
|
775
|
+
this.performanceEndMark();
|
|
776
|
+
this.event = undefined;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private performanceEndMark(): void {
|
|
780
|
+
if (this.startMark && this.event) {
|
|
781
|
+
const endMark = `${this.event.eventName}-end`;
|
|
782
|
+
window.performance.mark(endMark);
|
|
783
|
+
window.performance.measure(`${this.event.eventName}`, this.startMark, endMark);
|
|
784
|
+
this.startMark = undefined;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
public cancel(props?: ITelemetryProperties, error?: unknown): void {
|
|
789
|
+
if (this.markers.cancel !== undefined) {
|
|
790
|
+
this.reportEvent("cancel", { category: this.markers.cancel, ...props }, error);
|
|
791
|
+
}
|
|
792
|
+
this.event = undefined;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Report the event, if it hasn't already been reported.
|
|
797
|
+
*/
|
|
798
|
+
public reportEvent(
|
|
799
|
+
eventNameSuffix: string,
|
|
800
|
+
props?: ITelemetryProperties,
|
|
801
|
+
error?: unknown,
|
|
802
|
+
): void {
|
|
803
|
+
// There are strange sequences involving multiple Promise chains
|
|
804
|
+
// where the event can be cancelled and then later a callback is invoked
|
|
805
|
+
// and the caller attempts to end directly, e.g. issue #3936. Just return.
|
|
806
|
+
if (!this.event) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (!this.emitLogs) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const event: ITelemetryPerformanceEvent = { ...this.event, ...props };
|
|
815
|
+
event.eventName = `${event.eventName}_${eventNameSuffix}`;
|
|
816
|
+
if (eventNameSuffix !== "start") {
|
|
817
|
+
event.duration = this.duration;
|
|
818
|
+
if (this.startMemoryCollection) {
|
|
819
|
+
const currentMemory = (performance as PerformanceWithMemory)?.memory
|
|
820
|
+
?.usedJSHeapSize;
|
|
821
|
+
const differenceInKBytes = Math.floor(
|
|
822
|
+
(currentMemory - this.startMemoryCollection) / 1024,
|
|
823
|
+
);
|
|
824
|
+
if (differenceInKBytes > 0) {
|
|
825
|
+
event.usedJSHeapSize = differenceInKBytes;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
} else if (this.recordHeapSize) {
|
|
829
|
+
this.startMemoryCollection = (performance as PerformanceWithMemory)?.memory
|
|
830
|
+
?.usedJSHeapSize;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
this.logger.sendPerformanceEvent(event, error);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private static readonly eventHits = new Map<string, number>();
|
|
837
|
+
private static shouldReport(event: ITelemetryGenericEvent, sampleThreshold: number): boolean {
|
|
838
|
+
const eventKey = `.${event.category}.${event.eventName}`;
|
|
839
|
+
const hitCount = PerformanceEvent.eventHits.get(eventKey) ?? 0;
|
|
840
|
+
PerformanceEvent.eventHits.set(eventKey, hitCount >= sampleThreshold ? 1 : hitCount + 1);
|
|
841
|
+
return hitCount % sampleThreshold === 0;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Null logger that no-ops for all telemetry events passed to it.
|
|
847
|
+
*
|
|
848
|
+
* @deprecated This will be removed in a future release.
|
|
849
|
+
* For internal use within the FluidFramework codebase, use {@link createChildLogger} with no arguments instead.
|
|
850
|
+
* For external consumers we recommend writing a trivial implementation of {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
|
|
851
|
+
* where the send() method does nothing and using that.
|
|
852
|
+
*
|
|
853
|
+
* @internal
|
|
854
|
+
*/
|
|
855
|
+
export class TelemetryNullLogger implements ITelemetryLoggerExt {
|
|
856
|
+
public send(event: ITelemetryBaseEvent): void {}
|
|
857
|
+
public sendTelemetryEvent(event: ITelemetryGenericEvent, error?: unknown): void {}
|
|
858
|
+
public sendErrorEvent(event: ITelemetryErrorEvent, error?: unknown): void {}
|
|
859
|
+
public sendPerformanceEvent(event: ITelemetryPerformanceEvent, error?: unknown): void {}
|
|
529
860
|
}
|
|
530
861
|
|
|
531
862
|
/**
|
|
532
|
-
*
|
|
533
|
-
*
|
|
863
|
+
* Takes in an event object, and converts all of its values to a basePropertyType.
|
|
864
|
+
* In the case of an invalid property type, the value will be converted to an error string.
|
|
865
|
+
* @param event - Event with fields you want to stringify.
|
|
534
866
|
*/
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
public logGenericError(eventName: string, error: any) {
|
|
546
|
-
this.reportError(`genericError in UT logger!`, { eventName }, error);
|
|
547
|
-
}
|
|
548
|
-
public logException(event: ITelemetryErrorEvent, exception: any): void {
|
|
549
|
-
this.reportError("exception in UT logger!", event, exception);
|
|
550
|
-
}
|
|
551
|
-
public debugAssert(condition: boolean, event?: ITelemetryErrorEvent): void {
|
|
552
|
-
this.reportError("debugAssert in UT logger!");
|
|
553
|
-
}
|
|
554
|
-
public shipAssert(condition: boolean, event?: ITelemetryErrorEvent): void {
|
|
555
|
-
this.reportError("shipAssert in UT logger!");
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
private reportError(message: string, event?: ITelemetryErrorEvent, err?: any) {
|
|
559
|
-
const error = new Error(message);
|
|
560
|
-
(error as any).error = error;
|
|
561
|
-
(error as any).event = event;
|
|
562
|
-
// report to console as exception can be eaten
|
|
563
|
-
console.error(message);
|
|
564
|
-
console.error(error);
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
867
|
+
function convertToBaseEvent({
|
|
868
|
+
category,
|
|
869
|
+
eventName,
|
|
870
|
+
...props
|
|
871
|
+
}: ITelemetryEventExt): ITelemetryBaseEvent {
|
|
872
|
+
const newEvent: ITelemetryBaseEvent = { category, eventName };
|
|
873
|
+
for (const key of Object.keys(props)) {
|
|
874
|
+
newEvent[key] = convertToBasePropertyType(props[key]);
|
|
875
|
+
}
|
|
876
|
+
return newEvent;
|
|
567
877
|
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Takes in value, and does one of 4 things.
|
|
881
|
+
* if value is of primitive type - returns the original value.
|
|
882
|
+
* If the value is a flat array or object - returns a stringified version of the array/object.
|
|
883
|
+
* If the value is an object of type Tagged<TelemetryEventPropertyType> - returns the object
|
|
884
|
+
* with its values recursively converted to base property Type.
|
|
885
|
+
* If none of these cases are reached - returns an error string
|
|
886
|
+
* @param x - value passed in to convert to a base property type
|
|
887
|
+
*/
|
|
888
|
+
export function convertToBasePropertyType(
|
|
889
|
+
x: TelemetryEventPropertyTypeExt | Tagged<TelemetryEventPropertyTypeExt>,
|
|
890
|
+
): TelemetryEventPropertyType | Tagged<TelemetryEventPropertyType> {
|
|
891
|
+
return isTaggedTelemetryPropertyValue(x)
|
|
892
|
+
? {
|
|
893
|
+
value: convertToBasePropertyTypeUntagged(x.value),
|
|
894
|
+
tag: x.tag,
|
|
895
|
+
}
|
|
896
|
+
: convertToBasePropertyTypeUntagged(x);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function convertToBasePropertyTypeUntagged(
|
|
900
|
+
x: TelemetryEventPropertyTypeExt,
|
|
901
|
+
): TelemetryEventPropertyType {
|
|
902
|
+
switch (typeof x) {
|
|
903
|
+
case "string":
|
|
904
|
+
case "number":
|
|
905
|
+
case "boolean":
|
|
906
|
+
case "undefined": {
|
|
907
|
+
return x;
|
|
908
|
+
}
|
|
909
|
+
case "object": {
|
|
910
|
+
// We assume this is an array or flat object based on the input types
|
|
911
|
+
return JSON.stringify(x);
|
|
912
|
+
}
|
|
913
|
+
default: {
|
|
914
|
+
// should never reach this case based on the input types
|
|
915
|
+
console.error(
|
|
916
|
+
`convertToBasePropertyTypeUntagged: INVALID PROPERTY (typed as ${typeof x})`,
|
|
917
|
+
);
|
|
918
|
+
return `INVALID PROPERTY (typed as ${typeof x})`;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Tags all given `values` with the same `tag`.
|
|
925
|
+
*
|
|
926
|
+
* @param tag - The tag with which all `values` will be annotated.
|
|
927
|
+
* @param values - The values to be tagged.
|
|
928
|
+
*
|
|
929
|
+
* @remarks
|
|
930
|
+
* It supports properties of type {@link @fluidframework/core-interfaces#TelemetryBaseEventPropertyType},
|
|
931
|
+
* as well as callbacks that return that type.
|
|
932
|
+
*
|
|
933
|
+
* @example Sample usage
|
|
934
|
+
* ```typescript
|
|
935
|
+
* {
|
|
936
|
+
* // ...Other properties being added to a telemetry event
|
|
937
|
+
* ...tagData("someTag", {foo: 1, bar: 2}),
|
|
938
|
+
* // ...
|
|
939
|
+
* }
|
|
940
|
+
* ```
|
|
941
|
+
* This will result in `foo` and `bar` added to the event with their values tagged.
|
|
942
|
+
*
|
|
943
|
+
* @internal
|
|
944
|
+
*/
|
|
945
|
+
export const tagData = <
|
|
946
|
+
T extends TelemetryDataTag,
|
|
947
|
+
V extends Record<
|
|
948
|
+
string,
|
|
949
|
+
TelemetryBaseEventPropertyType | (() => TelemetryBaseEventPropertyType)
|
|
950
|
+
>,
|
|
951
|
+
>(
|
|
952
|
+
tag: T,
|
|
953
|
+
values: V,
|
|
954
|
+
): {
|
|
955
|
+
[P in keyof V]:
|
|
956
|
+
| (V[P] extends () => TelemetryBaseEventPropertyType
|
|
957
|
+
? () => {
|
|
958
|
+
value: ReturnType<V[P]>;
|
|
959
|
+
tag: T;
|
|
960
|
+
}
|
|
961
|
+
: {
|
|
962
|
+
value: Exclude<V[P], undefined>;
|
|
963
|
+
tag: T;
|
|
964
|
+
})
|
|
965
|
+
| (V[P] extends undefined ? undefined : never);
|
|
966
|
+
} =>
|
|
967
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
968
|
+
Object.entries(values)
|
|
969
|
+
.filter((e) => e[1] !== undefined)
|
|
970
|
+
// eslint-disable-next-line unicorn/no-array-reduce, unicorn/prefer-object-from-entries
|
|
971
|
+
.reduce((pv, cv) => {
|
|
972
|
+
const [key, value] = cv;
|
|
973
|
+
// The ternary form is less legible in this case.
|
|
974
|
+
// eslint-disable-next-line unicorn/prefer-ternary
|
|
975
|
+
if (typeof value === "function") {
|
|
976
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
977
|
+
pv[key] = () => {
|
|
978
|
+
return { tag, value: value() };
|
|
979
|
+
};
|
|
980
|
+
} else {
|
|
981
|
+
pv[key] = { tag, value };
|
|
982
|
+
}
|
|
983
|
+
return pv;
|
|
984
|
+
}, {}) as ReturnType<typeof tagData>;
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Tags all provided `values` as {@link TelemetryDataTag.CodeArtifact}.
|
|
988
|
+
*
|
|
989
|
+
* @param values - The values to be tagged.
|
|
990
|
+
*
|
|
991
|
+
* @remarks
|
|
992
|
+
* It supports properties of type {@link @fluidframework/core-interfaces#TelemetryBaseEventPropertyType},
|
|
993
|
+
* as well as callbacks that return that type.
|
|
994
|
+
*
|
|
995
|
+
* @example Sample usage
|
|
996
|
+
* ```typescript
|
|
997
|
+
* {
|
|
998
|
+
* // ...Other properties being added to a telemetry event
|
|
999
|
+
* ...tagCodeArtifacts("someTag", {foo: 1, bar: 2}),
|
|
1000
|
+
* // ...
|
|
1001
|
+
* }
|
|
1002
|
+
* ```
|
|
1003
|
+
* This will result in `foo` and `bar` added to the event with their values tagged as {@link TelemetryDataTag.CodeArtifact}.
|
|
1004
|
+
*
|
|
1005
|
+
* @see {@link tagData}
|
|
1006
|
+
*
|
|
1007
|
+
* @internal
|
|
1008
|
+
*/
|
|
1009
|
+
export const tagCodeArtifacts = <
|
|
1010
|
+
T extends Record<
|
|
1011
|
+
string,
|
|
1012
|
+
TelemetryBaseEventPropertyType | (() => TelemetryBaseEventPropertyType)
|
|
1013
|
+
>,
|
|
1014
|
+
>(
|
|
1015
|
+
values: T,
|
|
1016
|
+
): {
|
|
1017
|
+
[P in keyof T]:
|
|
1018
|
+
| (T[P] extends () => TelemetryBaseEventPropertyType
|
|
1019
|
+
? () => {
|
|
1020
|
+
value: ReturnType<T[P]>;
|
|
1021
|
+
tag: TelemetryDataTag.CodeArtifact;
|
|
1022
|
+
}
|
|
1023
|
+
: {
|
|
1024
|
+
value: Exclude<T[P], undefined>;
|
|
1025
|
+
tag: TelemetryDataTag.CodeArtifact;
|
|
1026
|
+
})
|
|
1027
|
+
| (T[P] extends undefined ? undefined : never);
|
|
1028
|
+
} => tagData<TelemetryDataTag.CodeArtifact, T>(TelemetryDataTag.CodeArtifact, values);
|