@fluidframework/telemetry-utils 0.57.1 → 0.58.0-55561
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/errorLogging.d.ts +17 -3
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +50 -16
- package/dist/errorLogging.js.map +1 -1
- package/dist/fluidErrorBase.d.ts +5 -6
- package/dist/fluidErrorBase.d.ts.map +1 -1
- package/dist/fluidErrorBase.js +1 -2
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/mockLogger.d.ts +2 -2
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +18 -15
- package/dist/mockLogger.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/lib/errorLogging.d.ts +17 -3
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +47 -15
- package/lib/errorLogging.js.map +1 -1
- package/lib/fluidErrorBase.d.ts +5 -6
- package/lib/fluidErrorBase.d.ts.map +1 -1
- package/lib/fluidErrorBase.js +1 -2
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/mockLogger.d.ts +2 -2
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +18 -15
- package/lib/mockLogger.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/package.json +3 -3
- package/src/errorLogging.ts +65 -25
- package/src/fluidErrorBase.ts +6 -9
- package/src/mockLogger.ts +18 -16
- package/src/packageVersion.ts +1 -1
package/src/errorLogging.ts
CHANGED
|
@@ -78,16 +78,20 @@ export interface IFluidErrorAnnotations {
|
|
|
78
78
|
props?: ITelemetryProperties;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/** For backwards compatibility with pre-
|
|
82
|
-
function
|
|
83
|
-
legacyError: Omit<IFluidErrorBase, "
|
|
81
|
+
/** For backwards compatibility with pre-errorInstanceId valid errors */
|
|
82
|
+
function patchLegacyError(
|
|
83
|
+
legacyError: Omit<IFluidErrorBase, "errorInstanceId">,
|
|
84
84
|
): asserts legacyError is IFluidErrorBase {
|
|
85
|
-
const patchMe: { -readonly [P in "
|
|
86
|
-
if (patchMe.
|
|
87
|
-
patchMe.
|
|
85
|
+
const patchMe: { -readonly [P in "errorInstanceId"]?: IFluidErrorBase[P] } = legacyError as any;
|
|
86
|
+
if (patchMe.errorInstanceId === undefined) {
|
|
87
|
+
patchMe.errorInstanceId = uuid();
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// errorType "genericError" is used as a default value throughout the code.
|
|
92
|
+
// Note that this matches ContainerErrorType/DriverErrorType's genericError
|
|
93
|
+
const defaultErrorTypeForNormalize = "genericError";
|
|
94
|
+
|
|
91
95
|
/**
|
|
92
96
|
* Normalize the given error yielding a valid Fluid Error
|
|
93
97
|
* @returns A valid Fluid Error with any provided annotations applied
|
|
@@ -100,7 +104,7 @@ export function normalizeError(
|
|
|
100
104
|
): IFluidErrorBase {
|
|
101
105
|
// Back-compat, while IFluidErrorBase is rolled out
|
|
102
106
|
if (isValidLegacyError(error)) {
|
|
103
|
-
|
|
107
|
+
patchLegacyError(error);
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
if (isFluidError(error)) {
|
|
@@ -112,8 +116,7 @@ export function normalizeError(
|
|
|
112
116
|
// We have to construct a new Fluid Error, copying safe properties over
|
|
113
117
|
const { message, stack } = extractLogSafeErrorProperties(error, false /* sanitizeStack */);
|
|
114
118
|
const fluidError: IFluidErrorBase = new SimpleFluidError({
|
|
115
|
-
errorType:
|
|
116
|
-
fluidErrorCode: "",
|
|
119
|
+
errorType: defaultErrorTypeForNormalize,
|
|
117
120
|
message,
|
|
118
121
|
stack,
|
|
119
122
|
});
|
|
@@ -171,7 +174,7 @@ export function generateStack(): string | undefined {
|
|
|
171
174
|
* @param newErrorFn - callback that will create a new error given the original error's message
|
|
172
175
|
* @returns A new error object "wrapping" the given error
|
|
173
176
|
*/
|
|
174
|
-
|
|
177
|
+
export function wrapError<T extends LoggingError>(
|
|
175
178
|
innerError: unknown,
|
|
176
179
|
newErrorFn: (message: string) => T,
|
|
177
180
|
): T {
|
|
@@ -186,7 +189,16 @@ export function generateStack(): string | undefined {
|
|
|
186
189
|
overwriteStack(newError, stack);
|
|
187
190
|
}
|
|
188
191
|
|
|
192
|
+
// Mark external errors with untrustedOrigin flag
|
|
193
|
+
if (originatedAsExternalError(innerError)) {
|
|
194
|
+
newError.addTelemetryProperties({ untrustedOrigin: 1 });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Reuse errorInstanceId
|
|
189
198
|
if (hasErrorInstanceId(innerError)) {
|
|
199
|
+
newError.overwriteErrorInstanceId(innerError.errorInstanceId);
|
|
200
|
+
|
|
201
|
+
// For "back-compat" in the logs
|
|
190
202
|
newError.addTelemetryProperties({ innerErrorInstanceId: innerError.errorInstanceId });
|
|
191
203
|
}
|
|
192
204
|
|
|
@@ -194,25 +206,29 @@ export function generateStack(): string | undefined {
|
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
/** The same as wrapError, but also logs the innerError, including the wrapping error's instance id */
|
|
197
|
-
export function wrapErrorAndLog<T extends
|
|
209
|
+
export function wrapErrorAndLog<T extends LoggingError>(
|
|
198
210
|
innerError: unknown,
|
|
199
211
|
newErrorFn: (message: string) => T,
|
|
200
212
|
logger: ITelemetryLogger,
|
|
201
213
|
) {
|
|
202
214
|
const newError = wrapError(innerError, newErrorFn);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
|
|
216
|
+
// This will match innerError.errorInstanceId if present (see wrapError)
|
|
217
|
+
const errorInstanceId = newError.errorInstanceId;
|
|
218
|
+
|
|
219
|
+
// For "back-compat" in the logs
|
|
220
|
+
const wrappedByErrorInstanceId = errorInstanceId;
|
|
206
221
|
|
|
207
222
|
logger.sendTelemetryEvent({
|
|
208
223
|
eventName: "WrapError",
|
|
224
|
+
errorInstanceId,
|
|
209
225
|
wrappedByErrorInstanceId,
|
|
210
226
|
}, innerError);
|
|
211
227
|
|
|
212
228
|
return newError;
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
function overwriteStack(error: IFluidErrorBase, stack: string) {
|
|
231
|
+
function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string) {
|
|
216
232
|
// supposedly setting stack on an Error can throw.
|
|
217
233
|
try {
|
|
218
234
|
Object.assign(error, { stack });
|
|
@@ -221,6 +237,24 @@ function overwriteStack(error: IFluidErrorBase, stack: string) {
|
|
|
221
237
|
}
|
|
222
238
|
}
|
|
223
239
|
|
|
240
|
+
/**
|
|
241
|
+
* True for any error object that is either external itself or is a wrapped/normalized external error
|
|
242
|
+
* False for any error we created and raised within the FF codebase.
|
|
243
|
+
*/
|
|
244
|
+
export function originatedAsExternalError(e: any): boolean {
|
|
245
|
+
return !isValidLegacyError(e) || (e.getTelemetryProperties().untrustedOrigin === 1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* True for any error object that is an (optionally normalized) external error
|
|
250
|
+
* False for any error we created and raised within the FF codebase, or wrapped in a well-known error type
|
|
251
|
+
*/
|
|
252
|
+
export function isExternalError(e: any): boolean {
|
|
253
|
+
return !isValidLegacyError(e) ||
|
|
254
|
+
(e.getTelemetryProperties().untrustedOrigin === 1 &&
|
|
255
|
+
e.errorType === defaultErrorTypeForNormalize);
|
|
256
|
+
}
|
|
257
|
+
|
|
224
258
|
/**
|
|
225
259
|
* Type guard to identify if a particular value (loosely) appears to be a tagged telemetry property
|
|
226
260
|
*/
|
|
@@ -287,7 +321,13 @@ export const getCircularReplacer = () => {
|
|
|
287
321
|
* PLEASE take care to avoid setting sensitive data on this object without proper tagging!
|
|
288
322
|
*/
|
|
289
323
|
export class LoggingError extends Error implements ILoggingError, Pick<IFluidErrorBase, "errorInstanceId"> {
|
|
290
|
-
|
|
324
|
+
private _errorInstanceId = uuid();
|
|
325
|
+
get errorInstanceId() { return this._errorInstanceId; }
|
|
326
|
+
overwriteErrorInstanceId(id: string) { this._errorInstanceId = id; }
|
|
327
|
+
|
|
328
|
+
/** Back-compat to appease isFluidError typeguard in old code that may handle this error */
|
|
329
|
+
// @ts-expect-error - This field shouldn't be referenced in the current version, but needs to exist at runtime.
|
|
330
|
+
private fluidErrorCode: "-" = "-";
|
|
291
331
|
|
|
292
332
|
/**
|
|
293
333
|
* Create a new LoggingError
|
|
@@ -302,8 +342,9 @@ export class LoggingError extends Error implements ILoggingError, Pick<IFluidErr
|
|
|
302
342
|
) {
|
|
303
343
|
super(message);
|
|
304
344
|
|
|
305
|
-
// Don't log this list itself
|
|
345
|
+
// Don't log this list itself, or the private _errorInstanceId
|
|
306
346
|
omitPropsFromLogging.add("omitPropsFromLogging");
|
|
347
|
+
omitPropsFromLogging.add("_errorInstanceId");
|
|
307
348
|
|
|
308
349
|
if (props) {
|
|
309
350
|
this.addTelemetryProperties(props);
|
|
@@ -322,11 +363,12 @@ export class LoggingError extends Error implements ILoggingError, Pick<IFluidErr
|
|
|
322
363
|
*/
|
|
323
364
|
public getTelemetryProperties(): ITelemetryProperties {
|
|
324
365
|
const taggableProps = getValidTelemetryProps(this, this.omitPropsFromLogging);
|
|
325
|
-
// Include non-enumerable props
|
|
366
|
+
// Include non-enumerable props that are not returned by getValidTelemetryProps
|
|
326
367
|
return {
|
|
327
368
|
...taggableProps,
|
|
328
369
|
stack: this.stack,
|
|
329
370
|
message: this.message,
|
|
371
|
+
errorInstanceId: this._errorInstanceId,
|
|
330
372
|
};
|
|
331
373
|
}
|
|
332
374
|
}
|
|
@@ -334,18 +376,16 @@ export class LoggingError extends Error implements ILoggingError, Pick<IFluidErr
|
|
|
334
376
|
/** Simple implementation of IFluidErrorBase, extending LoggingError */
|
|
335
377
|
class SimpleFluidError extends LoggingError implements IFluidErrorBase {
|
|
336
378
|
readonly errorType: string;
|
|
337
|
-
readonly fluidErrorCode: string;
|
|
338
379
|
|
|
339
380
|
constructor(
|
|
340
|
-
errorProps:
|
|
341
|
-
| "
|
|
342
|
-
| "
|
|
343
|
-
| "
|
|
344
|
-
|
|
381
|
+
errorProps: Pick<IFluidErrorBase,
|
|
382
|
+
| "message"
|
|
383
|
+
| "stack"
|
|
384
|
+
| "errorType"
|
|
385
|
+
>,
|
|
345
386
|
) {
|
|
346
387
|
super(errorProps.message);
|
|
347
388
|
this.errorType = errorProps.errorType;
|
|
348
|
-
this.fluidErrorCode = errorProps.fluidErrorCode;
|
|
349
389
|
if (errorProps.stack !== undefined) {
|
|
350
390
|
overwriteStack(this, errorProps.stack);
|
|
351
391
|
}
|
package/src/fluidErrorBase.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* All normalized errors flowing through the Fluid Framework adhere to this readonly interface.
|
|
10
|
-
* It features errorType
|
|
10
|
+
* It features errorType and errorInstanceId on top of Error's members as readonly,
|
|
11
11
|
* and a getter/setter for telemetry props to be included when the error is logged.
|
|
12
12
|
*/
|
|
13
13
|
export interface IFluidErrorBase extends Error {
|
|
@@ -15,12 +15,10 @@ export interface IFluidErrorBase extends Error {
|
|
|
15
15
|
readonly errorType: string;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Error's message property, made readonly.
|
|
19
|
+
* Be specific, but also take care when including variable data to consider suitability for aggregation in telemetry
|
|
20
|
+
* Also avoid including any data that jeopardizes the user's privacy. Add a tagged telemetry property instead.
|
|
20
21
|
*/
|
|
21
|
-
readonly fluidErrorCode: string;
|
|
22
|
-
|
|
23
|
-
/** The free-form error message */
|
|
24
22
|
readonly message: string;
|
|
25
23
|
|
|
26
24
|
/** Error's stack property, made readonly */
|
|
@@ -52,14 +50,13 @@ export const hasErrorInstanceId = (x: any): x is { errorInstanceId: string } =>
|
|
|
52
50
|
/** type guard for IFluidErrorBase interface */
|
|
53
51
|
export function isFluidError(e: any): e is IFluidErrorBase {
|
|
54
52
|
return typeof e?.errorType === "string" &&
|
|
55
|
-
typeof e?.fluidErrorCode === "string" &&
|
|
56
53
|
typeof e?.message === "string" &&
|
|
57
|
-
|
|
54
|
+
hasErrorInstanceId(e) &&
|
|
58
55
|
hasTelemetryPropFunctions(e);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
/** type guard for old standard of valid/known errors */
|
|
62
|
-
export function isValidLegacyError(e: any): e is Omit<IFluidErrorBase, "
|
|
59
|
+
export function isValidLegacyError(e: any): e is Omit<IFluidErrorBase, "errorInstanceId"> {
|
|
63
60
|
return typeof e?.errorType === "string" &&
|
|
64
61
|
typeof e?.message === "string" &&
|
|
65
62
|
hasTelemetryPropFunctions(e);
|
package/src/mockLogger.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger, ITelemetryBaseEvent } from "@fluidframework/common-definitions";
|
|
7
|
-
import { assert } from "@fluidframework/common-utils";
|
|
8
7
|
import { TelemetryLogger } from "./logger";
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -35,14 +34,16 @@ export class MockLogger extends TelemetryLogger implements ITelemetryLogger {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/** Asserts that matchEvents is true, and prints the actual/expected output if not */
|
|
38
|
-
assertMatch(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
assertMatch(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string) {
|
|
38
|
+
const actualEvents = this.events;
|
|
39
|
+
if (!this.matchEvents(expectedEvents)) {
|
|
40
|
+
throw new Error(`${message}
|
|
41
|
+
expected:
|
|
42
|
+
${JSON.stringify(expectedEvents)}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
actual:
|
|
45
|
+
${JSON.stringify(actualEvents)}`);
|
|
46
|
+
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -59,15 +60,16 @@ export class MockLogger extends TelemetryLogger implements ITelemetryLogger {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/** Asserts that matchAnyEvent is true, and prints the actual/expected output if not */
|
|
62
|
-
assertMatchAny(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]) {
|
|
63
|
-
|
|
63
|
+
assertMatchAny(expectedEvents: Omit<ITelemetryBaseEvent, "category">[], message?: string) {
|
|
64
|
+
const actualEvents = this.events;
|
|
65
|
+
if (!this.matchAnyEvent(expectedEvents)) {
|
|
66
|
+
throw new Error(`${message}
|
|
67
|
+
expected:
|
|
68
|
+
${JSON.stringify(expectedEvents)}
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
actual:
|
|
70
|
-
${JSON.stringify(actualEvents)}` */);
|
|
70
|
+
actual:
|
|
71
|
+
${JSON.stringify(actualEvents)}`);
|
|
72
|
+
}
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
private getMatchedEventsCount(expectedEvents: Omit<ITelemetryBaseEvent, "category">[]): number {
|
package/src/packageVersion.ts
CHANGED