@fluidframework/telemetry-utils 2.13.0 → 2.20.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/lib/mockLogger.js CHANGED
@@ -11,28 +11,7 @@ import { createChildLogger } from "./logger.js";
11
11
  * Records events sent to it, and then can walk back over those events, searching for a set of expected events to
12
12
  * match against the logged events.
13
13
  *
14
- * @deprecated
15
- *
16
- * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from
17
- * package exports in the near future.
18
- *
19
- * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
20
- * implementation, or by copying this code as-is into your own repo.
21
- *
22
- * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.
23
- *
24
- * @deprecated
25
- *
26
- * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from
27
- * package exports in the near future.
28
- *
29
- * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
30
- * implementation, or by copying this code as-is into your own repo.
31
- *
32
- * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.
33
- *
34
- * @legacy
35
- * @alpha
14
+ * @internal
36
15
  */
37
16
  export class MockLogger {
38
17
  /**
@@ -236,6 +215,22 @@ ${JSON.stringify(actualEvents)}`);
236
215
  }
237
216
  return matchObjects(actual, expected);
238
217
  }
218
+ /**
219
+ * Throws if any errors were logged
220
+ */
221
+ assertNoErrors(message, clearEventsAfterCheck = true) {
222
+ const actualEvents = this.events;
223
+ const errors = actualEvents.filter((event) => event.category === "error");
224
+ if (clearEventsAfterCheck) {
225
+ this.clear();
226
+ }
227
+ if (errors.length > 0) {
228
+ throw new Error(`${message ?? "Errors found in logs"}
229
+
230
+ error logs:
231
+ ${JSON.stringify(errors)}`);
232
+ }
233
+ }
239
234
  }
240
235
  function matchObjects(actual, expected) {
241
236
  for (const [expectedKey, expectedValue] of Object.entries(expected)) {
@@ -269,28 +264,4 @@ export function createMockLoggerExt(minLogLevel) {
269
264
  });
270
265
  return childLogger;
271
266
  }
