@fluidframework/telemetry-utils 2.0.0-internal.6.1.2 → 2.0.0-internal.6.2.1

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.
Files changed (109) hide show
  1. package/.eslintrc.js +2 -1
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +4 -3
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +9 -4
  6. package/dist/config.js.map +1 -1
  7. package/dist/error.d.ts +92 -0
  8. package/dist/error.d.ts.map +1 -0
  9. package/dist/error.js +133 -0
  10. package/dist/error.js.map +1 -0
  11. package/dist/errorLogging.d.ts +27 -11
  12. package/dist/errorLogging.d.ts.map +1 -1
  13. package/dist/errorLogging.js +42 -17
  14. package/dist/errorLogging.js.map +1 -1
  15. package/dist/eventEmitterWithErrorHandling.d.ts +2 -2
  16. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  17. package/dist/eventEmitterWithErrorHandling.js +4 -1
  18. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  19. package/dist/events.d.ts +1 -1
  20. package/dist/events.d.ts.map +1 -1
  21. package/dist/events.js.map +1 -1
  22. package/dist/fluidErrorBase.d.ts +48 -15
  23. package/dist/fluidErrorBase.d.ts.map +1 -1
  24. package/dist/fluidErrorBase.js +18 -11
  25. package/dist/fluidErrorBase.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +7 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/logger.d.ts +28 -16
  31. package/dist/logger.d.ts.map +1 -1
  32. package/dist/logger.js +41 -14
  33. package/dist/logger.js.map +1 -1
  34. package/dist/mockLogger.d.ts +14 -5
  35. package/dist/mockLogger.d.ts.map +1 -1
  36. package/dist/mockLogger.js +19 -7
  37. package/dist/mockLogger.js.map +1 -1
  38. package/dist/sampledTelemetryHelper.d.ts +8 -7
  39. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  40. package/dist/sampledTelemetryHelper.js +10 -8
  41. package/dist/sampledTelemetryHelper.js.map +1 -1
  42. package/dist/telemetryTypes.d.ts +8 -4
  43. package/dist/telemetryTypes.d.ts.map +1 -1
  44. package/dist/telemetryTypes.js.map +1 -1
  45. package/dist/thresholdCounter.d.ts.map +1 -1
  46. package/dist/thresholdCounter.js.map +1 -1
  47. package/dist/utils.d.ts +1 -1
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/utils.js.map +1 -1
  50. package/lib/config.d.ts.map +1 -1
  51. package/lib/config.js +9 -4
  52. package/lib/config.js.map +1 -1
  53. package/lib/error.d.ts +92 -0
  54. package/lib/error.d.ts.map +1 -0
  55. package/lib/error.js +125 -0
  56. package/lib/error.js.map +1 -0
  57. package/lib/errorLogging.d.ts +27 -11
  58. package/lib/errorLogging.d.ts.map +1 -1
  59. package/lib/errorLogging.js +42 -17
  60. package/lib/errorLogging.js.map +1 -1
  61. package/lib/eventEmitterWithErrorHandling.d.ts +2 -2
  62. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  63. package/lib/eventEmitterWithErrorHandling.js +4 -1
  64. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  65. package/lib/events.d.ts +1 -1
  66. package/lib/events.d.ts.map +1 -1
  67. package/lib/events.js.map +1 -1
  68. package/lib/fluidErrorBase.d.ts +48 -15
  69. package/lib/fluidErrorBase.d.ts.map +1 -1
  70. package/lib/fluidErrorBase.js +18 -11
  71. package/lib/fluidErrorBase.js.map +1 -1
  72. package/lib/index.d.ts +1 -0
  73. package/lib/index.d.ts.map +1 -1
  74. package/lib/index.js +1 -0
  75. package/lib/index.js.map +1 -1
  76. package/lib/logger.d.ts +28 -16
  77. package/lib/logger.d.ts.map +1 -1
  78. package/lib/logger.js +41 -14
  79. package/lib/logger.js.map +1 -1
  80. package/lib/mockLogger.d.ts +14 -5
  81. package/lib/mockLogger.d.ts.map +1 -1
  82. package/lib/mockLogger.js +19 -7
  83. package/lib/mockLogger.js.map +1 -1
  84. package/lib/sampledTelemetryHelper.d.ts +8 -7
  85. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  86. package/lib/sampledTelemetryHelper.js +10 -8
  87. package/lib/sampledTelemetryHelper.js.map +1 -1
  88. package/lib/telemetryTypes.d.ts +8 -4
  89. package/lib/telemetryTypes.d.ts.map +1 -1
  90. package/lib/telemetryTypes.js.map +1 -1
  91. package/lib/thresholdCounter.d.ts.map +1 -1
  92. package/lib/thresholdCounter.js.map +1 -1
  93. package/lib/utils.d.ts +1 -1
  94. package/lib/utils.d.ts.map +1 -1
  95. package/lib/utils.js.map +1 -1
  96. package/package.json +11 -11
  97. package/src/config.ts +12 -7
  98. package/src/error.ts +202 -0
  99. package/src/errorLogging.ts +78 -38
  100. package/src/eventEmitterWithErrorHandling.ts +4 -2
  101. package/src/events.ts +3 -3
  102. package/src/fluidErrorBase.ts +62 -26
  103. package/src/index.ts +7 -0
  104. package/src/logger.ts +109 -35
  105. package/src/mockLogger.ts +25 -14
  106. package/src/sampledTelemetryHelper.ts +17 -13
  107. package/src/telemetryTypes.ts +20 -4
  108. package/src/thresholdCounter.ts +2 -2
  109. 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: any, logger: ITelemetryBaseLogger, event: string | ITelemetryGenericEvent): condition is true;
