@fluidframework/telemetry-utils 2.0.0-rc.4.0.6 → 2.0.0-rc.5.0.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.
Files changed (133) hide show
  1. package/.eslintrc.cjs +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  4. package/api-extractor/api-extractor-lint-legacy.cjs.json +5 -0
  5. package/api-extractor/api-extractor-lint-legacy.esm.json +5 -0
  6. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  7. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  8. package/api-extractor.json +1 -1
  9. package/api-report/telemetry-utils.alpha.api.md +107 -0
  10. package/api-report/telemetry-utils.beta.api.md +29 -0
  11. package/api-report/telemetry-utils.public.api.md +29 -0
  12. package/biome.jsonc +4 -0
  13. package/dist/config.d.ts +2 -2
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/error.d.ts +5 -5
  17. package/dist/error.d.ts.map +1 -1
  18. package/dist/error.js.map +1 -1
  19. package/dist/errorLogging.d.ts +2 -6
  20. package/dist/errorLogging.d.ts.map +1 -1
  21. package/dist/errorLogging.js +22 -29
  22. package/dist/errorLogging.js.map +1 -1
  23. package/dist/eventEmitterWithErrorHandling.d.ts +2 -2
  24. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  25. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  26. package/dist/events.js.map +1 -1
  27. package/dist/fluidErrorBase.d.ts +0 -6
  28. package/dist/fluidErrorBase.d.ts.map +1 -1
  29. package/dist/fluidErrorBase.js +1 -12
  30. package/dist/fluidErrorBase.js.map +1 -1
  31. package/dist/index.d.ts +8 -7
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/logger.d.ts +3 -5
  36. package/dist/logger.d.ts.map +1 -1
  37. package/dist/logger.js.map +1 -1
  38. package/dist/mathTools.d.ts +13 -0
  39. package/dist/mathTools.d.ts.map +1 -0
  40. package/dist/mathTools.js +20 -0
  41. package/dist/mathTools.js.map +1 -0
  42. package/dist/mockLogger.d.ts +99 -19
  43. package/dist/mockLogger.d.ts.map +1 -1
  44. package/dist/mockLogger.js +120 -33
  45. package/dist/mockLogger.js.map +1 -1
  46. package/dist/sampledTelemetryHelper.d.ts +6 -2
  47. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  48. package/dist/sampledTelemetryHelper.js +11 -3
  49. package/dist/sampledTelemetryHelper.js.map +1 -1
  50. package/dist/telemetryEventBatcher.d.ts +64 -0
  51. package/dist/telemetryEventBatcher.d.ts.map +1 -0
  52. package/dist/telemetryEventBatcher.js +86 -0
  53. package/dist/telemetryEventBatcher.js.map +1 -0
  54. package/dist/telemetryTypes.d.ts +32 -18
  55. package/dist/telemetryTypes.d.ts.map +1 -1
  56. package/dist/telemetryTypes.js.map +1 -1
  57. package/dist/thresholdCounter.d.ts +1 -1
  58. package/dist/thresholdCounter.d.ts.map +1 -1
  59. package/dist/thresholdCounter.js.map +1 -1
  60. package/dist/utils.d.ts +14 -2
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +30 -2
  63. package/dist/utils.js.map +1 -1
  64. package/lib/config.d.ts +2 -2
  65. package/lib/config.d.ts.map +1 -1
  66. package/lib/config.js.map +1 -1
  67. package/lib/error.d.ts +5 -5
  68. package/lib/error.d.ts.map +1 -1
  69. package/lib/error.js.map +1 -1
  70. package/lib/errorLogging.d.ts +2 -6
  71. package/lib/errorLogging.d.ts.map +1 -1
  72. package/lib/errorLogging.js +23 -30
  73. package/lib/errorLogging.js.map +1 -1
  74. package/lib/eventEmitterWithErrorHandling.d.ts +2 -2
  75. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  76. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  77. package/lib/events.js.map +1 -1
  78. package/lib/fluidErrorBase.d.ts +0 -6
  79. package/lib/fluidErrorBase.d.ts.map +1 -1
  80. package/lib/fluidErrorBase.js +0 -10
  81. package/lib/fluidErrorBase.js.map +1 -1
  82. package/lib/index.d.ts +8 -7
  83. package/lib/index.d.ts.map +1 -1
  84. package/lib/index.js +4 -3
  85. package/lib/index.js.map +1 -1
  86. package/lib/logger.d.ts +3 -5
  87. package/lib/logger.d.ts.map +1 -1
  88. package/lib/logger.js.map +1 -1
  89. package/lib/mathTools.d.ts +13 -0
  90. package/lib/mathTools.d.ts.map +1 -0
  91. package/lib/mathTools.js +16 -0
  92. package/lib/mathTools.js.map +1 -0
  93. package/lib/mockLogger.d.ts +99 -19
  94. package/lib/mockLogger.d.ts.map +1 -1
  95. package/lib/mockLogger.js +118 -32
  96. package/lib/mockLogger.js.map +1 -1
  97. package/lib/sampledTelemetryHelper.d.ts +6 -2
  98. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  99. package/lib/sampledTelemetryHelper.js +11 -3
  100. package/lib/sampledTelemetryHelper.js.map +1 -1
  101. package/lib/telemetryEventBatcher.d.ts +64 -0
  102. package/lib/telemetryEventBatcher.d.ts.map +1 -0
  103. package/lib/telemetryEventBatcher.js +82 -0
  104. package/lib/telemetryEventBatcher.js.map +1 -0
  105. package/lib/telemetryTypes.d.ts +32 -18
  106. package/lib/telemetryTypes.d.ts.map +1 -1
  107. package/lib/telemetryTypes.js.map +1 -1
  108. package/lib/thresholdCounter.d.ts +1 -1
  109. package/lib/thresholdCounter.d.ts.map +1 -1
  110. package/lib/thresholdCounter.js.map +1 -1
  111. package/lib/tsdoc-metadata.json +1 -1
  112. package/lib/utils.d.ts +14 -2
  113. package/lib/utils.d.ts.map +1 -1
  114. package/lib/utils.js +28 -1
  115. package/lib/utils.js.map +1 -1
  116. package/package.json +30 -16
  117. package/src/config.ts +16 -16
  118. package/src/error.ts +13 -13
  119. package/src/errorLogging.ts +32 -46
  120. package/src/eventEmitterWithErrorHandling.ts +3 -3
  121. package/src/fluidErrorBase.ts +0 -15
  122. package/src/index.ts +17 -16
  123. package/src/logger.ts +21 -21
  124. package/src/mathTools.ts +16 -0
  125. package/src/mockLogger.ts +165 -35
  126. package/src/sampledTelemetryHelper.ts +15 -5
  127. package/src/telemetryEventBatcher.ts +103 -0
  128. package/src/telemetryTypes.ts +38 -19
  129. package/src/thresholdCounter.ts +1 -1
  130. package/src/utils.ts +31 -2
  131. package/tsconfig.json +2 -0
  132. package/tsdoc.json +4 -0
  133. package/api-report/telemetry-utils.api.md +0 -425
