@fluidframework/telemetry-utils 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191258

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 (122) hide show
  1. package/.eslintrc.js +2 -1
  2. package/CHANGELOG.md +108 -0
  3. package/README.md +4 -3
  4. package/dist/config.d.ts +2 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +34 -36
  7. package/dist/config.js.map +1 -1
  8. package/dist/error.d.ts +92 -0
  9. package/dist/error.d.ts.map +1 -0
  10. package/dist/error.js +133 -0
  11. package/dist/error.js.map +1 -0
  12. package/dist/errorLogging.d.ts +44 -19
  13. package/dist/errorLogging.d.ts.map +1 -1
  14. package/dist/errorLogging.js +70 -31
  15. package/dist/errorLogging.js.map +1 -1
  16. package/dist/eventEmitterWithErrorHandling.d.ts +3 -3
  17. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  18. package/dist/eventEmitterWithErrorHandling.js +10 -3
  19. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  20. package/dist/events.d.ts +1 -1
  21. package/dist/events.d.ts.map +1 -1
  22. package/dist/events.js.map +1 -1
  23. package/dist/fluidErrorBase.d.ts +48 -15
  24. package/dist/fluidErrorBase.d.ts.map +1 -1
  25. package/dist/fluidErrorBase.js +21 -14
  26. package/dist/fluidErrorBase.js.map +1 -1
  27. package/dist/index.d.ts +5 -5
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +16 -8
  30. package/dist/index.js.map +1 -1
  31. package/dist/logger.d.ts +98 -60
  32. package/dist/logger.d.ts.map +1 -1
  33. package/dist/logger.js +193 -124
  34. package/dist/logger.js.map +1 -1
  35. package/dist/mockLogger.d.ts +17 -8
  36. package/dist/mockLogger.d.ts.map +1 -1
  37. package/dist/mockLogger.js +49 -28
  38. package/dist/mockLogger.js.map +1 -1
  39. package/dist/sampledTelemetryHelper.d.ts +8 -7
  40. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  41. package/dist/sampledTelemetryHelper.js +21 -16
  42. package/dist/sampledTelemetryHelper.js.map +1 -1
  43. package/dist/telemetryTypes.d.ts +20 -6
  44. package/dist/telemetryTypes.d.ts.map +1 -1
  45. package/dist/telemetryTypes.js.map +1 -1
  46. package/dist/thresholdCounter.d.ts.map +1 -1
  47. package/dist/thresholdCounter.js.map +1 -1
  48. package/dist/utils.d.ts +2 -2
  49. package/dist/utils.d.ts.map +1 -1
  50. package/dist/utils.js +2 -2
  51. package/dist/utils.js.map +1 -1
  52. package/lib/config.d.ts +2 -0
  53. package/lib/config.d.ts.map +1 -1
  54. package/lib/config.js +33 -36
  55. package/lib/config.js.map +1 -1
  56. package/lib/error.d.ts +92 -0
  57. package/lib/error.d.ts.map +1 -0
  58. package/lib/error.js +125 -0
  59. package/lib/error.js.map +1 -0
  60. package/lib/errorLogging.d.ts +44 -19
  61. package/lib/errorLogging.d.ts.map +1 -1
  62. package/lib/errorLogging.js +69 -31
  63. package/lib/errorLogging.js.map +1 -1
  64. package/lib/eventEmitterWithErrorHandling.d.ts +3 -3
  65. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  66. package/lib/eventEmitterWithErrorHandling.js +9 -2
  67. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  68. package/lib/events.d.ts +1 -1
  69. package/lib/events.d.ts.map +1 -1
  70. package/lib/events.js.map +1 -1
  71. package/lib/fluidErrorBase.d.ts +48 -15
  72. package/lib/fluidErrorBase.d.ts.map +1 -1
  73. package/lib/fluidErrorBase.js +21 -14
  74. package/lib/fluidErrorBase.js.map +1 -1
  75. package/lib/index.d.ts +5 -5
  76. package/lib/index.d.ts.map +1 -1
  77. package/lib/index.js +4 -4
  78. package/lib/index.js.map +1 -1
  79. package/lib/logger.d.ts +98 -60
  80. package/lib/logger.d.ts.map +1 -1
  81. package/lib/logger.js +184 -119
  82. package/lib/logger.js.map +1 -1
  83. package/lib/mockLogger.d.ts +17 -8
  84. package/lib/mockLogger.d.ts.map +1 -1
  85. package/lib/mockLogger.js +50 -29
  86. package/lib/mockLogger.js.map +1 -1
  87. package/lib/sampledTelemetryHelper.d.ts +8 -7
  88. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  89. package/lib/sampledTelemetryHelper.js +19 -14
  90. package/lib/sampledTelemetryHelper.js.map +1 -1
  91. package/lib/telemetryTypes.d.ts +20 -6
  92. package/lib/telemetryTypes.d.ts.map +1 -1
  93. package/lib/telemetryTypes.js.map +1 -1
  94. package/lib/thresholdCounter.d.ts.map +1 -1
  95. package/lib/thresholdCounter.js.map +1 -1
  96. package/lib/utils.d.ts +2 -2
  97. package/lib/utils.d.ts.map +1 -1
  98. package/lib/utils.js +2 -2
  99. package/lib/utils.js.map +1 -1
  100. package/package.json +19 -22
  101. package/src/config.ts +23 -13
  102. package/src/error.ts +202 -0
  103. package/src/errorLogging.ts +101 -56
  104. package/src/eventEmitterWithErrorHandling.ts +5 -3
  105. package/src/events.ts +3 -3
  106. package/src/fluidErrorBase.ts +62 -26
  107. package/src/index.ts +17 -6
  108. package/src/logger.ts +290 -120
  109. package/src/mockLogger.ts +65 -24
  110. package/src/sampledTelemetryHelper.ts +18 -14
  111. package/src/telemetryTypes.ts +29 -6
  112. package/src/thresholdCounter.ts +2 -2
  113. package/src/utils.ts +2 -2
  114. package/dist/debugLogger.d.ts +0 -39
  115. package/dist/debugLogger.d.ts.map +0 -1
  116. package/dist/debugLogger.js +0 -112
  117. package/dist/debugLogger.js.map +0 -1
  118. package/lib/debugLogger.d.ts +0 -39
  119. package/lib/debugLogger.d.ts.map +0 -1
  120. package/lib/debugLogger.js +0 -108
  121. package/lib/debugLogger.js.map +0 -1
  122. package/src/debugLogger.ts +0 -143