13
+ export declare function logIfFalse(condition: unknown, logger: ITelemetryBaseLogger, event: string | ITelemetryGenericEvent): condition is true;
14
14
  //# sourceMappingURL=utils.d.ts.map
@@ -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,GAAG,EACd,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,MAAM,GAAG,sBAAsB,GACpC,SAAS,IAAI,IAAI,CAUnB"}
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,SAAc,EACd,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: any,\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"]}
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.1.2",
3
+ "version": "2.0.0-internal.6.2.1",
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.1.2 <2.0.0-internal.6.2.0",
45
- "@fluidframework/core-utils": ">=2.0.0-internal.6.1.2 <2.0.0-internal.6.2.0",
43
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.6.2.1 <2.0.0-internal.6.3.0",
44
+ "@fluidframework/core-utils": ">=2.0.0-internal.6.2.1 <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": "^8.3.1"
48
+ "uuid": "^9.0.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@fluid-tools/build-cli": "^0.22.0",
51
+ "@fluid-tools/build-cli": "^0.23.0",
52
52
  "@fluidframework/build-common": "^2.0.0",
53
- "@fluidframework/build-tools": "^0.22.0",
54
- "@fluidframework/eslint-config-fluid": "^2.0.0",
55
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.1.2 <2.0.0-internal.6.2.0",
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.1 <2.0.0-internal.6.3.0",
56
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": "^8.3.0",
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": "cross-env FLUID_TEST_MULTIREPORT=1 npm run test:mocha",
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
- return undefined;
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
- /** `sessionStorage` is undefined in some environments such as Node and web pages with session storage disabled */
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
+ });
@@ -22,14 +22,25 @@ import {
22
22
  TelemetryEventPropertyTypeExt,
23
23
  } from "./telemetryTypes";
24
24
 
25
- /** @returns true if value is an object but neither null nor an array */
26
- const isRegularObject = (value: any): boolean => {
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
- /** Inspect the given error for common "safe" props and return them */
31
- export function extractLogSafeErrorProperties(error: any, sanitizeStack: boolean) {
32
- const removeMessageFromStack = (stack: string, errorName?: string) => {
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 = typeof error?.message === "string" ? (error.message as string) : String(error);
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
- /** type guard for ILoggingError interface */
67
- export const isILoggingError = (x: any): x is ILoggingError =>
68
- typeof x?.getTelemetryProperties === "function";
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
- /** Copy props from source onto target, but do not overwrite an existing prop that matches */
71
- function copyProps(target: ITelemetryProperties | LoggingError, source: ITelemetryProperties) {
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
- /** Metadata to annotate an error object when annotating or normalizing it */
100
+ /**
101
+ * Metadata to annotate an error object when annotating or normalizing it
102
+ */
80
103
  export interface IFluidErrorAnnotations {
81
- /** Telemetry props to log with the error */
104
+ /**
105
+ * Telemetry props to log with the error
106
+ */
82
107
  props?: ITelemetryProperties;
83
108
  }
84
109
 
85
- /** For backwards compatibility with pre-errorInstanceId valid errors */
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 (e) {
184
- return e as Error;
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
- /** The same as wrapError, but also logs the innerError, including the wrapping error's instance id */
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 (errorSettingStack) {
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(e: any): boolean {
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(e)) {
278
- if ((e as NormalizedLoggingError).errorType === NORMALIZED_ERROR_TYPE) {
279
- return e.getTelemetryProperties().untrustedOrigin === 1;
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(e);
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 any)?.tag === "string";
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: any, key: string): TelemetryEventPropertyType {
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: any): x is TelemetryEventPropertyType {
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: any, keysToOmit: Set<string>): ITelemetryProperties {
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
- export const getCircularReplacer = () => {
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: any): any => {
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
- /** Back-compat to appease isFluidError typeguard in old code that may handle this error */
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
- /** The Error class used when normalizing an external error */
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/common-definitions";
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: any[]): boolean {
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);