package/src/mockLogger.ts CHANGED
@@ -4,36 +4,74 @@
4
4
  */
5
5
 
6
6
  import {
7
- ITelemetryBaseEvent,
8
- ITelemetryBaseLogger,
7
+ type ITelemetryBaseEvent,
8
+ type ITelemetryBaseLogger,
9
9
  LogLevel,
10
10
  } from "@fluidframework/core-interfaces";
11
11
  import { assert } from "@fluidframework/core-utils/internal";
12
12
 
13
13
  import { createChildLogger } from "./logger.js";
14
- import { ITelemetryLoggerExt, ITelemetryPropertiesExt } from "./telemetryTypes.js";
14
+ import type {
15
+ ITelemetryEventExt,
16
+ ITelemetryLoggerExt,
17
+ ITelemetryPropertiesExt,
18
+ } from "./telemetryTypes.js";
15
19
 
16
20
  /**
17
- * The MockLogger records events sent to it, and then can walk back over those events
18
- * searching for a set of expected events to match against the logged events.
21
+ * Mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger} implementation.
22
+ *
23
+ * Records events sent to it, and then can walk back over those events, searching for a set of expected events to
24
+ * match against the logged events.
25
+ *
26
+ * @deprecated
27
+ *
28
+ * This class is not intended for use outside of the `fluid-framework` repo, and will be removed from
29
+ * package exports in the near future.
30
+ *
31
+ * Please migrate usages by either creating your own mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
32
+ * implementation, or by copying this code as-is into your own repo.
33
+ *
34
+ * @privateRemarks TODO: When we are ready, this type should be made `internal`, and the deprecation notice should be removed.
19
35
  *
20
36
  * @alpha
21
37
  */
