@fluidframework/telemetry-utils 2.0.0-internal.5.3.4 → 2.0.0-internal.5.4.2

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 (45) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/config.d.ts +2 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +9 -10
  5. package/dist/config.js.map +1 -1
  6. package/dist/debugLogger.d.ts +3 -3
  7. package/dist/debugLogger.d.ts.map +1 -1
  8. package/dist/debugLogger.js +6 -13
  9. package/dist/debugLogger.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +9 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/logger.d.ts +56 -2
  15. package/dist/logger.d.ts.map +1 -1
  16. package/dist/logger.js +90 -9
  17. package/dist/logger.js.map +1 -1
  18. package/dist/mockLogger.d.ts.map +1 -1
  19. package/dist/mockLogger.js +22 -5
  20. package/dist/mockLogger.js.map +1 -1
  21. package/lib/config.d.ts +2 -0
  22. package/lib/config.d.ts.map +1 -1
  23. package/lib/config.js +8 -10
  24. package/lib/config.js.map +1 -1
  25. package/lib/debugLogger.d.ts +3 -3
  26. package/lib/debugLogger.d.ts.map +1 -1
  27. package/lib/debugLogger.js +7 -14
  28. package/lib/debugLogger.js.map +1 -1
  29. package/lib/index.d.ts +2 -2
  30. package/lib/index.d.ts.map +1 -1
  31. package/lib/index.js +2 -2
  32. package/lib/index.js.map +1 -1
  33. package/lib/logger.d.ts +56 -2
  34. package/lib/logger.d.ts.map +1 -1
  35. package/lib/logger.js +83 -8
  36. package/lib/logger.js.map +1 -1
  37. package/lib/mockLogger.d.ts.map +1 -1
  38. package/lib/mockLogger.js +22 -5
  39. package/lib/mockLogger.js.map +1 -1
  40. package/package.json +6 -6
  41. package/src/config.ts +11 -6
  42. package/src/debugLogger.ts +12 -17
  43. package/src/index.ts +8 -0
  44. package/src/logger.ts +127 -9
  45. package/src/mockLogger.ts +30 -6
package/src/logger.ts CHANGED
@@ -59,14 +59,39 @@ export interface ITelemetryLoggerPropertyBags {
59
59
  error?: ITelemetryLoggerPropertyBag;
60
60
  }
61
61
 
62
+ /**
63
+ * Attempts to parse number from string.
64
+ * If fails,returns original string.
65
+ * Used to make telemetry data typed (and support math operations, like comparison),
66
+ * in places where we do expect numbers (like contentsize/duration property in http header)
67
+ */
68
+ export function numberFromString(str: string | null | undefined): string | number | undefined {
69
+ if (str === undefined || str === null) {
70
+ return undefined;
71
+ }
72
+ const num = Number(str);
73
+ return Number.isNaN(num) ? str : num;
74
+ }
75
+
76
+ export function formatTick(tick: number): number {
77
+ return Math.floor(tick);
78
+ }
79
+
80
+ export const eventNamespaceSeparator = ":" as const;
81
+
62
82
  /**
63
83
  * TelemetryLogger class contains various helper telemetry methods,
64
84
  * encoding in one place schemas for various types of Fluid telemetry events.
65
85
  * Creates sub-logger that appends properties to all events
86
+ *
87
+ * @deprecated - In a subsequent release this type will no longer be exported, use ITelemetryLogger instead
66
88
  */