272
- /**
273
- * Temporary extension to add new functionality during breaking change freeze,
274
- * since MockLogger wasn't able to be made internal yet.
275
- *
276
- * @internal
277
- */
278
- export class MockLogger2 extends MockLogger {
279
- /**
280
- * Throws if any errors were logged
281
- */
282
- assertNoErrors(message, clearEventsAfterCheck = true) {
283
- const actualEvents = this.events;
284
- const errors = actualEvents.filter((event) => event.category === "error");
285
- if (clearEventsAfterCheck) {
286
- this.clear();
287
- }
288
- if (errors.length > 0) {
289
- throw new Error(`${message ?? "Errors found in logs"}
290
-
291
- error logs:
292
- ${JSON.stringify(errors)}`);
293
- }
294
- }
295
- }
296
267
  //# sourceMappingURL=mockLogger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mockLogger.js","sourceRoot":"","sources":["../src/mockLogger.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport {\n\ttype ITelemetryBaseEvent,\n\ttype ITelemetryBaseLogger,\n\tLogLevel,\n\ttype Tagged,\n} from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\n\nimport { createChildLogger } from \"./logger.js\";\nimport type {\n\tITelemetryEventExt,\n\tITelemetryLoggerExt,\n\tITelemetryPropertiesExt,\n\tTelemetryEventPropertyTypeExt,\n} from \"./telemetryTypes.js\";\n\n/**\n * Mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger} implementation.\n *\n * Records events sent to it, and then can walk back over those events, searching for a set of expected events to\n * match against the logged events.\n *\n * @deprecated\n *\n * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from\n * package exports in the near future.\n *\n * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}\n * implementation, or by copying this code as-is into your own repo.\n *\n * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.\n *\n * @deprecated\n *\n * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from\n * package exports in the near future.\n *\n * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}\n * implementation, or by copying this code as-is into your own repo.\n *\n * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.\n *\n * @legacy\n * @alpha\n */\nexport class MockLogger implements ITelemetryBaseLogger {\n\t/**\n\t * Gets an immutable copy of the events logged thus far.\n\t */\n\tpublic get events(): readonly ITelemetryBaseEvent[] {\n\t\treturn [...this._events];\n\t}\n\n\tprivate _events: ITelemetryBaseEvent[] = [];\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.minLogLevel}\n\t */\n\tpublic readonly minLogLevel: LogLevel;\n\n\tpublic constructor(minLogLevel?: LogLevel) {\n\t\tthis.minLogLevel = minLogLevel ?? LogLevel.default;\n\t}\n\n\t/**\n\t * Clears the events logged thus far.\n\t */\n\tpublic clear(): void {\n\t\tthis._events = [];\n\t}\n\n\tpublic toTelemetryLogger(): ITelemetryLoggerExt {\n\t\treturn createChildLogger({ logger: this });\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}\n\t */\n\tpublic send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {\n\t\tif ((logLevel ?? LogLevel.default) >= this.minLogLevel) {\n\t\t\tthis._events.push(event);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking for the given expected\n\t * events in order.\n\t * @param expectedEvents - events in order that are expected to appear in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t */\n\tpublic matchEvents(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tconst matchedExpectedEventCount = this.getMatchedEventsCount(\n\t\t\texpectedEvents,\n\t\t\tinlineDetailsProp,\n\t\t\tclearEventsAfterCheck,\n\t\t);\n\t\t// How many expected events were left over? Hopefully none.\n\t\tconst unmatchedExpectedEventCount = expectedEvents.length - matchedExpectedEventCount;\n\t\treturn unmatchedExpectedEventCount === 0;\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchEvents} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatch(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking for any of the given\n\t * expected events.\n\t * @param expectedEvents - events that are expected to appear in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @returns if any of the expected events is found.\n\t */\n\tpublic matchAnyEvent(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tconst matchedExpectedEventCount = this.getMatchedEventsCount(\n\t\t\texpectedEvents,\n\t\t\tinlineDetailsProp,\n\t\t\tclearEventsAfterCheck,\n\t\t);\n\t\treturn matchedExpectedEventCount > 0;\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchAnyEvent} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchAny(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchAnyEvent(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking only for the given expected\n\t * events in order.\n\t * @param expectedEvents - events in order that are expected to be the only events in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t */\n\tpublic matchEventStrict(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tif (expectedEvents.length !== this._events.length) {\n\t\t\tif (clearEventsAfterCheck) {\n\t\t\t\tthis.clear();\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// `events` will be cleared by the below check if requested.\n\t\treturn this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck);\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchEvents} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchStrict(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchEventStrict(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchAnyEvent} is `false` for the given events.\n\t * @param disallowedEvents - The events expected to not appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchNone(\n\t\tdisallowedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (this.matchAnyEvent(disallowedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\ndisallowed events:\n${JSON.stringify(disallowedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\tprivate getMatchedEventsCount(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean,\n\t\tclearEventsAfterCheck: boolean,\n\t): number {\n\t\tlet iExpectedEvent = 0;\n\t\tfor (const event of this._events) {\n\t\t\tif (\n\t\t\t\tiExpectedEvent < expectedEvents.length &&\n\t\t\t\tMockLogger.eventsMatch(event, expectedEvents[iExpectedEvent], inlineDetailsProp)\n\t\t\t) {\n\t\t\t\t// We found the next expected event; increment\n\t\t\t\t++iExpectedEvent;\n\t\t\t}\n\t\t}\n\n\t\t// Remove the events so far; next call will just compare subsequent events from here\n\t\tif (clearEventsAfterCheck) {\n\t\t\tthis.clear();\n\t\t}\n\n\t\t// Return the count of matched events.\n\t\treturn iExpectedEvent;\n\t}\n\n\t/**\n\t * Ensure the expected event is a strict subset of the actual event\n\t */\n\tprivate static eventsMatch(\n\t\tactual: ITelemetryBaseEvent,\n\t\texpected: Omit<ITelemetryBaseEvent, \"category\">,\n\t\tinlineDetailsProp: boolean,\n\t): boolean {\n\t\tconst { details, ...actualForMatching } = actual;\n\t\t// \"details\" is used in a lot of telemetry logs to group a bunch of properties together and stringify them.\n\t\t// Some of the properties in the expected event may be inside \"details\". So, if inlineDetailsProp is true,\n\t\t// extract the properties from \"details\" in the actual event and inline them in the actual event.\n\t\tif (inlineDetailsProp && details !== undefined) {\n\t\t\tassert(\n\t\t\t\ttypeof details === \"string\",\n\t\t\t\t0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,\n\t\t\t);\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\tconst detailsExpanded = JSON.parse(details);\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\treturn matchObjects({ ...actualForMatching, ...detailsExpanded }, expected);\n\t\t}\n\t\treturn matchObjects(actual, expected);\n\t}\n}\n\nfunction matchObjects(\n\tactual: ITelemetryPropertiesExt,\n\texpected: ITelemetryPropertiesExt,\n): boolean {\n\tfor (const [expectedKey, expectedValue] of Object.entries(expected)) {\n\t\tconst actualValue:\n\t\t\t| TelemetryEventPropertyTypeExt\n\t\t\t| Tagged<TelemetryEventPropertyTypeExt>\n\t\t\t| undefined = actual[expectedKey];\n\t\tif (\n\t\t\t!Array.isArray(expectedValue) &&\n\t\t\texpectedValue !== null &&\n\t\t\ttypeof expectedValue === \"object\"\n\t\t) {\n\t\t\tif (\n\t\t\t\tArray.isArray(actualValue) ||\n\t\t\t\tactualValue === null ||\n\t\t\t\ttypeof actualValue !== \"object\" ||\n\t\t\t\t!matchObjects(\n\t\t\t\t\tactualValue as ITelemetryPropertiesExt,\n\t\t\t\t\texpectedValue as ITelemetryPropertiesExt,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/**\n * Mock {@link ITelemetryLoggerExt} implementation.\n *\n * @remarks Can be created via {@link createMockLoggerExt}.\n *\n * @internal\n */\nexport interface IMockLoggerExt extends ITelemetryLoggerExt {\n\t/**\n\t * Gets the events that have been logged so far.\n\t */\n\tevents(): readonly ITelemetryEventExt[];\n}\n\n/**\n * Creates an {@link IMockLoggerExt}.\n *\n * @internal\n */\nexport function createMockLoggerExt(minLogLevel?: LogLevel): IMockLoggerExt {\n\tconst mockLogger = new MockLogger(minLogLevel);\n\tconst childLogger = createChildLogger({ logger: mockLogger });\n\tObject.assign(childLogger, {\n\t\tevents: (): readonly ITelemetryEventExt[] =>\n\t\t\tmockLogger.events.map((e) => e as ITelemetryEventExt),\n\t});\n\treturn childLogger as IMockLoggerExt;\n}\n\n/**\n * Temporary extension to add new functionality during breaking change freeze,\n * since MockLogger wasn't able to be made internal yet.\n *\n * @internal\n */\nexport class MockLogger2 extends MockLogger {\n\t/**\n\t * Throws if any errors were logged\n\t */\n\tpublic assertNoErrors(message?: string, clearEventsAfterCheck: boolean = true): void {\n\t\tconst actualEvents = this.events;\n\t\tconst errors = actualEvents.filter((event) => event.category === \"error\");\n\t\tif (clearEventsAfterCheck) {\n\t\t\tthis.clear();\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tthrow new Error(`${message ?? \"Errors found in logs\"}\n\nerror logs:\n${JSON.stringify(errors)}`);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"mockLogger.js","sourceRoot":"","sources":["../src/mockLogger.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport {\n\ttype ITelemetryBaseEvent,\n\ttype ITelemetryBaseLogger,\n\tLogLevel,\n} from \"@fluidframework/core-interfaces\";\nimport { assert } from \"@fluidframework/core-utils/internal\";\n\nimport { createChildLogger } from \"./logger.js\";\nimport type {\n\tITelemetryEventExt,\n\tITelemetryLoggerExt,\n\tITelemetryPropertiesExt,\n} from \"./telemetryTypes.js\";\n\n/**\n * Mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger} implementation.\n *\n * Records events sent to it, and then can walk back over those events, searching for a set of expected events to\n * match against the logged events.\n *\n * @internal\n */\nexport class MockLogger implements ITelemetryBaseLogger {\n\t/**\n\t * Gets an immutable copy of the events logged thus far.\n\t */\n\tpublic get events(): readonly ITelemetryBaseEvent[] {\n\t\treturn [...this._events];\n\t}\n\n\tprivate _events: ITelemetryBaseEvent[] = [];\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.minLogLevel}\n\t */\n\tpublic readonly minLogLevel: LogLevel;\n\n\tpublic constructor(minLogLevel?: LogLevel) {\n\t\tthis.minLogLevel = minLogLevel ?? LogLevel.default;\n\t}\n\n\t/**\n\t * Clears the events logged thus far.\n\t */\n\tpublic clear(): void {\n\t\tthis._events = [];\n\t}\n\n\tpublic toTelemetryLogger(): ITelemetryLoggerExt {\n\t\treturn createChildLogger({ logger: this });\n\t}\n\n\t/**\n\t * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}\n\t */\n\tpublic send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {\n\t\tif ((logLevel ?? LogLevel.default) >= this.minLogLevel) {\n\t\t\tthis._events.push(event);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking for the given expected\n\t * events in order.\n\t * @param expectedEvents - events in order that are expected to appear in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t */\n\tpublic matchEvents(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tconst matchedExpectedEventCount = this.getMatchedEventsCount(\n\t\t\texpectedEvents,\n\t\t\tinlineDetailsProp,\n\t\t\tclearEventsAfterCheck,\n\t\t);\n\t\t// How many expected events were left over? Hopefully none.\n\t\tconst unmatchedExpectedEventCount = expectedEvents.length - matchedExpectedEventCount;\n\t\treturn unmatchedExpectedEventCount === 0;\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchEvents} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatch(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking for any of the given\n\t * expected events.\n\t * @param expectedEvents - events that are expected to appear in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @returns if any of the expected events is found.\n\t */\n\tpublic matchAnyEvent(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tconst matchedExpectedEventCount = this.getMatchedEventsCount(\n\t\t\texpectedEvents,\n\t\t\tinlineDetailsProp,\n\t\t\tclearEventsAfterCheck,\n\t\t);\n\t\treturn matchedExpectedEventCount > 0;\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchAnyEvent} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchAny(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchAnyEvent(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Search events logged since the last time matchEvents was called, looking only for the given expected\n\t * events in order.\n\t * @param expectedEvents - events in order that are expected to be the only events in the recorded log.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t */\n\tpublic matchEventStrict(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): boolean {\n\t\tif (expectedEvents.length !== this._events.length) {\n\t\t\tif (clearEventsAfterCheck) {\n\t\t\t\tthis.clear();\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// `events` will be cleared by the below check if requested.\n\t\treturn this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck);\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchEvents} is `true` for the given events.\n\t * @param expectedEvents - The events expected to appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchStrict(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (!this.matchEventStrict(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\nexpected:\n${JSON.stringify(expectedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\t/**\n\t * Asserts {@link MockLogger.matchAnyEvent} is `false` for the given events.\n\t * @param disallowedEvents - The events expected to not appear.\n\t * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.\n\t * @param inlineDetailsProp - true if the \"details\" property in the actual event should be extracted and inlined.\n\t * These event objects may be subsets of the logged events.\n\t * Note: category is omitted from the type because it's usually uninteresting and tedious to type.\n\t * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.\n\t * Default: true.\n\t * @throws An error containing the actual/expected event data if the condition is not satisfied.\n\t */\n\tpublic assertMatchNone(\n\t\tdisallowedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tmessage?: string,\n\t\tinlineDetailsProp: boolean = false,\n\t\tclearEventsAfterCheck: boolean = true,\n\t): void {\n\t\t// Use copy to ensure events aren't cleared out from under us before we (potentially) throw\n\t\tconst actualEvents = this.events;\n\n\t\tif (this.matchAnyEvent(disallowedEvents, inlineDetailsProp, clearEventsAfterCheck)) {\n\t\t\tthrow new Error(`${message ?? \"Logs don't match\"}\ndisallowed events:\n${JSON.stringify(disallowedEvents)}\n\nactual:\n${JSON.stringify(actualEvents)}`);\n\t\t}\n\t}\n\n\tprivate getMatchedEventsCount(\n\t\texpectedEvents: Omit<ITelemetryBaseEvent, \"category\">[],\n\t\tinlineDetailsProp: boolean,\n\t\tclearEventsAfterCheck: boolean,\n\t): number {\n\t\tlet iExpectedEvent = 0;\n\t\tfor (const event of this._events) {\n\t\t\tif (\n\t\t\t\tiExpectedEvent < expectedEvents.length &&\n\t\t\t\tMockLogger.eventsMatch(event, expectedEvents[iExpectedEvent], inlineDetailsProp)\n\t\t\t) {\n\t\t\t\t// We found the next expected event; increment\n\t\t\t\t++iExpectedEvent;\n\t\t\t}\n\t\t}\n\n\t\t// Remove the events so far; next call will just compare subsequent events from here\n\t\tif (clearEventsAfterCheck) {\n\t\t\tthis.clear();\n\t\t}\n\n\t\t// Return the count of matched events.\n\t\treturn iExpectedEvent;\n\t}\n\n\t/**\n\t * Ensure the expected event is a strict subset of the actual event\n\t */\n\tprivate static eventsMatch(\n\t\tactual: ITelemetryBaseEvent,\n\t\texpected: Omit<ITelemetryBaseEvent, \"category\">,\n\t\tinlineDetailsProp: boolean,\n\t): boolean {\n\t\tconst { details, ...actualForMatching } = actual;\n\t\t// \"details\" is used in a lot of telemetry logs to group a bunch of properties together and stringify them.\n\t\t// Some of the properties in the expected event may be inside \"details\". So, if inlineDetailsProp is true,\n\t\t// extract the properties from \"details\" in the actual event and inline them in the actual event.\n\t\tif (inlineDetailsProp && details !== undefined) {\n\t\t\tassert(\n\t\t\t\ttypeof details === \"string\",\n\t\t\t\t0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,\n\t\t\t);\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\tconst detailsExpanded = JSON.parse(details);\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\treturn matchObjects({ ...actualForMatching, ...detailsExpanded }, expected);\n\t\t}\n\t\treturn matchObjects(actual, expected);\n\t}\n\n\t/**\n\t * Throws if any errors were logged\n\t */\n\tpublic assertNoErrors(message?: string, clearEventsAfterCheck: boolean = true): void {\n\t\tconst actualEvents = this.events;\n\t\tconst errors = actualEvents.filter((event) => event.category === \"error\");\n\t\tif (clearEventsAfterCheck) {\n\t\t\tthis.clear();\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tthrow new Error(`${message ?? \"Errors found in logs\"}\n\nerror logs:\n${JSON.stringify(errors)}`);\n\t\t}\n\t}\n}\n\nfunction matchObjects(\n\tactual: ITelemetryPropertiesExt,\n\texpected: ITelemetryPropertiesExt,\n): boolean {\n\tfor (const [expectedKey, expectedValue] of Object.entries(expected)) {\n\t\tconst actualValue = actual[expectedKey];\n\t\tif (\n\t\t\t!Array.isArray(expectedValue) &&\n\t\t\texpectedValue !== null &&\n\t\t\ttypeof expectedValue === \"object\"\n\t\t) {\n\t\t\tif (\n\t\t\t\tArray.isArray(actualValue) ||\n\t\t\t\tactualValue === null ||\n\t\t\t\ttypeof actualValue !== \"object\" ||\n\t\t\t\t!matchObjects(\n\t\t\t\t\tactualValue as ITelemetryPropertiesExt,\n\t\t\t\t\texpectedValue as ITelemetryPropertiesExt,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/**\n * Mock {@link ITelemetryLoggerExt} implementation.\n *\n * @remarks Can be created via {@link createMockLoggerExt}.\n *\n * @internal\n */\nexport interface IMockLoggerExt extends ITelemetryLoggerExt {\n\t/**\n\t * Gets the events that have been logged so far.\n\t */\n\tevents(): readonly ITelemetryEventExt[];\n}\n\n/**\n * Creates an {@link IMockLoggerExt}.\n *\n * @internal\n */\nexport function createMockLoggerExt(minLogLevel?: LogLevel): IMockLoggerExt {\n\tconst mockLogger = new MockLogger(minLogLevel);\n\tconst childLogger = createChildLogger({ logger: mockLogger });\n\tObject.assign(childLogger, {\n\t\tevents: (): readonly ITelemetryEventExt[] =>\n\t\t\tmockLogger.events.map((e) => e as ITelemetryEventExt),\n\t});\n\treturn childLogger as IMockLoggerExt;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/telemetry-utils",