22
38
  export class MockLogger implements ITelemetryBaseLogger {
23
- events: ITelemetryBaseEvent[] = [];
39
+ /**
40
+ * Gets an immutable copy of the events logged thus far.
41
+ */
42
+ public get events(): readonly ITelemetryBaseEvent[] {
43
+ return [...this._events];
44
+ }
45
+
46
+ private _events: ITelemetryBaseEvent[] = [];
24
47
 
25
- constructor(public readonly minLogLevel?: LogLevel) {}
48
+ /**
49
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.minLogLevel}
50
+ */
51
+ public readonly minLogLevel: LogLevel;
26
52
 
27
- clear(): void {
28
- this.events = [];
53
+ public constructor(minLogLevel?: LogLevel) {
54
+ this.minLogLevel = minLogLevel ?? LogLevel.default;
29
55
  }
30
56
 
31
- toTelemetryLogger(): ITelemetryLoggerExt {
57
+ /**
58
+ * Clears the events logged thus far.
59
+ */
60
+ public clear(): void {
61
+ this._events = [];
62
+ }
63
+
64
+ public toTelemetryLogger(): ITelemetryLoggerExt {
32
65
  return createChildLogger({ logger: this });
33
66
  }
34
67
 
35
- send(event: ITelemetryBaseEvent): void {
36
- this.events.push(event);
68
+ /**
69
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}
70
+ */
71
+ public send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
72
+ if (logLevel ?? LogLevel.default >= this.minLogLevel) {
73
+ this._events.push(event);
74
+ }
37
75
  }
38
76
 
39
77
  /**
@@ -43,14 +81,18 @@ export class MockLogger implements ITelemetryBaseLogger {
43
81
  * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
44
82
  * These event objects may be subsets of the logged events.
45
83
  * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
84
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
85
+ * Default: true.
46
86
  */
47
- matchEvents(
87
+ public matchEvents(
48
88
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
49
89
  inlineDetailsProp: boolean = false,
90
+ clearEventsAfterCheck: boolean = true,
50
91
  ): boolean {
51
92
  const matchedExpectedEventCount = this.getMatchedEventsCount(
52
93
  expectedEvents,
53
94
  inlineDetailsProp,
95
+ clearEventsAfterCheck,
54
96
  );
55
97
  // How many expected events were left over? Hopefully none.
56
98
  const unmatchedExpectedEventCount = expectedEvents.length - matchedExpectedEventCount;
@@ -58,15 +100,26 @@ export class MockLogger implements ITelemetryBaseLogger {
58
100
  }
59
101
 
60
102
  /**
61
- * Asserts that matchEvents is true, and prints the actual/expected output if not.
103
+ * Asserts {@link MockLogger.matchEvents} is `true` for the given events.
104
+ * @param expectedEvents - The events expected to appear.
105
+ * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.
106
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
107
+ * These event objects may be subsets of the logged events.
108
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
109
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
110
+ * Default: true.
111
+ * @throws An error containing the actual/expected event data if the condition is not satisfied.
62
112
  */
63
- assertMatch(
113
+ public assertMatch(
64
114
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
65
115
  message?: string,
66
116
  inlineDetailsProp: boolean = false,
117
+ clearEventsAfterCheck: boolean = true,
67
118
  ): void {
119
+ // Use copy to ensure events aren't cleared out from under us before we (potentially) throw
68
120
  const actualEvents = this.events;
69
- if (!this.matchEvents(expectedEvents, inlineDetailsProp)) {
121
+
122
+ if (!this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {
70
123
  throw new Error(`${message ?? "Logs don't match"}
71
124
  expected:
72
125
  ${JSON.stringify(expectedEvents)}
@@ -85,27 +138,40 @@ ${JSON.stringify(actualEvents)}`);
85
138
  * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
86
139
  * @returns if any of the expected events is found.
87
140
  */
88
- matchAnyEvent(
141
+ public matchAnyEvent(
89
142
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
90
143
  inlineDetailsProp: boolean = false,
144
+ clearEventsAfterCheck: boolean = true,
91
145
  ): boolean {
92
146
  const matchedExpectedEventCount = this.getMatchedEventsCount(
93
147
  expectedEvents,
94
148
  inlineDetailsProp,
149
+ clearEventsAfterCheck,
95
150
  );
96
151
  return matchedExpectedEventCount > 0;
97
152
  }
98
153
 
99
154
  /**
100
- * Asserts that matchAnyEvent is true, and prints the actual/expected output if not.
155
+ * Asserts {@link MockLogger.matchAnyEvent} is `true` for the given events.
156
+ * @param expectedEvents - The events expected to appear.
157
+ * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.
158
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
159
+ * These event objects may be subsets of the logged events.
160
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
161
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
162
+ * Default: true.
163
+ * @throws An error containing the actual/expected event data if the condition is not satisfied.
101
164
  */
102
- assertMatchAny(
165
+ public assertMatchAny(
103
166
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
104
167
  message?: string,
105
168
  inlineDetailsProp: boolean = false,
169
+ clearEventsAfterCheck: boolean = true,
106
170
  ): void {
171
+ // Use copy to ensure events aren't cleared out from under us before we (potentially) throw
107
172
  const actualEvents = this.events;
108
- if (!this.matchAnyEvent(expectedEvents, inlineDetailsProp)) {
173
+
174
+ if (!this.matchAnyEvent(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {
109
175
  throw new Error(`${message ?? "Logs don't match"}
110
176
  expected:
111
177
  ${JSON.stringify(expectedEvents)}
@@ -122,27 +188,46 @@ ${JSON.stringify(actualEvents)}`);
122
188
  * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
123
189
  * These event objects may be subsets of the logged events.
124
190
  * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
191
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
192
+ * Default: true.
125
193
  */
126
- matchEventStrict(
194
+ public matchEventStrict(
127
195
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
128
196
  inlineDetailsProp: boolean = false,
197
+ clearEventsAfterCheck: boolean = true,
129
198
  ): boolean {
130
- return (
131
- expectedEvents.length === this.events.length &&
132
- this.matchEvents(expectedEvents, inlineDetailsProp)
133
- );
199
+ if (expectedEvents.length !== this._events.length) {
200
+ if (clearEventsAfterCheck) {
201
+ this.clear();
202
+ }
203
+ return false;
204
+ }
205
+
206
+ // `events` will be cleared by the below check if requested.
207
+ return this.matchEvents(expectedEvents, inlineDetailsProp, clearEventsAfterCheck);
134
208
  }
135
209
 
136
210
  /**
137
- * Asserts that matchEvents is true, and prints the actual/expected output if not
211
+ * Asserts {@link MockLogger.matchEvents} is `true` for the given events.
212
+ * @param expectedEvents - The events expected to appear.
213
+ * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.
214
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
215
+ * These event objects may be subsets of the logged events.
216
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
217
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
218
+ * Default: true.
219
+ * @throws An error containing the actual/expected event data if the condition is not satisfied.
138
220
  */
139
- assertMatchStrict(
221
+ public assertMatchStrict(
140
222
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
141
223
  message?: string,
142
224
  inlineDetailsProp: boolean = false,
225
+ clearEventsAfterCheck: boolean = true,
143
226
  ): void {
227
+ // Use copy to ensure events aren't cleared out from under us before we (potentially) throw
144
228
  const actualEvents = this.events;
145
- if (!this.matchEventStrict(expectedEvents, inlineDetailsProp)) {
229
+
230
+ if (!this.matchEventStrict(expectedEvents, inlineDetailsProp, clearEventsAfterCheck)) {
146
231
  throw new Error(`${message ?? "Logs don't match"}
147
232
  expected:
148
233
  ${JSON.stringify(expectedEvents)}
@@ -153,15 +238,26 @@ ${JSON.stringify(actualEvents)}`);
153
238
  }
154
239
 
155
240
  /**
156
- * Asserts that matchAnyEvent is false for the given events, and prints the actual/expected output if not
241
+ * Asserts {@link MockLogger.matchAnyEvent} is `false` for the given events.
242
+ * @param disallowedEvents - The events expected to not appear.
243
+ * @param message - Optional error message to include in the thrown error, if the condition is not satisfied.
244
+ * @param inlineDetailsProp - true if the "details" property in the actual event should be extracted and inlined.
245
+ * These event objects may be subsets of the logged events.
246
+ * Note: category is omitted from the type because it's usually uninteresting and tedious to type.
247
+ * @param clearEventsAfterCheck - Whether or not to clear the logger's {@link MockLogger.events} after performing the check.
248
+ * Default: true.
249
+ * @throws An error containing the actual/expected event data if the condition is not satisfied.
157
250
  */
158
- assertMatchNone(
251
+ public assertMatchNone(
159
252
  disallowedEvents: Omit<ITelemetryBaseEvent, "category">[],
160
253
  message?: string,
161
254
  inlineDetailsProp: boolean = false,
255
+ clearEventsAfterCheck: boolean = true,
162
256
  ): void {
257
+ // Use copy to ensure events aren't cleared out from under us before we (potentially) throw
163
258
  const actualEvents = this.events;
164
- if (this.matchAnyEvent(disallowedEvents, inlineDetailsProp)) {
259
+
260
+ if (this.matchAnyEvent(disallowedEvents, inlineDetailsProp, clearEventsAfterCheck)) {
165
261
  throw new Error(`${message ?? "Logs don't match"}
166
262
  disallowed events:
167
263
  ${JSON.stringify(disallowedEvents)}
@@ -174,9 +270,10 @@ ${JSON.stringify(actualEvents)}`);
174
270
  private getMatchedEventsCount(
175
271
  expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
176
272
  inlineDetailsProp: boolean,
273
+ clearEventsAfterCheck: boolean,
177
274
  ): number {
178
275
  let iExpectedEvent = 0;
179
- for (const event of this.events) {
276
+ for (const event of this._events) {
180
277
  if (
181
278
  iExpectedEvent < expectedEvents.length &&
182
279
  MockLogger.eventsMatch(event, expectedEvents[iExpectedEvent], inlineDetailsProp)
@@ -187,7 +284,9 @@ ${JSON.stringify(actualEvents)}`);
187
284
  }
188
285
 
189
286
  // Remove the events so far; next call will just compare subsequent events from here
190
- this.events = [];
287
+ if (clearEventsAfterCheck) {
288
+ this.clear();
289
+ }
191
290
 
192
291
  // Return the count of matched events.
193
292
  return iExpectedEvent;
@@ -208,7 +307,6 @@ ${JSON.stringify(actualEvents)}`);
208
307
  if (inlineDetailsProp && details !== undefined) {
209
308
  assert(
210
309
  typeof details === "string",
211
- // eslint-disable-next-line unicorn/numeric-separators-style
212
310
  0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,
213
311
  );
214
312
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -220,7 +318,10 @@ ${JSON.stringify(actualEvents)}`);
220
318
  }
221
319
  }
222
320
 
223
- function matchObjects(actual: ITelemetryPropertiesExt, expected: ITelemetryPropertiesExt): boolean {
321
+ function matchObjects(
322
+ actual: ITelemetryPropertiesExt,
323
+ expected: ITelemetryPropertiesExt,
324
+ ): boolean {
224
325
  for (const [expectedKey, expectedValue] of Object.entries(expected)) {
225
326
  const actualValue = actual[expectedKey];
226
327
  if (
@@ -245,3 +346,32 @@ function matchObjects(actual: ITelemetryPropertiesExt, expected: ITelemetryPrope
245
346
  }
246
347
  return true;
247
348
  }
349
+
350
+ /**
351
+ * Mock {@link ITelemetryLoggerExt} implementation.
352
+ *
353
+ * @remarks Can be created via {@link createMockLoggerExt}.
354
+ *
355
+ * @internal
356
+ */
357
+ export interface IMockLoggerExt extends ITelemetryLoggerExt {
358
+ /**
359
+ * Gets the events that have been logged so far.
360
+ */
361
+ events(): readonly ITelemetryEventExt[];
362
+ }
363
+
364
+ /**
365
+ * Creates an {@link IMockLoggerExt}.
366
+ *
367
+ * @internal
368
+ */
369
+ export function createMockLoggerExt(minLogLevel?: LogLevel): IMockLoggerExt {
370
+ const mockLogger = new MockLogger(minLogLevel);
371
+ const childLogger = createChildLogger({ logger: mockLogger });
372
+ Object.assign(childLogger, {
373
+ events: (): readonly ITelemetryEventExt[] =>
374
+ mockLogger.events.map((e) => e as ITelemetryEventExt),
375
+ });
376
+ return childLogger as IMockLoggerExt;
377
+ }
@@ -6,10 +6,10 @@
6
6
  import { performance } from "@fluid-internal/client-utils";
7
7
  import type { IDisposable, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
8
8
 
9
- import {
10
- type ITelemetryGenericEventExt,
9
+ import type {
10
+ ITelemetryGenericEventExt,
11
11
  ITelemetryLoggerExt,
12
- type ITelemetryPerformanceEventExt,
12
+ ITelemetryPerformanceEventExt,
13
13
  } from "./telemetryTypes.js";
14
14
 
15
15
  /**
@@ -58,7 +58,14 @@ interface Measurements {
58
58
  * @internal
59
59
  */
60
60
  export class SampledTelemetryHelper implements IDisposable {
61
- disposed: boolean = false;
61
+ private _disposed: boolean = false;
62
+
63
+ /**
64
+ * {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}
65
+ */
66
+ public get disposed(): boolean {
67
+ return this._disposed;
68
+ }
62
69
 
63
70
  private readonly measurementsMap = new Map<string, Measurements>();
64
71
 
@@ -145,6 +152,9 @@ export class SampledTelemetryHelper implements IDisposable {
145
152
  }
146
153
 
147
154
  public dispose(error?: Error | undefined): void {
148
- for (const [k] of this.measurementsMap.entries()) this.flushBucket(k);
155
+ for (const [k] of this.measurementsMap.entries()) {
156
+ this.flushBucket(k);
157
+ }
158
+ this._disposed = true;
149
159
  }
150
160
  }
@@ -0,0 +1,103 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { roundToDecimalPlaces } from "./mathTools.js";
7
+ import type {
8
+ ITelemetryGenericEventExt,
9
+ ITelemetryLoggerExt,
10
+ ITelemetryPerformanceEventExt,
11
+ } from "./telemetryTypes.js";
12
+
13
+ /**
14
+ * Telemetry class that accumulates measurements which are eventually logged in a telemetry event through the provided
15
+ * {@link TelemetryEventBatcher.logger | logger} when the number of calls to the function reaches the specified {@link TelemetryEventBatcher.threshold | threshold}.
16
+ *
17
+ * @remarks It is expected to be used for a single event type. If different properties should be logged at different times, a separate `TelemetryEventBatcher` should be created with separate `TMetrics` type.
18
+ * @typeparam TMetrics - The set of keys that should be logged.
19
+ * E.g., `keyof Foo` for logging properties `bar` and `baz` from `type Foo = { bar: number, baz: number }`.
20
+ *
21
+ * @sealed
22
+ * @internal
23
+ */
24
+ export class TelemetryEventBatcher<TMetrics extends string> {
25
+ /**
26
+ * Stores the sum of the custom data passed into the logger.
27
+ */
28
+ private dataSums: { [key in TMetrics]?: number } = {};
29
+
30
+ /**
31
+ * Stores the maximum value of the custom data passed into the logger.
32
+ */
33
+ private dataMaxes: { [key in TMetrics]?: number } = {};
34
+
35
+ /**
36
+ * Counter to keep track of the number of times the log function is called.
37
+ */
38
+ private counter = 0;
39
+
40
+ public constructor(
41
+ /**
42
+ * Custom properties to include in the telemetry performance event when it is written.
43
+ */
44
+ private readonly eventBase: ITelemetryGenericEventExt,
45
+
46
+ /**
47
+ * The logger to use to write the telemetry performance event.
48
+ */
49
+ private readonly logger: ITelemetryLoggerExt,
50
+
51
+ /**
52
+ * The number of logs to accumulate before sending the data to the logger.
53
+ */
54
+ private readonly threshold: number,
55
+ ) {}
56
+
57
+ /**
58
+ * Accumulates the custom data and sends it to the logger every {@link TelemetryEventBatcher.threshold} calls.
59
+ *
60
+ * @param customData -
61
+ * A record storing the custom data to be accumulated and eventually logged.
62
+ */
63
+ public accumulateAndLog(customData: Record<TMetrics, number>): void {
64
+ for (const key of Object.keys(customData) as TMetrics[]) {
65
+ this.dataSums[key] = (this.dataSums[key] ?? 0) + customData[key];
66
+ this.dataMaxes[key] = Math.max(
67
+ this.dataMaxes[key] ?? Number.NEGATIVE_INFINITY,
68
+ customData[key],
69
+ );
70
+ }
71
+
72
+ this.counter++;
73
+
74
+ if (this.counter >= this.threshold) {
75
+ this.sendData();
76
+ }
77
+ }
78
+
79
+ private sendData(): void {
80
+ const telemetryEvent: ITelemetryPerformanceEventExt = {
81
+ ...this.eventBase,
82
+ };
83
+
84
+ for (const key of Object.keys(this.dataSums) as TMetrics[]) {
85
+ if (this.dataSums[key] !== undefined) {
86
+ telemetryEvent[`avg_${key}`] = roundToDecimalPlaces(
87
+ this.dataSums[key]! / this.counter,
88
+ 6,
89
+ );
90
+ }
91
+ if (this.dataMaxes[key] !== undefined) {
92
+ telemetryEvent[`max_${key}`] = this.dataMaxes[key];
93
+ }
94
+ }
95
+
96
+ this.logger.sendPerformanceEvent(telemetryEvent);
97
+
98
+ // Reset the counter and the data.
99
+ this.counter = 0;
100
+ this.dataSums = {};
101
+ this.dataMaxes = {};
102
+ }
103
+ }
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryBaseLogger, LogLevel, Tagged } from "@fluidframework/core-interfaces";
6
+ import type { ITelemetryBaseLogger, LogLevel, Tagged } from "@fluidframework/core-interfaces";
7
7
 
8
8
  /**
9
9
  * The categories FF uses when instrumenting the code.
@@ -31,10 +31,7 @@ export type TelemetryEventPropertyTypeExt =
31
31
  | boolean
32
32
  | undefined
33
33
  | (string | number | boolean)[]
34
- | {
35
- [key: string]: // Flat objects can have the same properties as the event itself
36
- string | number | boolean | undefined | (string | number | boolean)[];
37
- };
34
+ | Record<string, string | number | boolean | undefined | (string | number | boolean)[]>;
38
35
 
39
36
  /**
40
37
  * A property to be logged to telemetry containing both the value and a tag. Tags are generic strings that can be used
@@ -53,9 +50,10 @@ export interface ITaggedTelemetryPropertyTypeExt {
53
50
  * JSON-serializable properties, which will be logged with telemetry.
54
51
  * @alpha
55
52
  */
56
- export interface ITelemetryPropertiesExt {
57
- [index: string]: TelemetryEventPropertyTypeExt | Tagged<TelemetryEventPropertyTypeExt>;
58
- }
53
+ export type ITelemetryPropertiesExt = Record<
54
+ string,
55
+ TelemetryEventPropertyTypeExt | Tagged<TelemetryEventPropertyTypeExt>
56
+ >;
59
57
 
60
58
  /**
61
59
  * Interface for logging telemetry statements.
@@ -66,7 +64,14 @@ export interface ITelemetryPropertiesExt {
66
64
  * @internal
67
65
  */
68
66
  export interface ITelemetryEventExt extends ITelemetryPropertiesExt {
67
+ /**
68
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseEvent.category}
69
+ */
69
70
  category: string;
71
+
72
+ /**
73
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseEvent.eventName}
74
+ */
70
75
  eventName: string;
71
76
  }
72
77
 
@@ -76,7 +81,15 @@ export interface ITelemetryEventExt extends ITelemetryPropertiesExt {
76
81
  * @alpha
77
82
  */
78
83
  export interface ITelemetryGenericEventExt extends ITelemetryPropertiesExt {
84
+ /**
85
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseEvent.eventName}
86
+ */
79
87
  eventName: string;
88
+
89
+ /**
90
+ * Optional event {@link @fluidframework/core-interfaces#ITelemetryBaseEvent.category}.
91
+ * @defaultValue "generic"
92
+ */
80
93
  category?: TelemetryEventCategory;
81
94
  }
82
95
 
@@ -86,6 +99,9 @@ export interface ITelemetryGenericEventExt extends ITelemetryPropertiesExt {
86
99
  * @alpha
87
100
  */
88
101
  export interface ITelemetryErrorEventExt extends ITelemetryPropertiesExt {
102
+ /**
103
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseEvent.eventName}
104
+ */
89
105
  eventName: string;
90
106
  }
91
107
 
@@ -95,7 +111,10 @@ export interface ITelemetryErrorEventExt extends ITelemetryPropertiesExt {
95
111
  * @alpha
96
112
  */
97
113
  export interface ITelemetryPerformanceEventExt extends ITelemetryGenericEventExt {
98
- duration?: number; // Duration of event (optional)
114
+ /**
115
+ * Duration of event (optional)
116
+ */
117
+ duration?: number;
99
118
  }
100
119
 
101
120
  /**
@@ -108,10 +127,10 @@ export interface ITelemetryPerformanceEventExt extends ITelemetryGenericEventExt
108
127
  */
109
128
  export interface ITelemetryLoggerExt extends ITelemetryBaseLogger {
110
129
  /**
111
- * Send information telemetry event
112
- * @param event - Event to send
113
- * @param error - optional error object to log
114
- * @param logLevel - optional level of the log.
130
+ * Send an information telemetry event.
131
+ * @param event - Event to send.
132
+ * @param error - Optional error object to log.
133
+ * @param logLevel - Optional level of the log. Default: {@link @fluidframework/core-interfaces#LogLevel.default}.
115
134
  */
116
135
  sendTelemetryEvent(
117
136
  event: ITelemetryGenericEventExt,
@@ -120,17 +139,17 @@ export interface ITelemetryLoggerExt extends ITelemetryBaseLogger {
120
139
  ): void;
121
140
 
122
141
  /**
123
- * Send error telemetry event
124
- * @param event - Event to send
125
- * @param error - optional error object to log
142
+ * Send an error telemetry event.
143
+ * @param event - Event to send.
144
+ * @param error - Optional error object to log.
126
145
  */
127
146
  sendErrorEvent(event: ITelemetryErrorEventExt, error?: unknown): void;
128
147
 
129
148
  /**
130
- * Send performance telemetry event
149
+ * Send a performance telemetry event.
131
150
  * @param event - Event to send
132
- * @param error - optional error object to log
133
- * @param logLevel - optional level of the log.
151
+ * @param error - Optional error object to log.
152
+ * @param logLevel - Optional level of the log. Default: {@link @fluidframework/core-interfaces#LogLevel.default}.
134
153
  */
135
154
  sendPerformanceEvent(
136
155
  event: ITelemetryPerformanceEventExt,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLoggerExt } from "./telemetryTypes.js";
6
+ import type { ITelemetryLoggerExt } from "./telemetryTypes.js";
7
7
 
8
8
  /**
9
9
  * Utility counter which will send event only if the provided value is above a configured threshold.