67
89
  export abstract class TelemetryLogger implements ITelemetryLoggerExt {
68
- public static readonly eventNamespaceSeparator = ":";
90
+ public static readonly eventNamespaceSeparator = eventNamespaceSeparator;
69
91
 
92
+ /**
93
+ * @deprecated - use formatTick
94
+ */
70
95
  public static formatTick(tick: number): number {
71
96
  return Math.floor(tick);
72
97
  }
@@ -76,6 +101,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
76
101
  * If fails,returns original string.
77
102
  * Used to make telemetry data typed (and support math operations, like comparison),
78
103
  * in places where we do expect numbers (like contentsize/duration property in http header)
104
+ * @deprecated - use numberFromString
79
105
  */
80
106
  public static numberFromString(str: string | null | undefined): string | number | undefined {
81
107
  if (str === undefined || str === null) {
@@ -163,7 +189,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
163
189
 
164
190
  // Will include Nan & Infinity, but probably we do not care
165
191
  if (typeof newEvent.duration === "number") {
166
- newEvent.duration = TelemetryLogger.formatTick(newEvent.duration);
192
+ newEvent.duration = formatTick(newEvent.duration);
167
193
  }
168
194
 
169
195
  this.send(newEvent);
@@ -211,6 +237,14 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
211
237
  if (this.namespace !== undefined) {
212
238
  newEvent.eventName = `${this.namespace}${TelemetryLogger.eventNamespaceSeparator}${newEvent.eventName}`;
213
239
  }
240
+ return this.extendProperties(newEvent, includeErrorProps);
241
+ }
242
+
243
+ private extendProperties<T extends ITelemetryLoggerPropertyBag = ITelemetryLoggerPropertyBag>(
244
+ toExtend: T,
245
+ includeErrorProps: boolean,
246
+ ) {
247
+ const eventLike: ITelemetryLoggerPropertyBag = toExtend;
214
248
  if (this.properties) {
215
249
  const properties: (undefined | ITelemetryLoggerPropertyBag)[] = [];
216
250
  properties.push(this.properties.all);
@@ -220,7 +254,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
220
254
  for (const props of properties) {
221
255
  if (props !== undefined) {
222
256
  for (const key of Object.keys(props)) {
223
- if (event[key] !== undefined) {
257
+ if (eventLike[key] !== undefined) {
224
258
  continue;
225
259
  }
226
260
  const getterOrValue = props[key];
@@ -228,13 +262,13 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
228
262
  const value =
229
263
  typeof getterOrValue === "function" ? getterOrValue() : getterOrValue;
230
264
  if (value !== undefined) {
231
- newEvent[key] = value;
265
+ eventLike[key] = value;
232
266
  }
233
267
  }
234
268
  }
235
269
  }
236
270
  }
237
- return newEvent;
271
+ return toExtend;
238
272
  }
239
273
  }
240
274
 
@@ -284,10 +318,29 @@ export class TaggedLoggerAdapter implements ITelemetryBaseLogger {
284
318
  }
285
319
  }
286
320
 
321
+ /**
322
+ * Create a child logger based on the provided props object
323
+ * @param props - logger is the base logger the child will log to after it's processing, namespace will be prefixed to all event names, properties are default properties that will be applied events.
324
+ *
325
+ * @remarks
326
+ * Passing in no props object (i.e. undefined) will return a logger that is effectively a no-op.
327
+ */
328
+ export function createChildLogger(props?: {
329
+ logger?: ITelemetryBaseLogger;
330
+ namespace?: string;
331
+ properties?: ITelemetryLoggerPropertyBags;
332
+ }): ITelemetryLoggerExt {
333
+ if (props === undefined) {
334
+ return new TelemetryNullLogger();
335
+ }
336
+ return ChildLogger.create(props?.logger, props?.namespace, props?.properties);
337
+ }
338
+
287
339
  /**
288
340
  * ChildLogger class contains various helper telemetry methods,
289
341
  * encoding in one place schemas for various types of Fluid telemetry events.
290
342
  * Creates sub-logger that appends properties to all events
343
+ * @deprecated - Use createChildLogger instead
291
344
  */
292
345
  export class ChildLogger extends TelemetryLogger {
293
346
  /**
@@ -363,20 +416,60 @@ export class ChildLogger extends TelemetryLogger {
363
416
  }
364
417
  }
365
418
 
419
+ /**
420
+ * Create a logger which logs to multiple other loggers based on the provided props object
421
+ * @param props - loggers are the base loggers that will logged to after it's processing, namespace will be prefixed to all event names, properties are default properties that will be applied events.
422
+ * tryInheritProperties will attempted to copy those loggers properties to this loggers if they are of a known type e.g. one from this package
423
+ */
424
+ export function createMultiSinkLogger(props: {
425
+ namespace?: string;
426
+ properties?: ITelemetryLoggerPropertyBags;
427
+ loggers?: (ITelemetryBaseLogger | undefined)[];
428
+ tryInheritProperties?: true;
429
+ }): ITelemetryLoggerExt {
430
+ return new MultiSinkLogger(
431
+ props.namespace,
432
+ props.properties,
433
+ props.loggers?.filter((l): l is ITelemetryBaseLogger => l !== undefined),
434
+ props.tryInheritProperties,
435
+ );
436
+ }
437
+
366
438
  /**
367
439
  * Multi-sink logger
368
440
  * Takes multiple ITelemetryBaseLogger objects (sinks) and logs all events into each sink
441
+ * @deprecated - use createMultiSinkLogger instead
369
442
  */
370
443
  export class MultiSinkLogger extends TelemetryLogger {
371
- protected loggers: ITelemetryBaseLogger[] = [];
372
-
444
+ protected loggers: ITelemetryBaseLogger[];
373
445
  /**
374
446
  * Create multiple sink logger (i.e. logger that sends events to multiple sinks)
375
447
  * @param namespace - Telemetry event name prefix to add to all events
376
448
  * @param properties - Base properties to add to all events
449
+ * @param loggers - The list of loggers to use as sinks
450
+ * @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
377
451
  */
378
- constructor(namespace?: string, properties?: ITelemetryLoggerPropertyBags) {
379
- super(namespace, properties);
452
+ constructor(
453
+ namespace?: string,
454
+ properties?: ITelemetryLoggerPropertyBags,
455
+ loggers: ITelemetryBaseLogger[] = [],
456
+ tryInheritProperties?: true,
457
+ ) {
458
+ let realProperties = properties !== undefined ? { ...properties } : undefined;
459
+ if (tryInheritProperties === true) {
460
+ const merge = (realProperties ??= {});
461
+ loggers
462
+ .filter((l): l is this => l instanceof TelemetryLogger)
463
+ .map((l) => l.properties ?? {})
464
+ .forEach((cv) => {
465
+ Object.keys(cv).forEach((k) => {
466
+ merge[k] = { ...cv[k], ...merge?.[k] };
467
+ });
468
+ });
469
+ }
470
+
471
+ super(namespace, realProperties);
472
+ this.loggers = loggers;
380
473
  }
381
474
 
382
475
  /**
@@ -561,6 +654,7 @@ export class PerformanceEvent {
561
654
  /**
562
655
  * Logger that is useful for UT
563
656
  * It can be used in places where logger instance is required, but events should be not send over.
657
+ * @deprecated - Use createChildLogger instead
564
658
  */
565
659
  export class TelemetryUTLogger implements ITelemetryLoggerExt {
566
660
  public send(event: ITelemetryBaseEvent): void {}
@@ -596,6 +690,7 @@ export class TelemetryUTLogger implements ITelemetryLoggerExt {
596
690
  /**
597
691
  * Null logger
598
692
  * It can be used in places where logger instance is required, but events should be not send over.
693
+ * @deprecated - for internal use only
599
694
  */
600
695
  export class BaseTelemetryNullLogger implements ITelemetryBaseLogger {
601
696
  /**
@@ -611,6 +706,7 @@ export class BaseTelemetryNullLogger implements ITelemetryBaseLogger {
611
706
  /**
612
707
  * Null logger
613
708
  * It can be used in places where logger instance is required, but events should be not send over.
709
+ * @deprecated - for internal use only
614
710
  */
615
711
  export class TelemetryNullLogger implements ITelemetryLoggerExt {
616
712
  public send(event: ITelemetryBaseEvent): void {}
@@ -676,3 +772,25 @@ function convertToBasePropertyTypeUntagged(
676
772
  return `INVALID PROPERTY (typed as ${typeof x})`;
677
773
  }
678
774
  }
775
+
776
+ export const tagData = <
777
+ T extends TelemetryDataTag,
778
+ V extends Record<string, TelemetryEventPropertyTypeExt>,
779
+ >(
780
+ tag: T,
781
+ values: V,
782
+ ) =>
783
+ (Object.entries(values) as [keyof V, V[keyof V]][])
784
+ .filter((e): e is [keyof V, Exclude<V[keyof V], undefined>] => e[1] !== undefined)
785
+ .reduce<{
786
+ [P in keyof V]:
787
+ | (V[P] extends undefined ? undefined : never)
788
+ | { value: Exclude<V[P], undefined>; tag: T };
789
+ }>((pv, cv) => {
790
+ pv[cv[0]] = { tag, value: cv[1] };
791
+ return pv;
792
+ }, {} as any);
793
+
794
+ export const tagCodeArtifacts = <T extends Record<string, TelemetryEventPropertyTypeExt>>(
795
+ values: T,
796
+ ) => tagData(TelemetryDataTag.CodeArtifact, values);
package/src/mockLogger.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { ITelemetryBaseEvent } from "@fluidframework/core-interfaces";
7
7
  import { assert } from "@fluidframework/common-utils";
8
8
  import { TelemetryLogger } from "./logger";
9
- import { ITelemetryLoggerExt } from "./telemetryTypes";
9
+ import { ITelemetryLoggerExt, ITelemetryPropertiesExt } from "./telemetryTypes";
10
10
 
11
11
  /**
12
12
  * The MockLogger records events sent to it, and then can walk back over those events
@@ -185,7 +185,6 @@ ${JSON.stringify(actualEvents)}`);
185
185
  inlineDetailsProp: boolean,
186
186
  ): boolean {
187
187
  const { details, ...actualForMatching } = actual;
188
- let detailsExpanded = { details };
189
188
  // "details" is used in a lot of telemetry logs to group a bunch of properties together and stringify them.
190
189
  // Some of the properties in the expected event may be inside "details". So, if inlineDetailsProp is true,
191
190
  // extract the properties from "details" in the actual event and inline them in the actual event.
@@ -194,10 +193,35 @@ ${JSON.stringify(actualEvents)}`);
194
193
  typeof details === "string",
195
194
  0x6c9 /* Details should a JSON stringified string if inlineDetailsProp is true */,
196
195
  );
197
- detailsExpanded = JSON.parse(details);
196
+ const detailsExpanded = JSON.parse(details);
197
+ return matchObjects({ ...actualForMatching, ...detailsExpanded }, expected);
198
198
  }
199
- const actualExpanded: ITelemetryBaseEvent = { ...actualForMatching, ...detailsExpanded };
200
- const masked = { ...actualExpanded, ...expected };
201
- return JSON.stringify(masked) === JSON.stringify(actualExpanded);
199
+ return matchObjects(actual, expected);
202
200
  }
203
201
  }
202
+
203
+ function matchObjects(actual: ITelemetryPropertiesExt, expected: ITelemetryPropertiesExt) {
204
+ for (const [expectedKey, expectedValue] of Object.entries(expected)) {
205
+ const actualValue = actual[expectedKey];
206
+ if (
207
+ !Array.isArray(expectedValue) &&
208
+ expectedValue !== null &&
209
+ typeof expectedValue === "object"
210
+ ) {
211
+ if (
212
+ Array.isArray(actualValue) ||
213
+ actualValue === null ||
214
+ typeof actualValue !== "object" ||
215
+ !matchObjects(
216
+ actualValue as ITelemetryPropertiesExt,
217
+ expectedValue as ITelemetryPropertiesExt,
218
+ )
219
+ ) {
220
+ return false;
221
+ }
222
+ } else if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {
223
+ return false;
224
+ }
225
+ }
226
+ return true;
227
+ }