3
- "version": "2.13.0",
3
+ "version": "2.20.0",
4
4
  "description": "Collection of telemetry relates utilities for Fluid",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -69,22 +69,22 @@
69
69
  "temp-directory": "nyc/.nyc_output"
70
70
  },
71
71
  "dependencies": {
72
- "@fluid-internal/client-utils": "~2.13.0",
73
- "@fluidframework/core-interfaces": "~2.13.0",
74
- "@fluidframework/core-utils": "~2.13.0",
75
- "@fluidframework/driver-definitions": "~2.13.0",
72
+ "@fluid-internal/client-utils": "~2.20.0",
73
+ "@fluidframework/core-interfaces": "~2.20.0",
74
+ "@fluidframework/core-utils": "~2.20.0",
75
+ "@fluidframework/driver-definitions": "~2.20.0",
76
76
  "debug": "^4.3.4",
77
77
  "uuid": "^9.0.0"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@arethetypeswrong/cli": "^0.17.1",
81
81
  "@biomejs/biome": "~1.9.3",
82
- "@fluid-internal/mocha-test-setup": "~2.13.0",
82
+ "@fluid-internal/mocha-test-setup": "~2.20.0",
83
83
  "@fluid-tools/build-cli": "^0.51.0",
84
84
  "@fluidframework/build-common": "^2.0.3",
85
85
  "@fluidframework/build-tools": "^0.51.0",
86
86
  "@fluidframework/eslint-config-fluid": "^5.6.0",
87
- "@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@2.12.0",
87
+ "@fluidframework/telemetry-utils-previous": "npm:@fluidframework/telemetry-utils@2.13.0",
88
88
  "@microsoft/api-extractor": "7.47.8",
89
89
  "@types/debug": "^4.1.5",
90
90
  "@types/mocha": "^10.0.10",
@@ -105,7 +105,11 @@
105
105
  "typescript": "~5.4.5"
106
106
  },