package/src/logger.ts CHANGED
@@ -10,11 +10,13 @@ import {
10
10
  ITelemetryGenericEvent,
11
11
  ITelemetryPerformanceEvent,
12
12
  ITelemetryProperties,
13
- TelemetryEventPropertyType,
14
- ITaggedTelemetryPropertyType,
15
- TelemetryEventCategory,
13
+ TelemetryBaseEventPropertyType as TelemetryEventPropertyType,
14
+ LogLevel,
15
+ Tagged,
16
+ ITelemetryBaseProperties,
17
+ TelemetryBaseEventPropertyType,
16
18
  } from "@fluidframework/core-interfaces";
17
- import { IsomorphicPerformance, performance } from "@fluidframework/common-utils";
19
+ import { IsomorphicPerformance, performance } from "@fluid-internal/client-utils";
18
20
  import { CachedConfigProvider, loggerIsMonitoringContext, mixinMonitoringContext } from "./config";
19
21
  import {
20
22
  isILoggingError,
@@ -23,12 +25,12 @@ import {
23
25
  isTaggedTelemetryPropertyValue,
24
26
  } from "./errorLogging";
25
27
  import {
26
- ITaggedTelemetryPropertyTypeExt,
27
28
  ITelemetryEventExt,
28
29
  ITelemetryGenericEventExt,
29
30
  ITelemetryLoggerExt,
30
31
  ITelemetryPerformanceEventExt,
31
32
  TelemetryEventPropertyTypeExt,
33
+ TelemetryEventCategory,
32
34
  } from "./telemetryTypes";
33
35
 
34
36
  export interface Memory {
@@ -43,13 +45,17 @@ export interface PerformanceWithMemory extends IsomorphicPerformance {
43
45
  * Please do not modify existing entries for backwards compatibility.
44
46
  */
45
47
  export enum TelemetryDataTag {
46
- /** Data containing terms or IDs from code packages that may have been dynamically loaded */
48
+ /**
49
+ * Data containing terms or IDs from code packages that may have been dynamically loaded
50
+ */
47
51
  CodeArtifact = "CodeArtifact",
48
- /** Personal data of a variety of classifications that pertains to the user */
52
+ /**
53
+ * Personal data of a variety of classifications that pertains to the user
54
+ */
49
55
  UserData = "UserData",
50
56
  }
51
57
 
52
- export type TelemetryEventPropertyTypes = TelemetryEventPropertyType | ITaggedTelemetryPropertyType;
58
+ export type TelemetryEventPropertyTypes = ITelemetryBaseProperties[string];
53
59
 
54
60
  export interface ITelemetryLoggerPropertyBag {
55
61
  [index: string]: TelemetryEventPropertyTypes | (() => TelemetryEventPropertyTypes);
@@ -59,33 +65,36 @@ export interface ITelemetryLoggerPropertyBags {
59
65
  error?: ITelemetryLoggerPropertyBag;
60
66
  }
61
67
 
68
+ /**
69
+ * Attempts to parse number from string.
70
+ * If fails,returns original string.
71
+ * Used to make telemetry data typed (and support math operations, like comparison),
72
+ * in places where we do expect numbers (like contentsize/duration property in http header)
73
+ */
74
+ // eslint-disable-next-line @rushstack/no-new-null
75
+ export function numberFromString(str: string | null | undefined): string | number | undefined {
76
+ if (str === undefined || str === null) {
77
+ return undefined;
78
+ }
79
+ const num = Number(str);
80
+ return Number.isNaN(num) ? str : num;
81
+ }
82
+
83
+ export function formatTick(tick: number): number {
84
+ return Math.floor(tick);
85
+ }
86
+
87
+ export const eventNamespaceSeparator = ":" as const;
88
+
62
89
  /**
63
90
  * TelemetryLogger class contains various helper telemetry methods,
64
91
  * encoding in one place schemas for various types of Fluid telemetry events.
65
92
  * Creates sub-logger that appends properties to all events
66
93
  */
67
94
  export abstract class TelemetryLogger implements ITelemetryLoggerExt {
68
- public static readonly eventNamespaceSeparator = ":";
69
-
70
- public static formatTick(tick: number): number {
71
- return Math.floor(tick);
72
- }
73
-
74
- /**
75
- * Attempts to parse number from string.
76
- * If fails,returns original string.
77
- * Used to make telemetry data typed (and support math operations, like comparison),
78
- * in places where we do expect numbers (like contentsize/duration property in http header)
79
- */
80
- public static numberFromString(str: string | null | undefined): string | number | undefined {
81
- if (str === undefined || str === null) {
82
- return undefined;
83
- }
84
- const num = Number(str);
85
- return Number.isNaN(num) ? str : num;
86
- }
95
+ public static readonly eventNamespaceSeparator = eventNamespaceSeparator;
87
96
 
88
- public static sanitizePkgName(name: string) {
97
+ public static sanitizePkgName(name: string): string {
89
98
  return name.replace("@", "").replace("/", "-");
90
99
  }
91
100
 
@@ -96,7 +105,11 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
96
105
  * @param error - Error to extract info from
97
106
  * @param fetchStack - Whether to fetch the current callstack if error.stack is undefined
98
107
  */
99
- public static prepareErrorObject(event: ITelemetryBaseEvent, error: any, fetchStack: boolean) {
108
+ public static prepareErrorObject(
109
+ event: ITelemetryBaseEvent,
110
+ error: unknown,
111
+ fetchStack: boolean,
112
+ ): void {
100
113
  const { message, errorType, stack } = extractLogSafeErrorProperties(
101
114
  error,
102
115
  true /* sanitizeStack */,
@@ -134,16 +147,26 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
134
147
  *
135
148
  * @param event - the event to send
136
149
  */
137
- public abstract send(event: ITelemetryBaseEvent): void;
150
+ public abstract send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void;
138
151
 
139
152
  /**
140
153
  * Send a telemetry event with the logger
141
154
  *
142
155
  * @param event - the event to send
143
156
  * @param error - optional error object to log
157
+ * @param logLevel - optional level of the log. It category of event is set as error,
158
+ * then the logLevel will be upgraded to be an error.
144
159
  */
145
- public sendTelemetryEvent(event: ITelemetryGenericEventExt, error?: any) {
146
- this.sendTelemetryEventCore({ ...event, category: event.category ?? "generic" }, error);
160
+ public sendTelemetryEvent(
161
+ event: ITelemetryGenericEventExt,
162
+ error?: unknown,
163
+ logLevel: typeof LogLevel.verbose | typeof LogLevel.default = LogLevel.default,
164
+ ): void {
165
+ this.sendTelemetryEventCore(
166
+ { ...event, category: event.category ?? "generic" },
167
+ error,
168
+ event.category === "error" ? LogLevel.error : logLevel,
169
+ );
147
170
  }
148
171
 
149
172
  /**
@@ -151,11 +174,13 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
151
174
  *
152
175
  * @param event - the event to send
153
176
  * @param error - optional error object to log
177
+ * @param logLevel - optional level of the log.
154
178
  */
155
179
  protected sendTelemetryEventCore(
156
180
  event: ITelemetryGenericEventExt & { category: TelemetryEventCategory },
157
- error?: any,
158
- ) {
181
+ error?: unknown,
182
+ logLevel?: LogLevel,
183
+ ): void {
159
184
  const newEvent = convertToBaseEvent(event);
160
185
  if (error !== undefined) {
161
186
  TelemetryLogger.prepareErrorObject(newEvent, error, false);
@@ -163,10 +188,10 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
163
188
 
164
189
  // Will include Nan & Infinity, but probably we do not care
165
190
  if (typeof newEvent.duration === "number") {
166
- newEvent.duration = TelemetryLogger.formatTick(newEvent.duration);
191
+ newEvent.duration = formatTick(newEvent.duration);
167
192
  }
168
193
 
169
- this.send(newEvent);
194
+ this.send(newEvent, logLevel);
170
195
  }
171
196
 
172
197
  /**
@@ -175,7 +200,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
175
200
  * @param event - the event to send
176
201
  * @param error - optional error object to log
177
202
  */
178
- public sendErrorEvent(event: ITelemetryErrorEvent, error?: any) {
203
+ public sendErrorEvent(event: ITelemetryErrorEvent, error?: unknown): void {
179
204
  this.sendTelemetryEventCore(
180
205
  {
181
206
  // ensure the error field has some value,
@@ -185,6 +210,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
185
210
  category: "error",
186
211
  },
187
212
  error,
213
+ LogLevel.error,
188
214
  );
189
215
  }
190
216
 
@@ -193,14 +219,24 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
193
219
  *
194
220
  * @param event - Event to send
195
221
  * @param error - optional error object to log
222
+ * @param logLevel - optional level of the log. It category of event is set as error,
223
+ * then the logLevel will be upgraded to be an error.
196
224
  */
197
- public sendPerformanceEvent(event: ITelemetryPerformanceEventExt, error?: any): void {
225
+ public sendPerformanceEvent(
226
+ event: ITelemetryPerformanceEventExt,
227
+ error?: unknown,
228
+ logLevel: typeof LogLevel.verbose | typeof LogLevel.default = LogLevel.default,
229
+ ): void {
198
230
  const perfEvent = {
199
231
  ...event,
200
232
  category: event.category ?? "performance",
201
233
  };
202
234
 
203
- this.sendTelemetryEventCore(perfEvent, error);
235
+ this.sendTelemetryEventCore(
236
+ perfEvent,
237
+ error,
238
+ perfEvent.category === "error" ? LogLevel.error : logLevel,
239
+ );
204
240
  }
205
241
 
206
242
  protected prepareEvent(event: ITelemetryBaseEvent): ITelemetryBaseEvent {
@@ -211,6 +247,14 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
211
247
  if (this.namespace !== undefined) {
212
248
  newEvent.eventName = `${this.namespace}${TelemetryLogger.eventNamespaceSeparator}${newEvent.eventName}`;
213
249
  }
250
+ return this.extendProperties(newEvent, includeErrorProps);
251
+ }
252
+
253
+ private extendProperties<T extends ITelemetryLoggerPropertyBag = ITelemetryLoggerPropertyBag>(
254
+ toExtend: T,
255
+ includeErrorProps: boolean,
256
+ ): T {
257
+ const eventLike: ITelemetryLoggerPropertyBag = toExtend;
214
258
  if (this.properties) {
215
259
  const properties: (undefined | ITelemetryLoggerPropertyBag)[] = [];
216
260
  properties.push(this.properties.all);
@@ -220,7 +264,7 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
220
264
  for (const props of properties) {
221
265
  if (props !== undefined) {
222
266
  for (const key of Object.keys(props)) {
223
- if (event[key] !== undefined) {
267
+ if (eventLike[key] !== undefined) {
224
268
  continue;
225
269
  }
226
270
  const getterOrValue = props[key];
@@ -228,13 +272,13 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
228
272
  const value =
229
273
  typeof getterOrValue === "function" ? getterOrValue() : getterOrValue;
230
274
  if (value !== undefined) {
231
- newEvent[key] = value;
275
+ eventLike[key] = value;
232
276
  }
233
277
  }
234
278
  }
235
279
  }
236
280
  }
237
- return newEvent;
281
+ return toExtend;
238
282
  }
239
283
  }
240
284
 
@@ -246,7 +290,10 @@ export abstract class TelemetryLogger implements ITelemetryLoggerExt {
246
290
  export class TaggedLoggerAdapter implements ITelemetryBaseLogger {
247
291
  public constructor(private readonly logger: ITelemetryBaseLogger) {}
248
292
 
249
- public send(eventWithTagsMaybe: ITelemetryBaseEvent) {
293
+ /**
294
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}
295
+ */
296
+ public send(eventWithTagsMaybe: ITelemetryBaseEvent): void {
250
297
  const newEvent: ITelemetryBaseEvent = {
251
298
  category: eventWithTagsMaybe.category,
252
299
  eventName: eventWithTagsMaybe.eventName,
@@ -284,6 +331,21 @@ export class TaggedLoggerAdapter implements ITelemetryBaseLogger {
284
331
  }
285
332
  }
286
333
 
334
+ /**
335
+ * Create a child logger based on the provided props object
336
+ * @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.
337
+ *
338
+ * @remarks
339
+ * Passing in no props object (i.e. undefined) will return a logger that is effectively a no-op.
340
+ */
341
+ export function createChildLogger(props?: {
342
+ logger?: ITelemetryBaseLogger;
343
+ namespace?: string;
344
+ properties?: ITelemetryLoggerPropertyBags;
345
+ }): ITelemetryLoggerExt {
346
+ return ChildLogger.create(props?.logger, props?.namespace, props?.properties);
347
+ }
348
+
287
349
  /**
288
350
  * ChildLogger class contains various helper telemetry methods,
289
351
  * encoding in one place schemas for various types of Fluid telemetry events.
@@ -330,11 +392,20 @@ export class ChildLogger extends TelemetryLogger {
330
392
  ? baseLogger.namespace
331
393
  : `${baseLogger.namespace}${TelemetryLogger.eventNamespaceSeparator}${namespace}`;
332
394
 
333
- return new ChildLogger(baseLogger.baseLogger, combinedNamespace, combinedProperties);
395
+ const child = new ChildLogger(
396
+ baseLogger.baseLogger,
397
+ combinedNamespace,
398
+ combinedProperties,
399
+ );
400
+
401
+ if (!loggerIsMonitoringContext(child) && loggerIsMonitoringContext(baseLogger)) {
402
+ mixinMonitoringContext(child, baseLogger.config);
403
+ }
404
+ return child;
334
405
  }
335
406
 
336
407
  return new ChildLogger(
337
- baseLogger ? baseLogger : new BaseTelemetryNullLogger(),
408
+ baseLogger ? baseLogger : { send(): void {} },
338
409
  namespace,
339
410
  properties,
340
411
  );
@@ -353,39 +424,116 @@ export class ChildLogger extends TelemetryLogger {
353
424
  }
354
425
  }
355
426
 
427
+ public get minLogLevel(): LogLevel | undefined {
428
+ return this.baseLogger.minLogLevel;
429
+ }
430
+
431
+ private shouldFilterOutEvent(event: ITelemetryBaseEvent, logLevel?: LogLevel): boolean {
432
+ const eventLogLevel = logLevel ?? LogLevel.default;
433
+ const configLogLevel = this.baseLogger.minLogLevel ?? LogLevel.default;
434
+ // Filter out in case event log level is below what is wanted in config.
435
+ return eventLogLevel < configLogLevel;
436
+ }
437
+
356
438
  /**
357
439
  * Send an event with the logger
358
440
  *
359
441
  * @param event - the event to send
360
442
  */
361
- public send(event: ITelemetryBaseEvent): void {
362
- this.baseLogger.send(this.prepareEvent(event));
443
+ public send(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
444
+ if (this.shouldFilterOutEvent(event, logLevel)) {
445
+ return;
446
+ }
447
+ this.baseLogger.send(this.prepareEvent(event), logLevel);
363
448
  }
364
449
  }
365
450
 
451
+ /**
452
+ * Create a logger which logs to multiple other loggers based on the provided props object
453
+ * @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.
454
+ * tryInheritProperties will attempted to copy those loggers properties to this loggers if they are of a known type e.g. one from this package
455
+ */
456
+ export function createMultiSinkLogger(props: {
457
+ namespace?: string;
458
+ properties?: ITelemetryLoggerPropertyBags;
459
+ loggers?: (ITelemetryBaseLogger | undefined)[];
460
+ tryInheritProperties?: true;
461
+ }): ITelemetryLoggerExt {
462
+ return new MultiSinkLogger(
463
+ props.namespace,
464
+ props.properties,
465
+ props.loggers?.filter((l): l is ITelemetryBaseLogger => l !== undefined),
466
+ props.tryInheritProperties,
467
+ );
468
+ }
469
+
366
470
  /**
367
471
  * Multi-sink logger
368
472
  * Takes multiple ITelemetryBaseLogger objects (sinks) and logs all events into each sink
369
473
  */
370
474
  export class MultiSinkLogger extends TelemetryLogger {
371
- protected loggers: ITelemetryBaseLogger[] = [];
475
+ protected loggers: ITelemetryBaseLogger[];
476
+ // This is minimum of minLlogLevel of all loggers.
477
+ private _minLogLevelOfAllLoggers: LogLevel;
372
478
 
373
479
  /**
374
480
  * Create multiple sink logger (i.e. logger that sends events to multiple sinks)
375
481
  * @param namespace - Telemetry event name prefix to add to all events
376
482
  * @param properties - Base properties to add to all events
483
+ * @param loggers - The list of loggers to use as sinks
484
+ * @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
485
  */
378
- constructor(namespace?: string, properties?: ITelemetryLoggerPropertyBags) {
379
- super(namespace, properties);
486
+ constructor(
487
+ namespace?: string,
488
+ properties?: ITelemetryLoggerPropertyBags,
489
+ loggers: ITelemetryBaseLogger[] = [],
490
+ tryInheritProperties?: true,
491
+ ) {
492
+ let realProperties = properties !== undefined ? { ...properties } : undefined;
493
+ if (tryInheritProperties === true) {
494
+ const merge = (realProperties ??= {});
495
+ loggers
496
+ .filter((l): l is this => l instanceof TelemetryLogger)
497
+ .map((l) => l.properties ?? {})
498
+ // eslint-disable-next-line unicorn/no-array-for-each
499
+ .forEach((cv) => {
500
+ // eslint-disable-next-line unicorn/no-array-for-each
501
+ Object.keys(cv).forEach((k) => {
502
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
503
+ merge[k] = { ...cv[k], ...merge?.[k] };
504
+ });
505
+ });
506
+ }
507
+
508
+ super(namespace, realProperties);
509
+ this.loggers = loggers;
510
+ this._minLogLevelOfAllLoggers = LogLevel.default;
511
+ this.calculateMinLogLevel();
512
+ }
513
+
514
+ public get minLogLevel(): LogLevel {
515
+ return this._minLogLevelOfAllLoggers;
516
+ }
517
+
518
+ private calculateMinLogLevel(): void {
519
+ if (this.loggers.length > 0) {
520
+ const logLevels: LogLevel[] = [];
521
+ for (const logger of this.loggers) {
522
+ logLevels.push(logger.minLogLevel ?? LogLevel.default);
523
+ }
524
+ this._minLogLevelOfAllLoggers = Math.min(...logLevels) as LogLevel;
525
+ }
380
526
  }
381
527
 
382
528
  /**
383
529
  * Add logger to send all events to
384
530
  * @param logger - Logger to add
385
531
  */
386
- public addLogger(logger?: ITelemetryBaseLogger) {
532
+ public addLogger(logger?: ITelemetryBaseLogger): void {
387
533
  if (logger !== undefined && logger !== null) {
388
534
  this.loggers.push(logger);
535
+ // Update in case the logLevel of added logger is less than the current.
536
+ this.calculateMinLogLevel();
389
537
  }
390
538
  }
391
539
 
@@ -396,9 +544,9 @@ export class MultiSinkLogger extends TelemetryLogger {
396
544
  */
397
545
  public send(event: ITelemetryBaseEvent): void {
398
546
  const newEvent = this.prepareEvent(event);
399
- this.loggers.forEach((logger: ITelemetryBaseLogger) => {
547
+ for (const logger of this.loggers) {
400
548
  logger.send(newEvent);
401
- });
549
+ }
402
550
  }
403
551
  }
404
552
 
@@ -423,7 +571,7 @@ export class PerformanceEvent {
423
571
  event: ITelemetryGenericEvent,
424
572
  markers?: IPerformanceEventMarkers,
425
573
  recordHeapSize: boolean = false,
426
- ) {
574
+ ): PerformanceEvent {
427
575
  return new PerformanceEvent(logger, event, markers, recordHeapSize);
428
576
  }
429
577
 
@@ -432,7 +580,7 @@ export class PerformanceEvent {
432
580
  event: ITelemetryGenericEvent,
433
581
  callback: (event: PerformanceEvent) => T,
434
582
  markers?: IPerformanceEventMarkers,
435
- ) {
583
+ ): T {
436
584
  const perfEvent = PerformanceEvent.start(logger, event, markers);
437
585
  try {
438
586
  const ret = callback(perfEvent);
@@ -450,7 +598,7 @@ export class PerformanceEvent {
450
598
  callback: (event: PerformanceEvent) => Promise<T>,
451
599
  markers?: IPerformanceEventMarkers,
452
600
  recordHeapSize?: boolean,
453
- ) {
601
+ ): Promise<T> {
454
602
  const perfEvent = PerformanceEvent.start(logger, event, markers, recordHeapSize);
455
603
  try {
456
604
  const ret = await callback(perfEvent);
@@ -462,7 +610,7 @@ export class PerformanceEvent {
462
610
  }
463
611
  }
464
612
 
465
- public get duration() {
613
+ public get duration(): number {
466
614
  return performance.now() - this.startTime;
467
615
  }
468
616
 
@@ -482,6 +630,7 @@ export class PerformanceEvent {
482
630
  this.reportEvent("start");
483
631
  }
484
632
 
633
+ // eslint-disable-next-line unicorn/no-null
485
634
  if (typeof window === "object" && window != null && window.performance?.mark) {
486
635
  this.startMark = `${event.eventName}-start`;
487
636
  window.performance.mark(this.startMark);
@@ -492,7 +641,7 @@ export class PerformanceEvent {
492
641
  this.reportEvent(eventNameSuffix, props);
493
642
  }
494
643
 
495
- private autoEnd() {
644
+ private autoEnd(): void {
496
645
  // Event might have been cancelled or ended in the callback
497
646
  if (this.event && this.markers.end) {
498
647
  this.reportEvent("end");
@@ -507,7 +656,7 @@ export class PerformanceEvent {
507
656
  this.event = undefined;
508
657
  }
509
658
 
510
- private performanceEndMark() {
659
+ private performanceEndMark(): void {
511
660
  if (this.startMark && this.event) {
512
661
  const endMark = `${this.event.eventName}-end`;
513
662
  window.performance.mark(endMark);
@@ -516,7 +665,7 @@ export class PerformanceEvent {
516
665
  }
517
666
  }
518
667
 
519
- public cancel(props?: ITelemetryProperties, error?: any): void {
668
+ public cancel(props?: ITelemetryProperties, error?: unknown): void {
520
669
  if (this.markers.cancel !== undefined) {
521
670
  this.reportEvent("cancel", { category: this.markers.cancel, ...props }, error);
522
671
  }
@@ -526,7 +675,11 @@ export class PerformanceEvent {
526
675
  /**
527
676
  * Report the event, if it hasn't already been reported.
528
677
  */
529
- public reportEvent(eventNameSuffix: string, props?: ITelemetryProperties, error?: any) {
678
+ public reportEvent(
679
+ eventNameSuffix: string,
680
+ props?: ITelemetryProperties,
681
+ error?: unknown,
682
+ ): void {
530
683
  // There are strange sequences involving multiple Promise chains
531
684
  // where the event can be cancelled and then later a callback is invoked
532
685
  // and the caller attempts to end directly, e.g. issue #3936. Just return.
@@ -559,64 +712,17 @@ export class PerformanceEvent {
559
712
  }
560
713
 
561
714
  /**
562
- * Logger that is useful for UT
563
- * It can be used in places where logger instance is required, but events should be not send over.
564
- */
565
- export class TelemetryUTLogger implements ITelemetryLoggerExt {
566
- public send(event: ITelemetryBaseEvent): void {}
567
- public sendTelemetryEvent(event: ITelemetryGenericEvent, error?: any) {}
568
- public sendErrorEvent(event: ITelemetryErrorEvent, error?: any) {
569
- this.reportError("errorEvent in UT logger!", event, error);
570
- }
571
- public sendPerformanceEvent(event: ITelemetryPerformanceEvent, error?: any): void {}
572
- public logGenericError(eventName: string, error: any) {
573
- this.reportError(`genericError in UT logger!`, { eventName }, error);
574
- }
575
- public logException(event: ITelemetryErrorEvent, exception: any): void {
576
- this.reportError("exception in UT logger!", event, exception);
577
- }
578
- public debugAssert(condition: boolean, event?: ITelemetryErrorEvent): void {
579
- this.reportError("debugAssert in UT logger!");
580
- }
581
- public shipAssert(condition: boolean, event?: ITelemetryErrorEvent): void {
582
- this.reportError("shipAssert in UT logger!");
583
- }
584
-
585
- private reportError(message: string, event?: ITelemetryErrorEvent, err?: any) {
586
- const error = new Error(message);
587
- (error as any).error = error;
588
- (error as any).event = event;
589
- // report to console as exception can be eaten
590
- console.error(message);
591
- console.error(error);
592
- throw error;
593
- }
594
- }
595
-
596
- /**
597
- * Null logger
598
- * It can be used in places where logger instance is required, but events should be not send over.
599
- */
600
- export class BaseTelemetryNullLogger implements ITelemetryBaseLogger {
601
- /**
602
- * Send an event with the logger
603
- *
604
- * @param event - the event to send
605
- */
606
- public send(event: ITelemetryBaseEvent): void {
607
- return;
608
- }
609
- }
610
-
611
- /**
612
- * Null logger
613
- * It can be used in places where logger instance is required, but events should be not send over.
715
+ * Null logger that no-ops for all telemetry events passed to it.
716
+ * @deprecated - This will be removed in a future release.
717
+ * For internal use within the FluidFramework codebase, use {@link createChildLogger} with no arguments instead.
718
+ * For external consumers we recommend writing a trivial implementation of {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
719
+ * where the send() method does nothing and using that.
614
720
  */
615
721
  export class TelemetryNullLogger implements ITelemetryLoggerExt {
616
722
  public send(event: ITelemetryBaseEvent): void {}
617
- public sendTelemetryEvent(event: ITelemetryGenericEvent, error?: any): void {}
618
- public sendErrorEvent(event: ITelemetryErrorEvent, error?: any): void {}
619
- public sendPerformanceEvent(event: ITelemetryPerformanceEvent, error?: any): void {}
723
+ public sendTelemetryEvent(event: ITelemetryGenericEvent, error?: unknown): void {}
724
+ public sendErrorEvent(event: ITelemetryErrorEvent, error?: unknown): void {}
725
+ public sendPerformanceEvent(event: ITelemetryPerformanceEvent, error?: unknown): void {}
620
726
  }
621
727
 
622
728
  /**
@@ -639,15 +745,15 @@ function convertToBaseEvent({
639
745
  /**
640
746
  * Takes in value, and does one of 4 things.
641
747
  * if value is of primitive type - returns the original value.
642
- * If the value is an array of primitives - returns a stringified version of the array.
643
- * If the value is an object of type ITaggedTelemetryPropertyType - returns the object
748
+ * If the value is a flat array or object - returns a stringified version of the array/object.
749
+ * If the value is an object of type Tagged<TelemetryEventPropertyType> - returns the object
644
750
  * with its values recursively converted to base property Type.
645
751
  * If none of these cases are reached - returns an error string
646
752
  * @param x - value passed in to convert to a base property type
647
753
  */
648
754
  export function convertToBasePropertyType(
649
- x: TelemetryEventPropertyTypeExt | ITaggedTelemetryPropertyTypeExt,
650
- ): TelemetryEventPropertyType | ITaggedTelemetryPropertyType {
755
+ x: TelemetryEventPropertyTypeExt | Tagged<TelemetryEventPropertyTypeExt>,
756
+ ): TelemetryEventPropertyType | Tagged<TelemetryEventPropertyType> {
651
757
  return isTaggedTelemetryPropertyValue(x)
652
758
  ? {
653
759
  value: convertToBasePropertyTypeUntagged(x.value),
@@ -676,3 +782,67 @@ function convertToBasePropertyTypeUntagged(
676
782
  return `INVALID PROPERTY (typed as ${typeof x})`;
677
783
  }
678
784
  }
785
+
786
+ export const tagData = <
787
+ T extends TelemetryDataTag,
788
+ V extends Record<
789
+ string,
790
+ TelemetryBaseEventPropertyType | (() => TelemetryBaseEventPropertyType)
791
+ >,
792
+ >(
793
+ tag: T,
794
+ values: V,
795
+ ): {
796
+ [P in keyof V]:
797
+ | (V[P] extends () => TelemetryBaseEventPropertyType
798
+ ? () => {
799
+ value: ReturnType<V[P]>;
800
+ tag: T;
801
+ }
802
+ : {
803
+ value: Exclude<V[P], undefined>;
804
+ tag: T;
805
+ })
806
+ | (V[P] extends undefined ? undefined : never);
807
+ } =>
808
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
809
+ Object.entries(values)
810
+ .filter((e) => e[1] !== undefined)
811
+ // eslint-disable-next-line unicorn/no-array-reduce, unicorn/prefer-object-from-entries
812
+ .reduce((pv, cv) => {
813
+ const [key, value] = cv;
814
+ if (typeof value === "function") {
815
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
816
+ pv[key] = () => {
817
+ return { tag, value: value() };
818
+ };
819
+ } else {
820
+ pv[key] = { tag, value };
821
+ }
822
+ return pv;
823
+ }, {}) as ReturnType<typeof tagData>;
824
+
825
+ /**
826
+ * Helper function to tag telemetry properties as CodeArtifacts. It supports properties of type
827
+ * TelemetryBaseEventPropertyType as well as getters that return TelemetryBaseEventPropertyType.
828
+ */
829
+ export const tagCodeArtifacts = <
830
+ T extends Record<
831
+ string,
832
+ TelemetryBaseEventPropertyType | (() => TelemetryBaseEventPropertyType)
833
+ >,
834
+ >(
835
+ values: T,
836
+ ): {
837
+ [P in keyof T]:
838
+ | (T[P] extends () => TelemetryBaseEventPropertyType
839
+ ? () => {
840
+ value: ReturnType<T[P]>;
841
+ tag: TelemetryDataTag.CodeArtifact;
842
+ }
843
+ : {
844
+ value: Exclude<T[P], undefined>;
845
+ tag: TelemetryDataTag.CodeArtifact;
846
+ })
847
+ | (T[P] extends undefined ? undefined : never);
848
+ } => tagData<TelemetryDataTag.CodeArtifact, T>(TelemetryDataTag.CodeArtifact, values);