@fluidframework/telemetry-utils 2.0.0-dev-rc.5.0.0.271262 → 2.0.0-dev-rc.5.0.0.272251
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/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-report/telemetry-utils.alpha.api.md +13 -28
- package/api-report/telemetry-utils.beta.api.md +7 -7
- package/api-report/telemetry-utils.public.api.md +7 -7
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -3
- 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 +9 -10
- package/dist/error.js.map +1 -1
- package/dist/errorLogging.d.ts +1 -1
- package/dist/errorLogging.d.ts.map +1 -1
- package/dist/errorLogging.js +27 -15
- 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 +0 -1
- package/dist/eventEmitterWithErrorHandling.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- 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 +7 -19
- 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 +15 -7
- package/dist/mockLogger.d.ts.map +1 -1
- package/dist/mockLogger.js +17 -11
- 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 -8
- package/dist/sampledTelemetryHelper.js.map +1 -1
- package/dist/telemetryEventBatcher.d.ts +87 -0
- package/dist/telemetryEventBatcher.d.ts.map +1 -0
- package/dist/telemetryEventBatcher.js +109 -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 +0 -3
- package/dist/thresholdCounter.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/lib/config.d.ts +2 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +1 -3
- 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 +9 -10
- package/lib/error.js.map +1 -1
- package/lib/errorLogging.d.ts +1 -1
- package/lib/errorLogging.d.ts.map +1 -1
- package/lib/errorLogging.js +27 -15
- 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 +0 -1
- package/lib/eventEmitterWithErrorHandling.js.map +1 -1
- package/lib/index.d.ts +7 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/logger.d.ts +3 -5
- package/lib/logger.d.ts.map +1 -1
- package/lib/logger.js +7 -19
- 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 +15 -7
- package/lib/mockLogger.d.ts.map +1 -1
- package/lib/mockLogger.js +17 -11
- 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 -8
- package/lib/sampledTelemetryHelper.js.map +1 -1
- package/lib/telemetryEventBatcher.d.ts +87 -0
- package/lib/telemetryEventBatcher.d.ts.map +1 -0
- package/lib/telemetryEventBatcher.js +105 -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 +0 -3
- package/lib/thresholdCounter.js.map +1 -1
- package/lib/utils.d.ts +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +16 -7
- package/src/config.ts +10 -10
- package/src/error.ts +13 -13
- package/src/errorLogging.ts +27 -14
- package/src/eventEmitterWithErrorHandling.ts +3 -3
- package/src/index.ts +12 -11
- package/src/logger.ts +11 -10
- package/src/mathTools.ts +16 -0
- package/src/mockLogger.ts +34 -20
- package/src/sampledTelemetryHelper.ts +15 -5
- package/src/telemetryEventBatcher.ts +144 -0
- package/src/telemetryTypes.ts +38 -19
- package/src/thresholdCounter.ts +1 -1
- package/src/utils.ts +2 -2
- package/tsdoc.json +4 -0
package/src/error.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IErrorBase, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
|
|
6
|
+
import type { IErrorBase, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
|
|
7
7
|
import {
|
|
8
8
|
FluidErrorTypes,
|
|
9
|
-
IGenericError,
|
|
10
|
-
IUsageError,
|
|
9
|
+
type IGenericError,
|
|
10
|
+
type IUsageError,
|
|
11
11
|
} from "@fluidframework/core-interfaces/internal";
|
|
12
|
-
import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
|
|
12
|
+
import type { ISequencedDocumentMessage } from "@fluidframework/driver-definitions/internal";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
LoggingError,
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
normalizeError,
|
|
19
19
|
wrapError,
|
|
20
20
|
} from "./errorLogging.js";
|
|
21
|
-
import { IFluidErrorBase } from "./fluidErrorBase.js";
|
|
22
|
-
import { ITelemetryPropertiesExt } from "./telemetryTypes.js";
|
|
21
|
+
import type { IFluidErrorBase } from "./fluidErrorBase.js";
|
|
22
|
+
import type { ITelemetryPropertiesExt } from "./telemetryTypes.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Throws a UsageError with the given message if the condition is not met.
|
|
@@ -46,7 +46,7 @@ export function validatePrecondition(
|
|
|
46
46
|
* @internal
|
|
47
47
|
*/
|
|
48
48
|
export class GenericError extends LoggingError implements IGenericError, IFluidErrorBase {
|
|
49
|
-
readonly errorType = FluidErrorTypes.genericError;
|
|
49
|
+
public readonly errorType = FluidErrorTypes.genericError;
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Create a new GenericError
|
|
@@ -54,7 +54,7 @@ export class GenericError extends LoggingError implements IGenericError, IFluidE
|
|
|
54
54
|
* @param error - inner error object
|
|
55
55
|
* @param props - Telemetry props to include when the error is logged
|
|
56
56
|
*/
|
|
57
|
-
constructor(
|
|
57
|
+
public constructor(
|
|
58
58
|
message: string,
|
|
59
59
|
// TODO: Use `unknown` instead (API breaking change because error is not just an input parameter, but a public member of the class)
|
|
60
60
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
@@ -72,9 +72,9 @@ export class GenericError extends LoggingError implements IGenericError, IFluidE
|
|
|
72
72
|
* @internal
|
|
73
73
|
*/
|
|
74
74
|
export class UsageError extends LoggingError implements IUsageError, IFluidErrorBase {
|
|
75
|
-
readonly errorType = FluidErrorTypes.usageError;
|
|
75
|
+
public readonly errorType = FluidErrorTypes.usageError;
|
|
76
76
|
|
|
77
|
-
constructor(message: string, props?: ITelemetryBaseProperties) {
|
|
77
|
+
public constructor(message: string, props?: ITelemetryBaseProperties) {
|
|
78
78
|
super(message, { ...props, usageError: true });
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -86,10 +86,10 @@ export class UsageError extends LoggingError implements IUsageError, IFluidError
|
|
|
86
86
|
* @internal
|
|
87
87
|
*/
|
|
88
88
|
export class DataCorruptionError extends LoggingError implements IErrorBase, IFluidErrorBase {
|
|
89
|
-
readonly errorType = FluidErrorTypes.dataCorruptionError;
|
|
90
|
-
readonly canRetry = false;
|
|
89
|
+
public readonly errorType = FluidErrorTypes.dataCorruptionError;
|
|
90
|
+
public readonly canRetry = false;
|
|
91
91
|
|
|
92
|
-
constructor(message: string, props: ITelemetryBaseProperties) {
|
|
92
|
+
public constructor(message: string, props: ITelemetryBaseProperties) {
|
|
93
93
|
super(message, { ...props, dataProcessingError: 1 });
|
|
94
94
|
}
|
|
95
95
|
}
|
package/src/errorLogging.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ITelemetryBaseProperties, Tagged } from "@fluidframework/core-inte
|
|
|
7
7
|
import type { ILoggingError } from "@fluidframework/core-interfaces/internal";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
9
|
|
|
10
|
-
import { IFluidErrorBase, hasErrorInstanceId, isFluidError } from "./fluidErrorBase.js";
|
|
10
|
+
import { type IFluidErrorBase, hasErrorInstanceId, isFluidError } from "./fluidErrorBase.js";
|
|
11
11
|
import { convertToBasePropertyType } from "./logger.js";
|
|
12
12
|
import type {
|
|
13
13
|
ITelemetryLoggerExt,
|
|
@@ -157,7 +157,12 @@ export function normalizeError(
|
|
|
157
157
|
|
|
158
158
|
const errorTelemetryProps = LoggingError.typeCheck(error)
|
|
159
159
|
? error.getTelemetryProperties()
|
|
160
|
-
: {
|
|
160
|
+
: {
|
|
161
|
+
untrustedOrigin: 1, // This will let us filter errors that did not originate from our own codebase
|
|
162
|
+
// FUTURE: Once 2.0 becomes LTS, switch to this more explicit property name
|
|
163
|
+
// Consider using a string to distinguish cases like "dependency" v. "callback"
|
|
164
|
+
// errorRunningExternalCode: 1,
|
|
165
|
+
};
|
|
161
166
|
|
|
162
167
|
fluidError.addTelemetryProperties({
|
|
163
168
|
...errorTelemetryProps,
|
|
@@ -231,7 +236,12 @@ export function wrapError<T extends LoggingError>(
|
|
|
231
236
|
|
|
232
237
|
// Mark external errors with untrustedOrigin flag
|
|
233
238
|
if (isExternalError(innerError)) {
|
|
234
|
-
newError.addTelemetryProperties({
|
|
239
|
+
newError.addTelemetryProperties({
|
|
240
|
+
untrustedOrigin: 1,
|
|
241
|
+
// FUTURE: Once 2.0 becomes LTS, switch to this more explicit property name
|
|
242
|
+
// Consider using a string to distinguish cases like "dependency" v. "callback"
|
|
243
|
+
// errorRunningExternalCode: 1,
|
|
244
|
+
});
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
// Reuse errorInstanceId
|
|
@@ -243,7 +253,7 @@ export function wrapError<T extends LoggingError>(
|
|
|
243
253
|
}
|
|
244
254
|
|
|
245
255
|
// Lastly, copy over all other telemetry properties. Note these will not overwrite existing properties
|
|
246
|
-
// This will include the untrustedOrigin
|
|
256
|
+
// This will include the untrustedOrigin/errorRunningExternalCode info if the inner error itself was created from an external error
|
|
247
257
|
if (isILoggingError(innerError)) {
|
|
248
258
|
newError.addTelemetryProperties(innerError.getTelemetryProperties());
|
|
249
259
|
}
|
|
@@ -308,11 +318,14 @@ export function overwriteStack(error: IFluidErrorBase | LoggingError, stack: str
|
|
|
308
318
|
*/
|
|
309
319
|
export function isExternalError(error: unknown): boolean {
|
|
310
320
|
// LoggingErrors are an internal FF error type. However, an external error can be converted
|
|
311
|
-
// into a LoggingError if it is normalized. In this case we must use the untrustedOrigin flag to
|
|
312
|
-
// determine whether the original error was
|
|
321
|
+
// into a LoggingError if it is normalized. In this case we must use the untrustedOrigin/errorRunningExternalCode flag to
|
|
322
|
+
// determine whether the original error was in fact external.
|
|
313
323
|
if (LoggingError.typeCheck(error)) {
|
|
314
324
|
if ((error as NormalizedLoggingError).errorType === NORMALIZED_ERROR_TYPE) {
|
|
315
|
-
|
|
325
|
+
const props = error.getTelemetryProperties();
|
|
326
|
+
// NOTE: errorRunningExternalCode is not currently used - once this "read" code reaches LTS,
|
|
327
|
+
// we can switch to writing this more explicit property
|
|
328
|
+
return props.untrustedOrigin === 1 || !!props.errorRunningExternalCode;
|
|
316
329
|
}
|
|
317
330
|
return false;
|
|
318
331
|
}
|
|
@@ -331,6 +344,8 @@ export function isTaggedTelemetryPropertyValue(
|
|
|
331
344
|
return typeof (x as Partial<Tagged<unknown>>)?.tag === "string";
|
|
332
345
|
}
|
|
333
346
|
|
|
347
|
+
// TODO: Use `unknown` instead (API breaking change)
|
|
348
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
334
349
|
/**
|
|
335
350
|
* Borrowed from
|
|
336
351
|
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples}
|
|
@@ -340,8 +355,6 @@ export function isTaggedTelemetryPropertyValue(
|
|
|
340
355
|
*
|
|
341
356
|
* @internal
|
|
342
357
|
*/
|
|
343
|
-
// TODO: Use `unknown` instead (API breaking change)
|
|
344
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
345
358
|
export const getCircularReplacer = (): ((key: string, value: unknown) => any) => {
|
|
346
359
|
const seen = new WeakSet();
|
|
347
360
|
return (key: string, value: unknown): any => {
|
|
@@ -370,10 +383,10 @@ export class LoggingError
|
|
|
370
383
|
implements ILoggingError, Omit<IFluidErrorBase, "errorType">
|
|
371
384
|
{
|
|
372
385
|
private _errorInstanceId = uuid();
|
|
373
|
-
get errorInstanceId(): string {
|
|
386
|
+
public get errorInstanceId(): string {
|
|
374
387
|
return this._errorInstanceId;
|
|
375
388
|
}
|
|
376
|
-
overwriteErrorInstanceId(id: string): void {
|
|
389
|
+
public overwriteErrorInstanceId(id: string): void {
|
|
377
390
|
this._errorInstanceId = id;
|
|
378
391
|
}
|
|
379
392
|
|
|
@@ -383,7 +396,7 @@ export class LoggingError
|
|
|
383
396
|
* @param props - telemetry props to include on the error for when it's logged
|
|
384
397
|
* @param omitPropsFromLogging - properties by name to omit from telemetry props
|
|
385
398
|
*/
|
|
386
|
-
constructor(
|
|
399
|
+
public constructor(
|
|
387
400
|
message: string,
|
|
388
401
|
props?: ITelemetryBaseProperties,
|
|
389
402
|
private readonly omitPropsFromLogging: Set<string> = new Set(),
|
|
@@ -464,9 +477,9 @@ export const NORMALIZED_ERROR_TYPE = "genericError";
|
|
|
464
477
|
class NormalizedLoggingError extends LoggingError {
|
|
465
478
|
// errorType "genericError" is used as a default value throughout the code.
|
|
466
479
|
// Note that this matches ContainerErrorTypes/DriverErrorTypes' genericError
|
|
467
|
-
errorType = NORMALIZED_ERROR_TYPE;
|
|
480
|
+
public readonly errorType = NORMALIZED_ERROR_TYPE;
|
|
468
481
|
|
|
469
|
-
constructor(errorProps: Pick<IFluidErrorBase, "message" | "stack">) {
|
|
482
|
+
public constructor(errorProps: Pick<IFluidErrorBase, "message" | "stack">) {
|
|
470
483
|
super(errorProps.message);
|
|
471
484
|
|
|
472
485
|
if (errorProps.stack !== undefined) {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { EventEmitterEventType, TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
7
|
-
import { IEvent } from "@fluidframework/core-interfaces";
|
|
6
|
+
import { type EventEmitterEventType, TypedEventEmitter } from "@fluid-internal/client-utils";
|
|
7
|
+
import type { IEvent } from "@fluidframework/core-interfaces";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Event Emitter helper class
|
|
@@ -20,7 +20,7 @@ import { IEvent } from "@fluidframework/core-interfaces";
|
|
|
20
20
|
export class EventEmitterWithErrorHandling<
|
|
21
21
|
TEvent extends IEvent = IEvent,
|
|
22
22
|
> extends TypedEventEmitter<TEvent> {
|
|
23
|
-
constructor(
|
|
23
|
+
public constructor(
|
|
24
24
|
// TODO: use `unknown` instead (breaking API change)
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
26
|
private readonly errorHandler: (eventName: EventEmitterEventType, error: any) => void,
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
export {
|
|
7
7
|
createChildMonitoringContext,
|
|
8
|
-
MonitoringContext,
|
|
8
|
+
type MonitoringContext,
|
|
9
9
|
sessionStorageConfigProvider,
|
|
10
10
|
mixinMonitoringContext,
|
|
11
|
-
IConfigProvider,
|
|
11
|
+
type IConfigProvider,
|
|
12
12
|
loggerToMonitoringContext,
|
|
13
13
|
wrapConfigProviderWithDefaults,
|
|
14
14
|
} from "./config.js";
|
|
@@ -25,7 +25,7 @@ export {
|
|
|
25
25
|
generateErrorWithStack,
|
|
26
26
|
generateStack,
|
|
27
27
|
getCircularReplacer,
|
|
28
|
-
IFluidErrorAnnotations,
|
|
28
|
+
type IFluidErrorAnnotations,
|
|
29
29
|
isExternalError,
|
|
30
30
|
isILoggingError,
|
|
31
31
|
isTaggedTelemetryPropertyValue,
|
|
@@ -43,29 +43,29 @@ export {
|
|
|
43
43
|
raiseConnectedEvent,
|
|
44
44
|
safeRaiseEvent,
|
|
45
45
|
} from "./events.js";
|
|
46
|
-
export { hasErrorInstanceId, IFluidErrorBase, isFluidError } from "./fluidErrorBase.js";
|
|
46
|
+
export { hasErrorInstanceId, type IFluidErrorBase, isFluidError } from "./fluidErrorBase.js";
|
|
47
47
|
export {
|
|
48
48
|
eventNamespaceSeparator,
|
|
49
49
|
createChildLogger,
|
|
50
50
|
createMultiSinkLogger,
|
|
51
51
|
formatTick,
|
|
52
|
-
IPerformanceEventMarkers,
|
|
53
|
-
ITelemetryLoggerPropertyBag,
|
|
54
|
-
ITelemetryLoggerPropertyBags,
|
|
55
|
-
MultiSinkLoggerProperties,
|
|
52
|
+
type IPerformanceEventMarkers,
|
|
53
|
+
type ITelemetryLoggerPropertyBag,
|
|
54
|
+
type ITelemetryLoggerPropertyBags,
|
|
55
|
+
type MultiSinkLoggerProperties,
|
|
56
56
|
numberFromString,
|
|
57
57
|
PerformanceEvent,
|
|
58
58
|
TaggedLoggerAdapter,
|
|
59
59
|
tagData,
|
|
60
60
|
tagCodeArtifacts,
|
|
61
61
|
TelemetryDataTag,
|
|
62
|
-
TelemetryEventPropertyTypes,
|
|
62
|
+
type TelemetryEventPropertyTypes,
|
|
63
63
|
} from "./logger.js";
|
|
64
64
|
export { MockLogger } from "./mockLogger.js";
|
|
65
65
|
export { ThresholdCounter } from "./thresholdCounter.js";
|
|
66
66
|
export { SampledTelemetryHelper } from "./sampledTelemetryHelper.js";
|
|
67
|
-
export { createSampledLogger, IEventSampler, ISampledTelemetryLogger } from "./utils.js";
|
|
68
|
-
export {
|
|
67
|
+
export { createSampledLogger, type IEventSampler, type ISampledTelemetryLogger } from "./utils.js";
|
|
68
|
+
export type {
|
|
69
69
|
TelemetryEventPropertyTypeExt,
|
|
70
70
|
ITelemetryEventExt,
|
|
71
71
|
ITelemetryGenericEventExt,
|
|
@@ -76,3 +76,4 @@ export {
|
|
|
76
76
|
ITelemetryPropertiesExt,
|
|
77
77
|
TelemetryEventCategory,
|
|
78
78
|
} from "./telemetryTypes.js";
|
|
79
|
+
export { type IMeasuredCodeResult, TelemetryEventBatcher } from "./telemetryEventBatcher.js";
|
package/src/logger.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { performance } from "@fluid-internal/client-utils";
|
|
7
7
|
import {
|
|
8
|
-
ITelemetryBaseEvent,
|
|
9
|
-
ITelemetryBaseLogger,
|
|
8
|
+
type ITelemetryBaseEvent,
|
|
9
|
+
type ITelemetryBaseLogger,
|
|
10
10
|
LogLevel,
|
|
11
|
-
Tagged,
|
|
12
|
-
TelemetryBaseEventPropertyType,
|
|
11
|
+
type Tagged,
|
|
12
|
+
type TelemetryBaseEventPropertyType,
|
|
13
13
|
} from "@fluidframework/core-interfaces";
|
|
14
14
|
|
|
15
15
|
import {
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
isILoggingError,
|
|
24
24
|
isTaggedTelemetryPropertyValue,
|
|
25
25
|
} from "./errorLogging.js";
|
|
26
|
-
import {
|
|
27
|
-
|
|
26
|
+
import type {
|
|
27
|
+
ITelemetryErrorEventExt,
|
|
28
28
|
ITelemetryEventExt,
|
|
29
29
|
ITelemetryGenericEventExt,
|
|
30
30
|
ITelemetryLoggerExt,
|
|
@@ -60,9 +60,10 @@ export type TelemetryEventPropertyTypes = ITelemetryPropertiesExt[string];
|
|
|
60
60
|
/**
|
|
61
61
|
* @alpha
|
|
62
62
|
*/
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
export type ITelemetryLoggerPropertyBag = Record<
|
|
64
|
+
string,
|
|
65
|
+
TelemetryEventPropertyTypes | (() => TelemetryEventPropertyTypes)
|
|
66
|
+
>;
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* @alpha
|
|
@@ -533,7 +534,7 @@ export class MultiSinkLogger extends TelemetryLogger {
|
|
|
533
534
|
* @param loggers - The list of loggers to use as sinks
|
|
534
535
|
* @param tryInheritProperties - Will attempted to copy those loggers properties to this loggers if they are of a known type e.g. one from this package
|
|
535
536
|
*/
|
|
536
|
-
constructor(
|
|
537
|
+
public constructor(
|
|
537
538
|
namespace?: string,
|
|
538
539
|
properties?: ITelemetryLoggerPropertyBags,
|
|
539
540
|
loggers: ITelemetryBaseLogger[] = [],
|
package/src/mathTools.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Function to round a number to a specified number of decimal places.
|
|
8
|
+
*
|
|
9
|
+
* @param number - The number to round.
|
|
10
|
+
* @param decimalPlaces - The number of decimal places to round to.
|
|
11
|
+
* @returns The rounded number.
|
|
12
|
+
*/
|
|
13
|
+
export function roundToDecimalPlaces(number: number, decimalPlaces: number): number {
|
|
14
|
+
const factor = Math.pow(10, decimalPlaces);
|
|
15
|
+
return Math.round(number * factor) / factor;
|
|
16
|
+
}
|
package/src/mockLogger.ts
CHANGED
|
@@ -4,36 +4,51 @@
|
|
|
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 { ITelemetryLoggerExt, ITelemetryPropertiesExt } from "./telemetryTypes.js";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Mock {@link @fluidframework/core-interfaces#ITelemetryBaseLogger} implementation.
|
|
18
|
+
*
|
|
19
|
+
* Records events sent to it, and then can walk back over those events, searching for a set of expected events to
|
|
20
|
+
* match against the logged events.
|
|
19
21
|
*
|
|
20
22
|
* @alpha
|
|
21
23
|
*/
|
|
22
24
|
export class MockLogger implements ITelemetryBaseLogger {
|
|
23
|
-
|
|
25
|
+
// TODO: don't expose mutability to external consumers
|
|
26
|
+
public events: ITelemetryBaseEvent[] = [];
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
/**
|
|
29
|
+
* {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.minLogLevel}
|
|
30
|
+
*/
|
|
31
|
+
public readonly minLogLevel: LogLevel;
|
|
32
|
+
|
|
33
|
+
public constructor(minLogLevel?: LogLevel) {
|
|
34
|
+
this.minLogLevel = minLogLevel ?? LogLevel.default;
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
clear(): void {
|
|
37
|
+
public clear(): void {
|
|
28
38
|
this.events = [];
|
|
29
39
|
}
|
|
30
40
|
|
|
31
|
-
toTelemetryLogger(): ITelemetryLoggerExt {
|
|
41
|
+
public toTelemetryLogger(): ITelemetryLoggerExt {
|
|
32
42
|
return createChildLogger({ logger: this });
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
/**
|
|
46
|
+
* {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}
|
|
47
|
+
*/
|
|
48
|
+
public send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
|
|
49
|
+
if (logLevel ?? LogLevel.default >= this.minLogLevel) {
|
|
50
|
+
this.events.push(event);
|
|
51
|
+
}
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
/**
|
|
@@ -44,7 +59,7 @@ export class MockLogger implements ITelemetryBaseLogger {
|
|
|
44
59
|
* These event objects may be subsets of the logged events.
|
|
45
60
|
* Note: category is omitted from the type because it's usually uninteresting and tedious to type.
|
|
46
61
|
*/
|
|
47
|
-
matchEvents(
|
|
62
|
+
public matchEvents(
|
|
48
63
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
49
64
|
inlineDetailsProp: boolean = false,
|
|
50
65
|
): boolean {
|
|
@@ -60,7 +75,7 @@ export class MockLogger implements ITelemetryBaseLogger {
|
|
|
60
75
|
/**
|
|
61
76
|
* Asserts that matchEvents is true, and prints the actual/expected output if not.
|
|
62
77
|
*/
|
|
63
|
-
assertMatch(
|
|
78
|
+
public assertMatch(
|
|
64
79
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
65
80
|
message?: string,
|
|
66
81
|
inlineDetailsProp: boolean = false,
|
|
@@ -85,7 +100,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
85
100
|
* Note: category is omitted from the type because it's usually uninteresting and tedious to type.
|
|
86
101
|
* @returns if any of the expected events is found.
|
|
87
102
|
*/
|
|
88
|
-
matchAnyEvent(
|
|
103
|
+
public matchAnyEvent(
|
|
89
104
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
90
105
|
inlineDetailsProp: boolean = false,
|
|
91
106
|
): boolean {
|
|
@@ -99,7 +114,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
99
114
|
/**
|
|
100
115
|
* Asserts that matchAnyEvent is true, and prints the actual/expected output if not.
|
|
101
116
|
*/
|
|
102
|
-
assertMatchAny(
|
|
117
|
+
public assertMatchAny(
|
|
103
118
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
104
119
|
message?: string,
|
|
105
120
|
inlineDetailsProp: boolean = false,
|
|
@@ -123,7 +138,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
123
138
|
* These event objects may be subsets of the logged events.
|
|
124
139
|
* Note: category is omitted from the type because it's usually uninteresting and tedious to type.
|
|
125
140
|
*/
|
|
126
|
-
matchEventStrict(
|
|
141
|
+
public matchEventStrict(
|
|
127
142
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
128
143
|
inlineDetailsProp: boolean = false,
|
|
129
144
|
): boolean {
|
|
@@ -136,7 +151,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
136
151
|
/**
|
|
137
152
|
* Asserts that matchEvents is true, and prints the actual/expected output if not
|
|
138
153
|
*/
|
|
139
|
-
assertMatchStrict(
|
|
154
|
+
public assertMatchStrict(
|
|
140
155
|
expectedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
141
156
|
message?: string,
|
|
142
157
|
inlineDetailsProp: boolean = false,
|
|
@@ -155,7 +170,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
155
170
|
/**
|
|
156
171
|
* Asserts that matchAnyEvent is false for the given events, and prints the actual/expected output if not
|
|
157
172
|
*/
|
|
158
|
-
assertMatchNone(
|
|
173
|
+
public assertMatchNone(
|
|
159
174
|
disallowedEvents: Omit<ITelemetryBaseEvent, "category">[],
|
|
160
175
|
message?: string,
|
|
161
176
|
inlineDetailsProp: boolean = false,
|
|
@@ -187,7 +202,7 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
// Remove the events so far; next call will just compare subsequent events from here
|
|
190
|
-
this.
|
|
205
|
+
this.clear();
|
|
191
206
|
|
|
192
207
|
// Return the count of matched events.
|
|
193
208
|
return iExpectedEvent;
|
|
@@ -208,7 +223,6 @@ ${JSON.stringify(actualEvents)}`);
|
|
|
208
223
|
if (inlineDetailsProp && details !== undefined) {
|
|
209
224
|
assert(
|
|
210
225
|
typeof details === "string",
|
|
211
|
-
// eslint-disable-next-line unicorn/numeric-separators-style
|
|
212
226
|
0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,
|
|
213
227
|
);
|
|
214
228
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
@@ -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,144 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { performance } from "@fluid-internal/client-utils";
|
|
7
|
+
|
|
8
|
+
import { roundToDecimalPlaces } from "./mathTools.js";
|
|
9
|
+
import type {
|
|
10
|
+
ITelemetryGenericEventExt,
|
|
11
|
+
ITelemetryLoggerExt,
|
|
12
|
+
ITelemetryPerformanceEventExt,
|
|
13
|
+
} from "./telemetryTypes.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Expected type of the custom data passed into the logger.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export interface IMeasuredCodeResult<TKey extends string> {
|
|
20
|
+
/**
|
|
21
|
+
* Optional properties to log custom data. The set of properties must be the same for all calls to the `measure` function.
|
|
22
|
+
*/
|
|
23
|
+
telemetryProperties?: { readonly [key in TKey]: number };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Telemetry class that measures the execution time of a given piece of code and accumulates user defined telemetry metrics, to finally log an event through the {@link TelemetryEventBatcher.logger | logger} provided to this class when the number of calls to the {@link TelemetryEventBatcher.measure | measure} function reaches the specified by {@link TelemetryEventBatcher.threshold | threshold}.
|
|
28
|
+
*
|
|
29
|
+
* @remarks It is expected to be used for a single event type. If the set of `telemetryProperties` is different for different events, a separate `TelemetryEventBatcher` should be created for each event type.
|
|
30
|
+
* @typeparam TMetrics - The set of keys that should be logged.
|
|
31
|
+
* E.g., `keyof Foo` for logging properties `bar` and `baz` from `type Foo = { bar: number, baz: number }`.
|
|
32
|
+
*
|
|
33
|
+
* @sealed
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export class TelemetryEventBatcher<TMetrics extends string> {
|
|
37
|
+
/**
|
|
38
|
+
* Stores the accumulated duration of the code passed into the logger.
|
|
39
|
+
*/
|
|
40
|
+
private accumulatedDuration: number = 0;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Stores the sum of the custom data passed into the logger.
|
|
44
|
+
*/
|
|
45
|
+
private dataSums: { [key in TMetrics]?: number } = {};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stores the maximum value of the custom data passed into the logger.
|
|
49
|
+
*/
|
|
50
|
+
private dataMaxes: { [key in TMetrics]?: number } = {};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Counter to keep track of the number of times the log function is called.
|
|
54
|
+
*/
|
|
55
|
+
private counter = 0;
|
|
56
|
+
|
|
57
|
+
public constructor(
|
|
58
|
+
/**
|
|
59
|
+
* Custom properties to include in the telemetry performance event when it is written.
|
|
60
|
+
*/
|
|
61
|
+
private readonly eventBase: ITelemetryGenericEventExt,
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The logger to use to write the telemetry performance event.
|
|
65
|
+
*/
|
|
66
|
+
private readonly logger: ITelemetryLoggerExt,
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The number of logs to accumulate before sending the data to the logger.
|
|
70
|
+
*/
|
|
71
|
+
private readonly threshold: number,
|
|
72
|
+
) {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Executes the specified code, keeping statistics of its execution time and the telemetry properties it returns, and when the {@link TelemetryEventBatcher.threshold} is reached it logs a performance event which includes the maxes and averages.
|
|
76
|
+
* @param codeToMeasure - The code to be executed and measured.
|
|
77
|
+
* @param customData - Custom data to be logged.
|
|
78
|
+
*
|
|
79
|
+
* @returns Whatever the passed-in code block returns.
|
|
80
|
+
*/
|
|
81
|
+
public measure<T extends IMeasuredCodeResult<TMetrics>>(codeToMeasure: () => T): T {
|
|
82
|
+
const start = performance.now();
|
|
83
|
+
const returnValue = codeToMeasure();
|
|
84
|
+
const duration = performance.now() - start;
|
|
85
|
+
|
|
86
|
+
this.accumulatedDuration += duration;
|
|
87
|
+
|
|
88
|
+
if (returnValue.telemetryProperties) {
|
|
89
|
+
this.accumulateAndLog(returnValue.telemetryProperties);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return returnValue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Accumulates the custom data and sends it to the logger every {@link TelemetryEventBatcher.threshold} calls.
|
|
97
|
+
*
|
|
98
|
+
* @param customData -
|
|
99
|
+
* A record storing the custom data to be accumulated and eventually logged.
|
|
100
|
+
*/
|
|
101
|
+
private accumulateAndLog(customData: Record<TMetrics, number>): void {
|
|
102
|
+
for (const key of Object.keys(customData) as TMetrics[]) {
|
|
103
|
+
this.dataSums[key] = (this.dataSums[key] ?? 0) + customData[key];
|
|
104
|
+
this.dataMaxes[key] = Math.max(
|
|
105
|
+
this.dataMaxes[key] ?? Number.NEGATIVE_INFINITY,
|
|
106
|
+
customData[key],
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.counter++;
|
|
111
|
+
|
|
112
|
+
if (this.counter >= this.threshold) {
|
|
113
|
+
this.sendData();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private sendData(): void {
|
|
118
|
+
const telemetryEvent: ITelemetryPerformanceEventExt = {
|
|
119
|
+
...this.eventBase,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
telemetryEvent.duration = this.accumulatedDuration /= this.counter;
|
|
123
|
+
|
|
124
|
+
for (const key of Object.keys(this.dataSums) as TMetrics[]) {
|
|
125
|
+
if (this.dataSums[key] !== undefined) {
|
|
126
|
+
telemetryEvent[`avg${key}`] = roundToDecimalPlaces(
|
|
127
|
+
this.dataSums[key]! / this.counter,
|
|
128
|
+
6,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (this.dataMaxes[key] !== undefined) {
|
|
132
|
+
telemetryEvent[`max${key}`] = this.dataMaxes[key];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.logger.sendPerformanceEvent(telemetryEvent);
|
|
137
|
+
|
|
138
|
+
// Reset the counter and the data.
|
|
139
|
+
this.counter = 0;
|
|
140
|
+
this.accumulatedDuration = 0;
|
|
141
|
+
this.dataSums = {};
|
|
142
|
+
this.dataMaxes = {};
|
|
143
|
+
}
|
|
144
|
+
}
|