107
107
  "typeValidation": {
108
- "broken": {},
108
+ "broken": {
109
+ "Class_MockLogger": {
110
+ "forwardCompat": false
111
+ }
112
+ },
109
113
  "entrypoint": "legacy"
110
114
  },
111
115
  "scripts": {
package/src/index.ts CHANGED
@@ -67,7 +67,6 @@ export {
67
67
  createMockLoggerExt,
68
68
  type IMockLoggerExt,
69
69
  MockLogger,
70
- MockLogger2,
71
70
  } from "./mockLogger.js";
72
71
  export { ThresholdCounter } from "./thresholdCounter.js";
73
72
  export {
package/src/logger.ts CHANGED
@@ -763,12 +763,16 @@ export class PerformanceEvent {
763
763
  this.reportEvent("end");
764
764
  }
765
765
  this.performanceEndMark();
766
+
767
+ // To prevent the event from being reported again later
766
768
  this.event = undefined;
767
769
  }
768
770
 
769
771
  public end(props?: ITelemetryPropertiesExt): void {
770
772
  this.reportEvent("end", props);
771
773
  this.performanceEndMark();
774
+
775
+ // To prevent the event from being reported again later
772
776
  this.event = undefined;
773
777
  }
774
778
 
@@ -785,6 +789,8 @@ export class PerformanceEvent {
785
789
  if (this.markers.cancel !== undefined) {
786
790
  this.reportEvent("cancel", { category: this.markers.cancel, ...props }, error);
787
791
  }
792
+
793
+ // To prevent the event from being reported again later
788
794
  this.event = undefined;
789
795
  }
790
796
 
@@ -796,9 +802,8 @@ export class PerformanceEvent {
796
802
  props?: ITelemetryPropertiesExt,
797
803
  error?: unknown,
798
804
  ): void {
799
- // There are strange sequences involving multiple Promise chains
800
- // where the event can be cancelled and then later a callback is invoked
801
- // and the caller attempts to end directly, e.g. issue #3936. Just return.
805
+ // If the caller invokes cancel or end directly inside the callback for timedExec[Async],
806
+ // then it's possible to come back through reportEvent twice. Only the first time counts.
802
807
  if (!this.event) {
803
808
  return;
804
809
  }
package/src/mockLogger.ts CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  type ITelemetryBaseEvent,
8
8
  type ITelemetryBaseLogger,
9
9
  LogLevel,
10
- type Tagged,
11
10
  } from "@fluidframework/core-interfaces";
12
11
  import { assert } from "@fluidframework/core-utils/internal";
13
12
 
@@ -16,7 +15,6 @@ import type {
16
15
  ITelemetryEventExt,
17
16
  ITelemetryLoggerExt,
18
17
  ITelemetryPropertiesExt,
19
- TelemetryEventPropertyTypeExt,
20
18
  } from "./telemetryTypes.js";
21
19
 
22
20
  /**
@@ -25,28 +23,7 @@ import type {
25
23
  * Records events sent to it, and then can walk back over those events, searching for a set of expected events to
26
24
  * match against the logged events.
27
25
  *
28
- * @deprecated
29
- *
30
- * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from
31
- * package exports in the near future.
32
- *
33
- * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
34
- * implementation, or by copying this code as-is into your own repo.
35
- *
36
- * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.
37
- *
38
- * @deprecated
39
- *
40
- * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from
41
- * package exports in the near future.
42
- *
43
- * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
44
- * implementation, or by copying this code as-is into your own repo.
45
- *
46
- * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.
47
- *
48
- * @legacy
49
- * @alpha
26
+ * @internal
50
27
  */
51
28
  export class MockLogger implements ITelemetryBaseLogger {
52
29
  /**
@@ -329,6 +306,23 @@ ${JSON.stringify(actualEvents)}`);
329
306
  }
330
307
  return matchObjects(actual, expected);
331
308
  }
309
+
310
+ /**
311
+ * Throws if any errors were logged
312
+ */
313
+ public assertNoErrors(message?: string, clearEventsAfterCheck: boolean = true): void {
314
+ const actualEvents = this.events;
315
+ const errors = actualEvents.filter((event) => event.category === "error");
316
+ if (clearEventsAfterCheck) {
317
+ this.clear();
318
+ }
319
+ if (errors.length > 0) {
320
+ throw new Error(`${message ?? "Errors found in logs"}
321
+
322
+ error logs:
323
+ ${JSON.stringify(errors)}`);
324
+ }
325
+ }
332
326
  }
333
327
 
334
328
  function matchObjects(
@@ -336,10 +330,7 @@ function matchObjects(
336
330
  expected: ITelemetryPropertiesExt,
337
331
  ): boolean {
338
332
  for (const [expectedKey, expectedValue] of Object.entries(expected)) {
339
- const actualValue:
340
- | TelemetryEventPropertyTypeExt
341
- | Tagged<TelemetryEventPropertyTypeExt>
342
- | undefined = actual[expectedKey];
333
+ const actualValue = actual[expectedKey];
343
334
  if (
344
335
  !Array.isArray(expectedValue) &&
345
336
  expectedValue !== null &&
@@ -391,28 +382,3 @@ export function createMockLoggerExt(minLogLevel?: LogLevel): IMockLoggerExt {
391
382
  });
392
383
  return childLogger as IMockLoggerExt;
393
384
  }
394
-
395
- /**
396
- * Temporary extension to add new functionality during breaking change freeze,
397
- * since MockLogger wasn't able to be made internal yet.
398
- *
399
- * @internal
400
- */
401
- export class MockLogger2 extends MockLogger {
402
- /**
403
- * Throws if any errors were logged
404
- */
405
- public assertNoErrors(message?: string, clearEventsAfterCheck: boolean = true): void {
406
- const actualEvents = this.events;
407
- const errors = actualEvents.filter((event) => event.category === "error");
408
- if (clearEventsAfterCheck) {
409
- this.clear();
410
- }
411
- if (errors.length > 0) {
412
- throw new Error(`${message ?? "Errors found in logs"}
413
-
414
- error logs:
415
- ${JSON.stringify(errors)}`);
416
- }
417
- }
418
- }