@fluidframework/telemetry-utils 2.0.0-rc.4.0.6 → 2.0.0-rc.5.0.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.
- package/.eslintrc.cjs +1 -1
- package/CHANGELOG.md +27 -0
- package/api-extractor/api-extractor-lint-bundle.json +5 -0
- package/api-extractor/api-extractor-lint-legacy.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-legacy.esm.json +5 -0
- package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
- package/api-extractor/api-extractor-lint-public.esm.json +5 -0
- package/api-extractor.json +1 -1
- package/api-report/telemetry-utils.alpha.api.md +107 -0
- package/api-report/telemetry-utils.beta.api.md +29 -0
- package/api-report/telemetry-utils.public.api.md +29 -0
- package/biome.jsonc +4 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/error.d.ts +5 -5
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/errorLogging.d.ts +2 -6
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +22 -29
- package/dist/errorLogging.js.map +1 -1
- package/dist/eventEmitterWithErrorHandling.d.ts +2 -2
- package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/fluidErrorBase.d.ts +0 -6
- package/dist/fluidErrorBase.d.ts.map +1 -1
- package/dist/fluidErrorBase.js +1 -12
- package/dist/fluidErrorBase.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +3 -5
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/mathTools.d.ts +13 -0
- package/dist/mathTools.d.ts.map +1 -0
- package/dist/mathTools.js +20 -0
- package/dist/mathTools.js.map +1 -0
- package/dist/mockLogger.d.ts +99 -19
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +120 -33
- package/dist/mockLogger.js.map +1 -1
- package/dist/sampledTelemetryHelper.d.ts +6 -2
- package/dist/sampledTelemetryHelper.d.ts.map +1 -1
- package/dist/sampledTelemetryHelper.js +11 -3
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetryEventBatcher.d.ts +64 -0
- package/dist/telemetryEventBatcher.d.ts.map +1 -0
- package/dist/telemetryEventBatcher.js +86 -0
- package/dist/telemetryEventBatcher.js.map +1 -0
- package/dist/telemetryTypes.d.ts +32 -18
- package/dist/telemetryTypes.d.ts.map +1 -1
- package/dist/telemetryTypes.js.map +1 -1
- package/dist/thresholdCounter.d.ts +1 -1
- package/dist/thresholdCounter.d.ts.map +1 -1
- package/dist/thresholdCounter.js.map +1 -1
- package/dist/utils.d.ts +14 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +30 -2
- package/dist/utils.js.map +1 -1
- package/lib/config.d.ts +2 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/error.d.ts +5 -5
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/errorLogging.d.ts +2 -6
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +23 -30
- package/lib/errorLogging.js.map +1 -1
- package/lib/eventEmitterWithErrorHandling.d.ts +2 -2
- package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/events.js.map +1 -1
- package/lib/fluidErrorBase.d.ts +0 -6
- package/lib/fluidErrorBase.d.ts.map +1 -1
- package/lib/fluidErrorBase.js +0 -10
- package/lib/fluidErrorBase.js.map +1 -1
- package/lib/index.d.ts +8 -7
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -3
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts +3 -5
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js.map +1 -1
- package/lib/mathTools.d.ts +13 -0
- package/lib/mathTools.d.ts.map +1 -0
- package/lib/mathTools.js +16 -0
- package/lib/mathTools.js.map +1 -0
- package/lib/mockLogger.d.ts +99 -19
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +118 -32
- package/lib/mockLogger.js.map +1 -1
- package/lib/sampledTelemetryHelper.d.ts +6 -2
- package/lib/sampledTelemetryHelper.d.ts.map +1 -1
- package/lib/sampledTelemetryHelper.js +11 -3
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetryEventBatcher.d.ts +64 -0
- package/lib/telemetryEventBatcher.d.ts.map +1 -0
- package/lib/telemetryEventBatcher.js +82 -0
- package/lib/telemetryEventBatcher.js.map +1 -0
- package/lib/telemetryTypes.d.ts +32 -18
- package/lib/telemetryTypes.d.ts.map +1 -1
- package/lib/telemetryTypes.js.map +1 -1
- package/lib/thresholdCounter.d.ts +1 -1
- package/lib/thresholdCounter.d.ts.map +1 -1
- package/lib/thresholdCounter.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/utils.d.ts +14 -2
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +28 -1
- package/lib/utils.js.map +1 -1
- package/package.json +30 -16
- package/src/config.ts +16 -16
- package/src/error.ts +13 -13
- package/src/errorLogging.ts +32 -46
- package/src/eventEmitterWithErrorHandling.ts +3 -3
- package/src/fluidErrorBase.ts +0 -15
- package/src/index.ts +17 -16
- package/src/logger.ts +21 -21
- package/src/mathTools.ts +16 -0
- package/src/mockLogger.ts +165 -35
- package/src/sampledTelemetryHelper.ts +15 -5
- package/src/telemetryEventBatcher.ts +103 -0
- package/src/telemetryTypes.ts +38 -19
- package/src/thresholdCounter.ts +1 -1
- package/src/utils.ts +31 -2
- package/tsconfig.json +2 -0
- package/tsdoc.json +4 -0
- 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 {
|
|
14
|
+
import type {
|
|
15
|
+
ITelemetryEventExt,
|
|
16
|
+
ITelemetryLoggerExt,
|
|
17
|
+
ITelemetryPropertiesExt,
|
|
18
|
+
} from "./telemetryTypes.js";
|
|
15
19
|
|
|
16
20
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
/**
|
|
49
|
+
* {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.minLogLevel}
|
|
50
|
+
*/
|
|
51
|
+
public readonly minLogLevel: LogLevel;
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
this.
|
|
53
|
+
public constructor(minLogLevel?: LogLevel) {
|
|
54
|
+
this.minLogLevel = minLogLevel ?? LogLevel.default;
|
|
29
55
|
}
|
|
30
56
|
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
9
|
+
import type {
|
|
10
|
+
ITelemetryGenericEventExt,
|
|
11
11
|
ITelemetryLoggerExt,
|
|
12
|
-
|
|
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
|
-
|
|
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())
|
|
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
|
+
}
|
package/src/telemetryTypes.ts
CHANGED
|
@@ -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
|
|
57
|
-
|
|
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
|
-
|
|
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 -
|
|
114
|
-
* @param logLevel -
|
|
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 -
|
|
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 -
|
|
133
|
-
* @param logLevel -
|
|
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,
|
package/src/thresholdCounter.ts
CHANGED
|
@@ -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.
|