@fluidframework/telemetry-utils 2.0.0-internal.6.1.0 → 2.0.0-internal.6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +2 -1
- package/CHANGELOG.md +31 -0
- package/README.md +4 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -4
- package/dist/config.js.map +1 -1
- package/dist/error.d.ts +92 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +133 -0
- package/dist/error.js.map +1 -0
- package/dist/errorLogging.d.ts +27 -11
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +42 -17
- package/dist/errorLogging.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts +2 -2
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js +4 -1
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/fluidErrorBase.d.ts +48 -15
- package/dist/fluidErrorBase.d.ts.map +1 -1
- package/dist/fluidErrorBase.js +18 -11
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +28 -16
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +41 -14
- package/dist/logger.js.map +1 -1
- package/dist/mockLogger.d.ts +14 -5
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +19 -7
- package/dist/mockLogger.js.map +1 -1
- package/dist/sampledTelemetryHelper.d.ts +8 -7
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js +10 -8
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetryTypes.d.ts +8 -4
- package/dist/telemetryTypes.d.ts.map +1 -1
- package/dist/telemetryTypes.js.map +1 -1
- package/dist/thresholdCounter.d.ts.map +1 -1
- package/dist/thresholdCounter.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +9 -4
- package/lib/config.js.map +1 -1
- package/lib/error.d.ts +92 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +125 -0
- package/lib/error.js.map +1 -0
- package/lib/errorLogging.d.ts +27 -11
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +42 -17
- package/lib/errorLogging.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts +2 -2
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js +4 -1
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/events.d.ts +1 -1
- package/lib/events.d.ts.map +1 -1
- package/lib/events.js.map +1 -1
- package/lib/fluidErrorBase.d.ts +48 -15
- package/lib/fluidErrorBase.d.ts.map +1 -1
- package/lib/fluidErrorBase.js +18 -11
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts +28 -16
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +41 -14
- package/lib/logger.js.map +1 -1
- package/lib/mockLogger.d.ts +14 -5
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +19 -7
- package/lib/mockLogger.js.map +1 -1
- package/lib/sampledTelemetryHelper.d.ts +8 -7
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js +10 -8
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetryTypes.d.ts +8 -4
- package/lib/telemetryTypes.d.ts.map +1 -1
- package/lib/telemetryTypes.js.map +1 -1
- package/lib/thresholdCounter.d.ts.map +1 -1
- package/lib/thresholdCounter.js.map +1 -1
- package/lib/utils.d.ts +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +12 -12
- package/src/config.ts +12 -7
- package/src/error.ts +202 -0
- package/src/errorLogging.ts +78 -38
- package/src/eventEmitterWithErrorHandling.ts +4 -2
- package/src/events.ts +3 -3
- package/src/fluidErrorBase.ts +62 -26
- package/src/index.ts +7 -0
- package/src/logger.ts +109 -35
- package/src/mockLogger.ts +25 -14
- package/src/sampledTelemetryHelper.ts +17 -13
- package/src/telemetryTypes.ts +20 -4
- package/src/thresholdCounter.ts +2 -2
- package/src/utils.ts +1 -1
package/lib/utils.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ import { ITelemetryBaseLogger, ITelemetryGenericEvent } from "@fluidframework/co
|
|
|
10
10
|
* @param event - The string or event to log
|
|
11
11
|
* @returns - The outcome of the condition
|
|
12
12
|
*/
|
|
13
|
-
export declare function logIfFalse(condition:
|
|
13
|
+
export declare function logIfFalse(condition: unknown, logger: ITelemetryBaseLogger, event: string | ITelemetryGenericEvent): condition is true;
|
|
14
14
|
//# sourceMappingURL=utils.d.ts.map
|
package/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAEN,oBAAoB,EACpB,sBAAsB,EACtB,MAAM,iCAAiC,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAEN,oBAAoB,EACpB,sBAAsB,EACtB,MAAM,iCAAiC,CAAC;AAEzC;;;;;;GAMG;AACH,wBAAgB,UAAU,CACzB,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,MAAM,GAAG,sBAAsB,GACpC,SAAS,IAAI,IAAI,CAUnB"}
|
package/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACzB,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACzB,SAAkB,EAClB,MAA4B,EAC5B,KAAsC;IAEtC,IAAI,SAAS,EAAE;QACd,OAAO,IAAI,CAAC;KACZ;IACD,MAAM,QAAQ,GACb,OAAO,KAAK,KAAK,QAAQ;QACxB,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE;QACzC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACpC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtB,OAAO,KAAK,CAAC;AACd,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport {\n\tITelemetryBaseEvent,\n\tITelemetryBaseLogger,\n\tITelemetryGenericEvent,\n} from \"@fluidframework/core-interfaces\";\n\n/**\n * Like assert, but logs only if the condition is false, rather than throwing\n * @param condition - The condition to attest too\n * @param logger - The logger to log with\n * @param event - The string or event to log\n * @returns - The outcome of the condition\n */\nexport function logIfFalse(\n\tcondition: unknown,\n\tlogger: ITelemetryBaseLogger,\n\tevent: string | ITelemetryGenericEvent,\n): condition is true {\n\tif (condition) {\n\t\treturn true;\n\t}\n\tconst newEvent: ITelemetryBaseEvent =\n\t\ttypeof event === \"string\"\n\t\t\t? { eventName: event, category: \"error\" }\n\t\t\t: { category: \"error\", ...event };\n\tlogger.send(newEvent);\n\treturn false;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/telemetry-utils",
|
|
3
|
-
"version": "2.0.0-internal.6.
|
|
3
|
+
"version": "2.0.0-internal.6.2.0",
|
|
4
4
|
"description": "Collection of telemetry relates utilities for Fluid",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -39,27 +39,27 @@
|
|
|
39
39
|
"temp-directory": "nyc/.nyc_output"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@fluidframework/common-definitions": "^0.20.1",
|
|
43
42
|
"@fluidframework/common-utils": "^1.1.1",
|
|
44
|
-
"@fluidframework/core-interfaces": ">=2.0.0-internal.6.
|
|
45
|
-
"@fluidframework/core-utils": ">=2.0.0-internal.6.
|
|
43
|
+
"@fluidframework/core-interfaces": ">=2.0.0-internal.6.2.0 <2.0.0-internal.6.3.0",
|
|
44
|
+
"@fluidframework/core-utils": ">=2.0.0-internal.6.2.0 <2.0.0-internal.6.3.0",
|
|
45
|
+
"@fluidframework/protocol-definitions": "^1.1.0",
|
|
46
46
|
"debug": "^4.1.1",
|
|
47
47
|
"events": "^3.1.0",
|
|
48
|
-
"uuid": "^
|
|
48
|
+
"uuid": "^9.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@fluid-tools/build-cli": "^0.
|
|
51
|
+
"@fluid-tools/build-cli": "^0.23.0",
|
|
52
52
|
"@fluidframework/build-common": "^2.0.0",
|
|
53
|
-
"@fluidframework/build-tools": "^0.
|
|
54
|
-
"@fluidframework/eslint-config-fluid": "^2.
|
|
55
|
-
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.
|
|
56
|
-
"@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@2.0.0-internal.6.
|
|
53
|
+
"@fluidframework/build-tools": "^0.23.0",
|
|
54
|
+
"@fluidframework/eslint-config-fluid": "^2.1.0",
|
|
55
|
+
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.2.0 <2.0.0-internal.6.3.0",
|
|
56
|
+
"@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@2.0.0-internal.6.1.1",
|
|
57
57
|
"@microsoft/api-extractor": "^7.34.4",
|
|
58
58
|
"@types/debug": "^4.1.5",
|
|
59
59
|
"@types/events": "^3.0.0",
|
|
60
60
|
"@types/mocha": "^9.1.1",
|
|
61
61
|
"@types/node": "^16.18.38",
|
|
62
|
-
"@types/uuid": "^
|
|
62
|
+
"@types/uuid": "^9.0.2",
|
|
63
63
|
"concurrently": "^7.6.0",
|
|
64
64
|
"copyfiles": "^2.4.1",
|
|
65
65
|
"cross-env": "^7.0.3",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"test": "npm run test:mocha",
|
|
98
98
|
"test:coverage": "nyc npm run test:report",
|
|
99
99
|
"test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
|
|
100
|
-
"test:mocha:multireport": "
|
|
100
|
+
"test:mocha:multireport": "npm run test:mocha",
|
|
101
101
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
102
102
|
"test:report": "npm test -- -- --reporter xunit --reporter-option output=nyc/mocha-junit-report.xml",
|
|
103
103
|
"tsc": "tsc",
|
package/src/config.ts
CHANGED
|
@@ -50,11 +50,12 @@ const NullConfigProvider: IConfigProviderBase = {
|
|
|
50
50
|
export const inMemoryConfigProvider = (storage: Storage | undefined): IConfigProviderBase => {
|
|
51
51
|
if (storage !== undefined && storage !== null) {
|
|
52
52
|
return new CachedConfigProvider(undefined, {
|
|
53
|
-
getRawConfig: (name: string) => {
|
|
53
|
+
getRawConfig: (name: string): ConfigTypes | undefined => {
|
|
54
54
|
try {
|
|
55
55
|
return stronglyTypedParse(storage.getItem(name) ?? undefined)?.raw;
|
|
56
|
-
} catch {
|
|
57
|
-
|
|
56
|
+
} catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
58
59
|
},
|
|
59
60
|
});
|
|
60
61
|
}
|
|
@@ -104,7 +105,7 @@ function stronglyTypedParse(input: ConfigTypes): StronglyTypedValue | undefined
|
|
|
104
105
|
// holds strings
|
|
105
106
|
if (typeof input === "string") {
|
|
106
107
|
try {
|
|
107
|
-
output = JSON.parse(input);
|
|
108
|
+
output = JSON.parse(input) as ConfigTypes;
|
|
108
109
|
// we succeeded in parsing, but we don't support parsing
|
|
109
110
|
// for any object as we can't do it type safely
|
|
110
111
|
// so in this case, the default return will be string
|
|
@@ -113,7 +114,9 @@ function stronglyTypedParse(input: ConfigTypes): StronglyTypedValue | undefined
|
|
|
113
114
|
// a false sense of security by just
|
|
114
115
|
// casting.
|
|
115
116
|
defaultReturn = { raw: input, string: input };
|
|
116
|
-
} catch {
|
|
117
|
+
} catch {
|
|
118
|
+
// No-op
|
|
119
|
+
}
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
if (output === undefined) {
|
|
@@ -144,7 +147,9 @@ function stronglyTypedParse(input: ConfigTypes): StronglyTypedValue | undefined
|
|
|
144
147
|
return defaultReturn;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* `sessionStorage` is undefined in some environments such as Node and web pages with session storage disabled.
|
|
152
|
+
*/
|
|
148
153
|
const safeSessionStorage = (): Storage | undefined => {
|
|
149
154
|
// For some configurations accessing "globalThis.sessionStorage" throws
|
|
150
155
|
// "'sessionStorage' property from 'Window': Access is denied for this document" rather than returning undefined.
|
|
@@ -264,7 +269,7 @@ export function loggerToMonitoringContext<L extends ITelemetryBaseLogger = ITele
|
|
|
264
269
|
export function mixinMonitoringContext<L extends ITelemetryBaseLogger = ITelemetryLoggerExt>(
|
|
265
270
|
logger: L,
|
|
266
271
|
...configs: (IConfigProviderBase | undefined)[]
|
|
267
|
-
) {
|
|
272
|
+
): MonitoringContext<L> {
|
|
268
273
|
if (loggerIsMonitoringContext<L>(logger)) {
|
|
269
274
|
throw new Error("Logger is already a monitoring context");
|
|
270
275
|
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
FluidErrorTypes,
|
|
8
|
+
IGenericError,
|
|
9
|
+
IErrorBase,
|
|
10
|
+
ITelemetryProperties,
|
|
11
|
+
IUsageError,
|
|
12
|
+
} from "@fluidframework/core-interfaces";
|
|
13
|
+
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
LoggingError,
|
|
17
|
+
NORMALIZED_ERROR_TYPE,
|
|
18
|
+
isExternalError,
|
|
19
|
+
normalizeError,
|
|
20
|
+
wrapError,
|
|
21
|
+
} from "./errorLogging";
|
|
22
|
+
import { IFluidErrorBase } from "./fluidErrorBase";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generic wrapper for an unrecognized/uncategorized error object
|
|
26
|
+
*/
|
|
27
|
+
export class GenericError extends LoggingError implements IGenericError, IFluidErrorBase {
|
|
28
|
+
readonly errorType = FluidErrorTypes.genericError;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new GenericError
|
|
32
|
+
* @param message - Error message
|
|
33
|
+
* @param error - inner error object
|
|
34
|
+
* @param props - Telemetry props to include when the error is logged
|
|
35
|
+
*/
|
|
36
|
+
// TODO: Use `unknown` instead (API breaking change because error is not just an input parameter, but a public member of the class)
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
38
|
+
constructor(message: string, public readonly error?: any, props?: ITelemetryProperties) {
|
|
39
|
+
// Don't try to log the inner error
|
|
40
|
+
super(message, props, new Set(["error"]));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Error indicating an API is being used improperly resulting in an invalid operation.
|
|
46
|
+
*/
|
|
47
|
+
export class UsageError extends LoggingError implements IUsageError, IFluidErrorBase {
|
|
48
|
+
readonly errorType = FluidErrorTypes.usageError;
|
|
49
|
+
|
|
50
|
+
constructor(message: string, props?: ITelemetryProperties) {
|
|
51
|
+
super(message, { ...props, usageError: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* DataCorruptionError indicates that we encountered definitive evidence that the data at rest
|
|
57
|
+
* backing this container is corrupted, and this container would never be expected to load properly again
|
|
58
|
+
*/
|
|
59
|
+
export class DataCorruptionError extends LoggingError implements IErrorBase, IFluidErrorBase {
|
|
60
|
+
readonly errorType = FluidErrorTypes.dataCorruptionError;
|
|
61
|
+
readonly canRetry = false;
|
|
62
|
+
|
|
63
|
+
constructor(message: string, props: ITelemetryProperties) {
|
|
64
|
+
super(message, { ...props, dataProcessingError: 1 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Indicates we hit a fatal error while processing incoming data from the Fluid Service.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
*
|
|
73
|
+
* The error will often originate in the dataStore or DDS implementation that is responding to incoming changes.
|
|
74
|
+
* This differs from {@link DataCorruptionError} in that this may be a transient error that will not repro in another
|
|
75
|
+
* client or session.
|
|
76
|
+
*/
|
|
77
|
+
export class DataProcessingError extends LoggingError implements IErrorBase, IFluidErrorBase {
|
|
78
|
+
/**
|
|
79
|
+
* {@inheritDoc IFluidErrorBase.errorType}
|
|
80
|
+
*/
|
|
81
|
+
public readonly errorType = FluidErrorTypes.dataProcessingError;
|
|
82
|
+
|
|
83
|
+
public readonly canRetry = false;
|
|
84
|
+
|
|
85
|
+
private constructor(errorMessage: string) {
|
|
86
|
+
super(errorMessage);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new `DataProcessingError` detected and raised within the Fluid Framework.
|
|
91
|
+
*/
|
|
92
|
+
public static create(
|
|
93
|
+
errorMessage: string,
|
|
94
|
+
dataProcessingCodepath: string,
|
|
95
|
+
sequencedMessage?: ISequencedDocumentMessage,
|
|
96
|
+
props: ITelemetryProperties = {},
|
|
97
|
+
): IFluidErrorBase {
|
|
98
|
+
const dataProcessingError = DataProcessingError.wrapIfUnrecognized(
|
|
99
|
+
errorMessage,
|
|
100
|
+
dataProcessingCodepath,
|
|
101
|
+
sequencedMessage,
|
|
102
|
+
);
|
|
103
|
+
dataProcessingError.addTelemetryProperties(props);
|
|
104
|
+
|
|
105
|
+
return dataProcessingError;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Wrap the given error in a `DataProcessingError`, unless the error is already of a known type
|
|
110
|
+
* with the exception of a normalized {@link LoggingError}, which will still be wrapped.
|
|
111
|
+
*
|
|
112
|
+
* In either case, the error will have some relevant properties added for telemetry.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
*
|
|
116
|
+
* We wrap conditionally since known error types represent well-understood failure modes, and ideally
|
|
117
|
+
* one day we will move away from throwing these errors but rather we'll return them.
|
|
118
|
+
* But an unrecognized error needs to be classified as `DataProcessingError`.
|
|
119
|
+
*
|
|
120
|
+
* @param originalError - The error to be converted.
|
|
121
|
+
* @param dataProcessingCodepath - Which code-path failed while processing data.
|
|
122
|
+
* @param messageLike - Message to include info about via telemetry props.
|
|
123
|
+
*
|
|
124
|
+
* @returns Either a new `DataProcessingError`, or (if wrapping is deemed unnecessary) the given error.
|
|
125
|
+
*/
|
|
126
|
+
public static wrapIfUnrecognized(
|
|
127
|
+
originalError: unknown,
|
|
128
|
+
dataProcessingCodepath: string,
|
|
129
|
+
messageLike?: Partial<
|
|
130
|
+
Pick<
|
|
131
|
+
ISequencedDocumentMessage,
|
|
132
|
+
| "clientId"
|
|
133
|
+
| "sequenceNumber"
|
|
134
|
+
| "clientSequenceNumber"
|
|
135
|
+
| "referenceSequenceNumber"
|
|
136
|
+
| "minimumSequenceNumber"
|
|
137
|
+
| "timestamp"
|
|
138
|
+
>
|
|
139
|
+
>,
|
|
140
|
+
): IFluidErrorBase {
|
|
141
|
+
const props = {
|
|
142
|
+
dataProcessingError: 1,
|
|
143
|
+
dataProcessingCodepath,
|
|
144
|
+
...(messageLike === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: extractSafePropertiesFromMessage(messageLike)),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const normalizedError = normalizeError(originalError, { props });
|
|
150
|
+
// Note that other errors may have the NORMALIZED_ERROR_TYPE errorType,
|
|
151
|
+
// but if so they are still suitable to be wrapped as DataProcessingError.
|
|
152
|
+
if (
|
|
153
|
+
isExternalError(normalizedError) ||
|
|
154
|
+
normalizedError.errorType === NORMALIZED_ERROR_TYPE
|
|
155
|
+
) {
|
|
156
|
+
// Create a new DataProcessingError to wrap this external error
|
|
157
|
+
const dataProcessingError = wrapError(
|
|
158
|
+
normalizedError,
|
|
159
|
+
(message: string) => new DataProcessingError(message),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Copy over the props above and any others added to this error since first being normalized
|
|
163
|
+
dataProcessingError.addTelemetryProperties(normalizedError.getTelemetryProperties());
|
|
164
|
+
|
|
165
|
+
return dataProcessingError;
|
|
166
|
+
}
|
|
167
|
+
return normalizedError;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Extracts specific properties from the provided message that we know are safe to log.
|
|
173
|
+
*
|
|
174
|
+
* @param messageLike - Message to include info about via telemetry props.
|
|
175
|
+
*/
|
|
176
|
+
export const extractSafePropertiesFromMessage = (
|
|
177
|
+
messageLike: Partial<
|
|
178
|
+
Pick<
|
|
179
|
+
ISequencedDocumentMessage,
|
|
180
|
+
| "clientId"
|
|
181
|
+
| "sequenceNumber"
|
|
182
|
+
| "clientSequenceNumber"
|
|
183
|
+
| "referenceSequenceNumber"
|
|
184
|
+
| "minimumSequenceNumber"
|
|
185
|
+
| "timestamp"
|
|
186
|
+
>
|
|
187
|
+
>,
|
|
188
|
+
): {
|
|
189
|
+
messageClientId: string | undefined;
|
|
190
|
+
messageSequenceNumber: number | undefined;
|
|
191
|
+
messageClientSequenceNumber: number | undefined;
|
|
192
|
+
messageReferenceSequenceNumber: number | undefined;
|
|
193
|
+
messageMinimumSequenceNumber: number | undefined;
|
|
194
|
+
messageTimestamp: number | undefined;
|
|
195
|
+
} => ({
|
|
196
|
+
messageClientId: messageLike.clientId === null ? "null" : messageLike.clientId,
|
|
197
|
+
messageSequenceNumber: messageLike.sequenceNumber,
|
|
198
|
+
messageClientSequenceNumber: messageLike.clientSequenceNumber,
|
|
199
|
+
messageReferenceSequenceNumber: messageLike.referenceSequenceNumber,
|
|
200
|
+
messageMinimumSequenceNumber: messageLike.minimumSequenceNumber,
|
|
201
|
+
messageTimestamp: messageLike.timestamp,
|
|
202
|
+
});
|
package/src/errorLogging.ts
CHANGED
|
@@ -22,14 +22,25 @@ import {
|
|
|
22
22
|
TelemetryEventPropertyTypeExt,
|
|
23
23
|
} from "./telemetryTypes";
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Determines if the provided value is an object but neither null nor an array.
|
|
27
|
+
*/
|
|
28
|
+
const isRegularObject = (value: unknown): boolean => {
|
|
27
29
|
return value !== null && !Array.isArray(value) && typeof value === "object";
|
|
28
30
|
};
|
|
29
31
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Inspect the given error for common "safe" props and return them.
|
|
34
|
+
*/
|
|
35
|
+
export function extractLogSafeErrorProperties(
|
|
36
|
+
error: unknown,
|
|
37
|
+
sanitizeStack: boolean,
|
|
38
|
+
): {
|
|
39
|
+
message: string;
|
|
40
|
+
errorType?: string | undefined;
|
|
41
|
+
stack?: string | undefined;
|
|
42
|
+
} {
|
|
43
|
+
const removeMessageFromStack = (stack: string, errorName?: string): string => {
|
|
33
44
|
if (!sanitizeStack) {
|
|
34
45
|
return stack;
|
|
35
46
|
}
|
|
@@ -41,14 +52,17 @@ export function extractLogSafeErrorProperties(error: any, sanitizeStack: boolean
|
|
|
41
52
|
return stackFrames.join("\n");
|
|
42
53
|
};
|
|
43
54
|
|
|
44
|
-
const message =
|
|
55
|
+
const message =
|
|
56
|
+
typeof (error as Partial<Error>)?.message === "string"
|
|
57
|
+
? (error as Error).message
|
|
58
|
+
: String(error);
|
|
45
59
|
|
|
46
60
|
const safeProps: { message: string; errorType?: string; stack?: string } = {
|
|
47
61
|
message,
|
|
48
62
|
};
|
|
49
63
|
|
|
50
64
|
if (isRegularObject(error)) {
|
|
51
|
-
const { errorType, stack, name } = error
|
|
65
|
+
const { errorType, stack, name } = error as Partial<IFluidErrorBase>;
|
|
52
66
|
|
|
53
67
|
if (typeof errorType === "string") {
|
|
54
68
|
safeProps.errorType = errorType;
|
|
@@ -63,12 +77,19 @@ export function extractLogSafeErrorProperties(error: any, sanitizeStack: boolean
|
|
|
63
77
|
return safeProps;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
/**
|
|
81
|
+
* type guard for ILoggingError interface
|
|
82
|
+
*/
|
|
83
|
+
export const isILoggingError = (x: unknown): x is ILoggingError =>
|
|
84
|
+
typeof (x as Partial<ILoggingError>)?.getTelemetryProperties === "function";
|
|
69
85
|
|
|
70
|
-
/**
|
|
71
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Copy props from source onto target, but do not overwrite an existing prop that matches
|
|
88
|
+
*/
|
|
89
|
+
function copyProps(
|
|
90
|
+
target: ITelemetryProperties | LoggingError,
|
|
91
|
+
source: ITelemetryProperties,
|
|
92
|
+
): void {
|
|
72
93
|
for (const key of Object.keys(source)) {
|
|
73
94
|
if (target[key] === undefined) {
|
|
74
95
|
target[key] = source[key];
|
|
@@ -76,16 +97,23 @@ function copyProps(target: ITelemetryProperties | LoggingError, source: ITelemet
|
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
99
|
|
|
79
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Metadata to annotate an error object when annotating or normalizing it
|
|
102
|
+
*/
|
|
80
103
|
export interface IFluidErrorAnnotations {
|
|
81
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Telemetry props to log with the error
|
|
106
|
+
*/
|
|
82
107
|
props?: ITelemetryProperties;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* For backwards compatibility with pre-errorInstanceId valid errors
|
|
112
|
+
*/
|
|
86
113
|
function patchLegacyError(
|
|
87
114
|
legacyError: Omit<IFluidErrorBase, "errorInstanceId">,
|
|
88
115
|
): asserts legacyError is IFluidErrorBase {
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
89
117
|
const patchMe: { -readonly [P in "errorInstanceId"]?: IFluidErrorBase[P] } = legacyError as any;
|
|
90
118
|
if (patchMe.errorInstanceId === undefined) {
|
|
91
119
|
patchMe.errorInstanceId = uuid();
|
|
@@ -180,8 +208,8 @@ export function generateErrorWithStack(): Error {
|
|
|
180
208
|
|
|
181
209
|
try {
|
|
182
210
|
throw err;
|
|
183
|
-
} catch (
|
|
184
|
-
return
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return error as Error;
|
|
185
213
|
}
|
|
186
214
|
}
|
|
187
215
|
|
|
@@ -230,12 +258,16 @@ export function wrapError<T extends LoggingError>(
|
|
|
230
258
|
return newError;
|
|
231
259
|
}
|
|
232
260
|
|
|
233
|
-
/**
|
|
261
|
+
/**
|
|
262
|
+
* The same as wrapError, but also logs the innerError, including the wrapping error's instance ID.
|
|
263
|
+
*
|
|
264
|
+
* @typeParam T - The kind of wrapper error to create.
|
|
265
|
+
*/
|
|
234
266
|
export function wrapErrorAndLog<T extends LoggingError>(
|
|
235
267
|
innerError: unknown,
|
|
236
268
|
newErrorFn: (message: string) => T,
|
|
237
269
|
logger: ITelemetryLoggerExt,
|
|
238
|
-
) {
|
|
270
|
+
): T {
|
|
239
271
|
const newError = wrapError(innerError, newErrorFn);
|
|
240
272
|
|
|
241
273
|
// This will match innerError.errorInstanceId if present (see wrapError)
|
|
@@ -256,11 +288,11 @@ export function wrapErrorAndLog<T extends LoggingError>(
|
|
|
256
288
|
return newError;
|
|
257
289
|
}
|
|
258
290
|
|
|
259
|
-
function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string) {
|
|
291
|
+
function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string): void {
|
|
260
292
|
// supposedly setting stack on an Error can throw.
|
|
261
293
|
try {
|
|
262
294
|
Object.assign(error, { stack });
|
|
263
|
-
} catch
|
|
295
|
+
} catch {
|
|
264
296
|
error.addTelemetryProperties({ stack2: stack });
|
|
265
297
|
}
|
|
266
298
|
}
|
|
@@ -270,17 +302,17 @@ function overwriteStack(error: IFluidErrorBase | LoggingError, stack: string) {
|
|
|
270
302
|
* False for any error we created and raised within the FF codebase via LoggingError base class,
|
|
271
303
|
* or wrapped in a well-known error type
|
|
272
304
|
*/
|
|
273
|
-
export function isExternalError(
|
|
305
|
+
export function isExternalError(error: unknown): boolean {
|
|
274
306
|
// LoggingErrors are an internal FF error type. However, an external error can be converted
|
|
275
307
|
// into a LoggingError if it is normalized. In this case we must use the untrustedOrigin flag to
|
|
276
308
|
// determine whether the original error was infact external.
|
|
277
|
-
if (LoggingError.typeCheck(
|
|
278
|
-
if ((
|
|
279
|
-
return
|
|
309
|
+
if (LoggingError.typeCheck(error)) {
|
|
310
|
+
if ((error as NormalizedLoggingError).errorType === NORMALIZED_ERROR_TYPE) {
|
|
311
|
+
return error.getTelemetryProperties().untrustedOrigin === 1;
|
|
280
312
|
}
|
|
281
313
|
return false;
|
|
282
314
|
}
|
|
283
|
-
return !isValidLegacyError(
|
|
315
|
+
return !isValidLegacyError(error);
|
|
284
316
|
}
|
|
285
317
|
|
|
286
318
|
/**
|
|
@@ -289,7 +321,7 @@ export function isExternalError(e: any): boolean {
|
|
|
289
321
|
export function isTaggedTelemetryPropertyValue(
|
|
290
322
|
x: ITaggedTelemetryPropertyTypeExt | TelemetryEventPropertyTypeExt,
|
|
291
323
|
): x is ITaggedTelemetryPropertyType | ITaggedTelemetryPropertyTypeExt {
|
|
292
|
-
return typeof (x as
|
|
324
|
+
return typeof (x as Partial<ITaggedTelemetryPropertyTypeExt>)?.tag === "string";
|
|
293
325
|
}
|
|
294
326
|
|
|
295
327
|
/**
|
|
@@ -298,7 +330,7 @@ export function isTaggedTelemetryPropertyValue(
|
|
|
298
330
|
* @returns - as-is if x is primitive. returns stringified if x is an array of primitive.
|
|
299
331
|
* otherwise returns null since this is what we support at the moment.
|
|
300
332
|
*/
|
|
301
|
-
function filterValidTelemetryProps(x:
|
|
333
|
+
function filterValidTelemetryProps(x: unknown, key: string): TelemetryEventPropertyType {
|
|
302
334
|
if (Array.isArray(x) && x.every((val) => isTelemetryEventPropertyValue(val))) {
|
|
303
335
|
return JSON.stringify(x);
|
|
304
336
|
}
|
|
@@ -311,7 +343,7 @@ function filterValidTelemetryProps(x: any, key: string): TelemetryEventPropertyT
|
|
|
311
343
|
}
|
|
312
344
|
|
|
313
345
|
// checking type of x, returns false if x is null
|
|
314
|
-
function isTelemetryEventPropertyValue(x:
|
|
346
|
+
function isTelemetryEventPropertyValue(x: unknown): x is TelemetryEventPropertyType {
|
|
315
347
|
switch (typeof x) {
|
|
316
348
|
case "string":
|
|
317
349
|
case "number":
|
|
@@ -325,13 +357,13 @@ function isTelemetryEventPropertyValue(x: any): x is TelemetryEventPropertyType
|
|
|
325
357
|
/**
|
|
326
358
|
* Walk an object's enumerable properties to find those fit for telemetry.
|
|
327
359
|
*/
|
|
328
|
-
function getValidTelemetryProps(obj:
|
|
360
|
+
function getValidTelemetryProps(obj: object, keysToOmit: Set<string>): ITelemetryProperties {
|
|
329
361
|
const props: ITelemetryProperties = {};
|
|
330
362
|
for (const key of Object.keys(obj)) {
|
|
331
363
|
if (keysToOmit.has(key)) {
|
|
332
364
|
continue;
|
|
333
365
|
}
|
|
334
|
-
const val = obj[key];
|
|
366
|
+
const val = obj[key] as ITaggedTelemetryPropertyTypeExt | TelemetryEventPropertyTypeExt;
|
|
335
367
|
|
|
336
368
|
// ensure only valid props get logged, since props of logging error could be in any shape
|
|
337
369
|
if (isTaggedTelemetryPropertyValue(val)) {
|
|
@@ -353,9 +385,11 @@ function getValidTelemetryProps(obj: any, keysToOmit: Set<string>): ITelemetryPr
|
|
|
353
385
|
* Not ideal, as will cut values that are not necessarily circular references.
|
|
354
386
|
* Could be improved by implementing Node's util.inspect() for browser (minus all the coloring code)
|
|
355
387
|
*/
|
|
356
|
-
|
|
388
|
+
// TODO: Use `unknown` instead (API breaking change)
|
|
389
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
390
|
+
export const getCircularReplacer = (): ((key: string, value: unknown) => any) => {
|
|
357
391
|
const seen = new WeakSet();
|
|
358
|
-
return (key: string, value:
|
|
392
|
+
return (key: string, value: unknown): any => {
|
|
359
393
|
if (typeof value === "object" && value !== null) {
|
|
360
394
|
if (seen.has(value)) {
|
|
361
395
|
return "<removed/circular>";
|
|
@@ -365,6 +399,7 @@ export const getCircularReplacer = () => {
|
|
|
365
399
|
return value;
|
|
366
400
|
};
|
|
367
401
|
};
|
|
402
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
368
403
|
|
|
369
404
|
/**
|
|
370
405
|
* Base class for "trusted" errors we create, whose properties can generally be logged to telemetry safely.
|
|
@@ -378,15 +413,18 @@ export class LoggingError
|
|
|
378
413
|
implements ILoggingError, Omit<IFluidErrorBase, "errorType">
|
|
379
414
|
{
|
|
380
415
|
private _errorInstanceId = uuid();
|
|
381
|
-
get errorInstanceId() {
|
|
416
|
+
get errorInstanceId(): string {
|
|
382
417
|
return this._errorInstanceId;
|
|
383
418
|
}
|
|
384
|
-
overwriteErrorInstanceId(id: string) {
|
|
419
|
+
overwriteErrorInstanceId(id: string): void {
|
|
385
420
|
this._errorInstanceId = id;
|
|
386
421
|
}
|
|
387
422
|
|
|
388
|
-
/**
|
|
423
|
+
/**
|
|
424
|
+
* Backwards compatibility to appease {@link isFluidError} in old code that may handle this error.
|
|
425
|
+
*/
|
|
389
426
|
// @ts-expect-error - This field shouldn't be referenced in the current version, but needs to exist at runtime.
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/prefer-as-const
|
|
390
428
|
private readonly fluidErrorCode: "-" = "-";
|
|
391
429
|
|
|
392
430
|
/**
|
|
@@ -430,7 +468,7 @@ export class LoggingError
|
|
|
430
468
|
/**
|
|
431
469
|
* Add additional properties to be logged
|
|
432
470
|
*/
|
|
433
|
-
public addTelemetryProperties(props: ITelemetryProperties) {
|
|
471
|
+
public addTelemetryProperties(props: ITelemetryProperties): void {
|
|
434
472
|
copyProps(this, props);
|
|
435
473
|
}
|
|
436
474
|
|
|
@@ -449,7 +487,9 @@ export class LoggingError
|
|
|
449
487
|
}
|
|
450
488
|
}
|
|
451
489
|
|
|
452
|
-
/**
|
|
490
|
+
/**
|
|
491
|
+
* The Error class used when normalizing an external error
|
|
492
|
+
*/
|
|
453
493
|
export const NORMALIZED_ERROR_TYPE = "genericError";
|
|
454
494
|
class NormalizedLoggingError extends LoggingError {
|
|
455
495
|
// errorType "genericError" is used as a default value throughout the code.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import { IEvent } from "@fluidframework/
|
|
5
|
+
import { IEvent } from "@fluidframework/core-interfaces";
|
|
6
6
|
import { TypedEventEmitter, EventEmitterEventType } from "@fluidframework/common-utils";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -14,12 +14,14 @@ export class EventEmitterWithErrorHandling<
|
|
|
14
14
|
TEvent extends IEvent = IEvent,
|
|
15
15
|
> extends TypedEventEmitter<TEvent> {
|
|
16
16
|
constructor(
|
|
17
|
+
// TODO: use `unknown` instead (breaking API change)
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
19
|
private readonly errorHandler: (eventName: EventEmitterEventType, error: any) => void,
|
|
18
20
|
) {
|
|
19
21
|
super();
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
public emit(event: EventEmitterEventType, ...args:
|
|
24
|
+
public emit(event: EventEmitterEventType, ...args: unknown[]): boolean {
|
|
23
25
|
try {
|
|
24
26
|
return super.emit(event, ...args);
|
|
25
27
|
} catch (error) {
|
package/src/events.ts
CHANGED
|
@@ -13,8 +13,8 @@ export function safeRaiseEvent(
|
|
|
13
13
|
emitter: EventEmitter,
|
|
14
14
|
logger: ITelemetryLoggerExt,
|
|
15
15
|
event: string,
|
|
16
|
-
...args
|
|
17
|
-
) {
|
|
16
|
+
...args: unknown[]
|
|
17
|
+
): void {
|
|
18
18
|
try {
|
|
19
19
|
emitter.emit(event, ...args);
|
|
20
20
|
} catch (error) {
|
|
@@ -36,7 +36,7 @@ export function raiseConnectedEvent(
|
|
|
36
36
|
connected: boolean,
|
|
37
37
|
clientId?: string,
|
|
38
38
|
disconnectedReason?: string,
|
|
39
|
-
) {
|
|
39
|
+
): void {
|
|
40
40
|
try {
|
|
41
41
|
if (connected) {
|
|
42
42
|
emitter.emit(connectedEventName, clientId);
|