@fluidframework/telemetry-utils 1.4.0-121020 → 2.0.0-dev-rc.1.0.0.224419

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 (155) hide show
  1. package/.eslintrc.js +12 -13
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +249 -0
  4. package/README.md +68 -1
  5. package/api-extractor-lint.json +4 -0
  6. package/api-extractor.json +2 -2
  7. package/api-report/telemetry-utils.api.md +444 -0
  8. package/dist/config.d.ts +47 -16
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +88 -38
  11. package/dist/config.js.map +1 -1
  12. package/dist/error.d.ts +112 -0
  13. package/dist/error.d.ts.map +1 -0
  14. package/dist/error.js +159 -0
  15. package/dist/error.js.map +1 -0
  16. package/dist/errorLogging.d.ts +86 -20
  17. package/dist/errorLogging.d.ts.map +1 -1
  18. package/dist/errorLogging.js +190 -60
  19. package/dist/errorLogging.js.map +1 -1
  20. package/dist/eventEmitterWithErrorHandling.d.ts +9 -3
  21. package/dist/eventEmitterWithErrorHandling.d.ts.map +1 -1
  22. package/dist/eventEmitterWithErrorHandling.js +16 -3
  23. package/dist/eventEmitterWithErrorHandling.js.map +1 -1
  24. package/dist/events.d.ts +27 -3
  25. package/dist/events.d.ts.map +1 -1
  26. package/dist/events.js +26 -2
  27. package/dist/events.js.map +1 -1
  28. package/dist/fluidErrorBase.d.ts +57 -16
  29. package/dist/fluidErrorBase.d.ts.map +1 -1
  30. package/dist/fluidErrorBase.js +27 -14
  31. package/dist/fluidErrorBase.js.map +1 -1
  32. package/dist/index.d.ts +12 -11
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +55 -21
  35. package/dist/index.js.map +1 -1
  36. package/dist/logger.d.ts +267 -51
  37. package/dist/logger.d.ts.map +1 -1
  38. package/dist/logger.js +423 -132
  39. package/dist/logger.js.map +1 -1
  40. package/dist/mockLogger.d.ts +39 -12
  41. package/dist/mockLogger.d.ts.map +1 -1
  42. package/dist/mockLogger.js +105 -22
  43. package/dist/mockLogger.js.map +1 -1
  44. package/dist/sampledTelemetryHelper.d.ts +18 -12
  45. package/dist/sampledTelemetryHelper.d.ts.map +1 -1
  46. package/dist/sampledTelemetryHelper.js +28 -19
  47. package/dist/sampledTelemetryHelper.js.map +1 -1
  48. package/dist/telemetry-utils-alpha.d.ts +290 -0
  49. package/dist/telemetry-utils-beta.d.ts +264 -0
  50. package/dist/telemetry-utils-public.d.ts +264 -0
  51. package/dist/telemetry-utils-untrimmed.d.ts +1102 -0
  52. package/dist/telemetryTypes.d.ts +115 -0
  53. package/dist/telemetryTypes.d.ts.map +1 -0
  54. package/dist/telemetryTypes.js +7 -0
  55. package/dist/telemetryTypes.js.map +1 -0
  56. package/dist/thresholdCounter.d.ts +6 -5
  57. package/dist/thresholdCounter.d.ts.map +1 -1
  58. package/dist/thresholdCounter.js +4 -3
  59. package/dist/thresholdCounter.js.map +1 -1
  60. package/dist/tsdoc-metadata.json +11 -0
  61. package/dist/utils.d.ts +54 -3
  62. package/dist/utils.d.ts.map +1 -1
  63. package/dist/utils.js +58 -3
  64. package/dist/utils.js.map +1 -1
  65. package/lib/config.d.ts +47 -16
  66. package/lib/config.d.ts.map +1 -1
  67. package/lib/config.js +85 -36
  68. package/lib/config.js.map +1 -1
  69. package/lib/error.d.ts +112 -0
  70. package/lib/error.d.ts.map +1 -0
  71. package/lib/error.js +150 -0
  72. package/lib/error.js.map +1 -0
  73. package/lib/errorLogging.d.ts +86 -20
  74. package/lib/errorLogging.d.ts.map +1 -1
  75. package/lib/errorLogging.js +189 -60
  76. package/lib/errorLogging.js.map +1 -1
  77. package/lib/eventEmitterWithErrorHandling.d.ts +9 -3
  78. package/lib/eventEmitterWithErrorHandling.d.ts.map +1 -1
  79. package/lib/eventEmitterWithErrorHandling.js +15 -2
  80. package/lib/eventEmitterWithErrorHandling.js.map +1 -1
  81. package/lib/events.d.ts +27 -3
  82. package/lib/events.d.ts.map +1 -1
  83. package/lib/events.js +26 -2
  84. package/lib/events.js.map +1 -1
  85. package/lib/fluidErrorBase.d.ts +57 -16
  86. package/lib/fluidErrorBase.d.ts.map +1 -1
  87. package/lib/fluidErrorBase.js +27 -14
  88. package/lib/fluidErrorBase.js.map +1 -1
  89. package/lib/index.d.ts +12 -11
  90. package/lib/index.d.ts.map +1 -1
  91. package/lib/index.js +11 -11
  92. package/lib/index.js.map +1 -1
  93. package/lib/logger.d.ts +267 -51
  94. package/lib/logger.d.ts.map +1 -1
  95. package/lib/logger.js +415 -131
  96. package/lib/logger.js.map +1 -1
  97. package/lib/mockLogger.d.ts +39 -12
  98. package/lib/mockLogger.d.ts.map +1 -1
  99. package/lib/mockLogger.js +106 -23
  100. package/lib/mockLogger.js.map +1 -1
  101. package/lib/sampledTelemetryHelper.d.ts +18 -12
  102. package/lib/sampledTelemetryHelper.d.ts.map +1 -1
  103. package/lib/sampledTelemetryHelper.js +26 -17
  104. package/lib/sampledTelemetryHelper.js.map +1 -1
  105. package/lib/telemetry-utils-alpha.d.ts +290 -0
  106. package/lib/telemetry-utils-beta.d.ts +264 -0
  107. package/lib/telemetry-utils-public.d.ts +264 -0
  108. package/lib/telemetry-utils-untrimmed.d.ts +1102 -0
  109. package/lib/telemetryTypes.d.ts +115 -0
  110. package/lib/telemetryTypes.d.ts.map +1 -0
  111. package/lib/telemetryTypes.js +6 -0
  112. package/lib/telemetryTypes.js.map +1 -0
  113. package/lib/thresholdCounter.d.ts +6 -5
  114. package/lib/thresholdCounter.d.ts.map +1 -1
  115. package/lib/thresholdCounter.js +4 -3
  116. package/lib/thresholdCounter.js.map +1 -1
  117. package/lib/utils.d.ts +54 -3
  118. package/lib/utils.d.ts.map +1 -1
  119. package/lib/utils.js +56 -2
  120. package/lib/utils.js.map +1 -1
  121. package/package.json +86 -57
  122. package/prettier.config.cjs +8 -0
  123. package/src/config.ts +254 -189
  124. package/src/error.ts +235 -0
  125. package/src/errorLogging.ts +440 -290
  126. package/src/eventEmitterWithErrorHandling.ts +26 -14
  127. package/src/events.ts +54 -25
  128. package/src/fluidErrorBase.ts +94 -46
  129. package/src/index.ts +76 -17
  130. package/src/logger.ts +966 -505
  131. package/src/mockLogger.ts +225 -83
  132. package/src/sampledTelemetryHelper.ts +136 -128
  133. package/src/telemetryTypes.ts +140 -0
  134. package/src/thresholdCounter.ts +38 -37
  135. package/src/utils.ts +108 -17
  136. package/tsconfig.esnext.json +6 -6
  137. package/tsconfig.json +9 -13
  138. package/dist/debugLogger.d.ts +0 -39
  139. package/dist/debugLogger.d.ts.map +0 -1
  140. package/dist/debugLogger.js +0 -101
  141. package/dist/debugLogger.js.map +0 -1
  142. package/dist/packageVersion.d.ts +0 -9
  143. package/dist/packageVersion.d.ts.map +0 -1
  144. package/dist/packageVersion.js +0 -12
  145. package/dist/packageVersion.js.map +0 -1
  146. package/lib/debugLogger.d.ts +0 -39
  147. package/lib/debugLogger.d.ts.map +0 -1
  148. package/lib/debugLogger.js +0 -97
  149. package/lib/debugLogger.js.map +0 -1
  150. package/lib/packageVersion.d.ts +0 -9
  151. package/lib/packageVersion.d.ts.map +0 -1
  152. package/lib/packageVersion.js +0 -9
  153. package/lib/packageVersion.js.map +0 -1
  154. package/src/debugLogger.ts +0 -126
  155. package/src/packageVersion.ts +0 -9
package/lib/logger.js CHANGED
@@ -2,51 +2,65 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { BaseTelemetryNullLogger, performance } from "@fluidframework/common-utils";
6
- import { CachedConfigProvider, loggerIsMonitoringContext, mixinMonitoringContext, } from "./config";
7
- import { isILoggingError, extractLogSafeErrorProperties, generateStack, } from "./errorLogging";
5
+ import { LogLevel, } from "@fluidframework/core-interfaces";
6
+ import { performance } from "@fluid-internal/client-utils";
7
+ import { CachedConfigProvider, loggerIsMonitoringContext, mixinMonitoringContext } from "./config";
8
+ import { isILoggingError, extractLogSafeErrorProperties, generateStack, isTaggedTelemetryPropertyValue, } from "./errorLogging";
8
9
  /**
9
10
  * Broad classifications to be applied to individual properties as they're prepared to be logged to telemetry.
10
- * Please do not modify existing entries for backwards compatibility.
11
+ *
12
+ * @privateRemarks Please do not modify existing entries, to maintain backwards compatibility.
13
+ *
14
+ * @internal
11
15
  */
12
16
  export var TelemetryDataTag;
13
17
  (function (TelemetryDataTag) {
14
18
  /**
15
- * Data containing terms from code packages that may have been dynamically loaded
16
- * @deprecated 1.0, will be removed in next release (see issue #6603). Use `TelemetryDataTag.CodeArtifact` instead.
19
+ * Data containing terms or IDs from code packages that may have been dynamically loaded
17
20
  */
18
- TelemetryDataTag["PackageData"] = "PackageData";
19
- /** Data containing terms or IDs from code packages that may have been dynamically loaded */
20
21
  TelemetryDataTag["CodeArtifact"] = "CodeArtifact";
21
- /** Personal data of a variety of classifications that pertains to the user */
22
+ /**
23
+ * Personal data of a variety of classifications that pertains to the user
24
+ */
22
25
  TelemetryDataTag["UserData"] = "UserData";
23
26
  })(TelemetryDataTag || (TelemetryDataTag = {}));
27
+ /**
28
+ * Attempts to parse number from string.
29
+ * If it fails, it will return the original string.
30
+ *
31
+ * @remarks
32
+ * Used to make telemetry data typed (and support math operations, like comparison),
33
+ * in places where we do expect numbers (like contentsize/duration property in http header).
34
+ *
35
+ * @internal
36
+ */
37
+ // eslint-disable-next-line @rushstack/no-new-null
38
+ export function numberFromString(str) {
39
+ if (str === undefined || str === null) {
40
+ return undefined;
41
+ }
42
+ const num = Number(str);
43
+ return Number.isNaN(num) ? str : num;
44
+ }
45
+ // TODO: add docs
46
+ // eslint-disable-next-line jsdoc/require-description
47
+ /**
48
+ * @internal
49
+ */
50
+ export function formatTick(tick) {
51
+ return Math.floor(tick);
52
+ }
53
+ /**
54
+ * String used to concatenate the namespace of parent loggers and their child loggers.
55
+ * @internal
56
+ */
57
+ export const eventNamespaceSeparator = ":";
24
58
  /**
25
59
  * TelemetryLogger class contains various helper telemetry methods,
26
60
  * encoding in one place schemas for various types of Fluid telemetry events.
27
61
  * Creates sub-logger that appends properties to all events
28
62
  */
29
63
  export class TelemetryLogger {
30
- constructor(namespace, properties) {
31
- this.namespace = namespace;
32
- this.properties = properties;
33
- }
34
- static formatTick(tick) {
35
- return Math.floor(tick);
36
- }
37
- /**
38
- * Attempts to parse number from string.
39
- * If fails,returns original string.
40
- * Used to make telemetry data typed (and support math operations, like comparison),
41
- * in places where we do expect numbers (like contentsize/duration property in http header)
42
- */
43
- static numberFromString(str) {
44
- if (str === undefined || str === null) {
45
- return undefined;
46
- }
47
- const num = Number(str);
48
- return Number.isNaN(num) ? str : num;
49
- }
50
64
  static sanitizePkgName(name) {
51
65
  return name.replace("@", "").replace("/", "-");
52
66
  }
@@ -79,32 +93,38 @@ export class TelemetryLogger {
79
93
  event.stack = generateStack();
80
94
  }
81
95
  }
96
+ constructor(namespace, properties) {
97
+ this.namespace = namespace;
98
+ this.properties = properties;
99
+ }
82
100
  /**
83
101
  * Send a telemetry event with the logger
84
102
  *
85
103
  * @param event - the event to send
86
104
  * @param error - optional error object to log
105
+ * @param logLevel - optional level of the log. It category of event is set as error,
106
+ * then the logLevel will be upgraded to be an error.
87
107
  */
88
- sendTelemetryEvent(event, error) {
89
- var _a;
90
- this.sendTelemetryEventCore(Object.assign(Object.assign({}, event), { category: (_a = event.category) !== null && _a !== void 0 ? _a : "generic" }), error);
108
+ sendTelemetryEvent(event, error, logLevel = LogLevel.default) {
109
+ this.sendTelemetryEventCore({ ...event, category: event.category ?? "generic" }, error, event.category === "error" ? LogLevel.error : logLevel);
91
110
  }
92
111
  /**
93
112
  * Send a telemetry event with the logger
94
113
  *
95
114
  * @param event - the event to send
96
115
  * @param error - optional error object to log
116
+ * @param logLevel - optional level of the log.
97
117
  */
98
- sendTelemetryEventCore(event, error) {
99
- const newEvent = Object.assign({}, event);
118
+ sendTelemetryEventCore(event, error, logLevel) {
119
+ const newEvent = convertToBaseEvent(event);
100
120
  if (error !== undefined) {
101
121
  TelemetryLogger.prepareErrorObject(newEvent, error, false);
102
122
  }
103
123
  // Will include Nan & Infinity, but probably we do not care
104
124
  if (typeof newEvent.duration === "number") {
105
- newEvent.duration = TelemetryLogger.formatTick(newEvent.duration);
125
+ newEvent.duration = formatTick(newEvent.duration);
106
126
  }
107
- this.send(newEvent);
127
+ this.send(newEvent, logLevel);
108
128
  }
109
129
  /**
110
130
  * Send an error telemetry event with the logger
@@ -113,28 +133,41 @@ export class TelemetryLogger {
113
133
  * @param error - optional error object to log
114
134
  */
115
135
  sendErrorEvent(event, error) {
116
- this.sendTelemetryEventCore(Object.assign(Object.assign({
136
+ this.sendTelemetryEventCore({
117
137
  // ensure the error field has some value,
118
138
  // this can and will be overridden by event, or error
119
- error: event.eventName }, event), { category: "error" }), error);
139
+ error: event.eventName,
140
+ ...event,
141
+ category: "error",
142
+ }, error, LogLevel.error);
120
143
  }
121
144
  /**
122
145
  * Send a performance telemetry event with the logger
123
146
  *
124
147
  * @param event - Event to send
125
148
  * @param error - optional error object to log
149
+ * @param logLevel - optional level of the log. It category of event is set as error,
150
+ * then the logLevel will be upgraded to be an error.
126
151
  */
127
- sendPerformanceEvent(event, error) {
128
- var _a;
129
- const perfEvent = Object.assign(Object.assign({}, event), { category: (_a = event.category) !== null && _a !== void 0 ? _a : "performance" });
130
- this.sendTelemetryEventCore(perfEvent, error);
152
+ sendPerformanceEvent(event, error, logLevel = LogLevel.default) {
153
+ const perfEvent = {
154
+ ...event,
155
+ category: event.category ?? "performance",
156
+ };
157
+ this.sendTelemetryEventCore(perfEvent, error, perfEvent.category === "error" ? LogLevel.error : logLevel);
131
158
  }
132
159
  prepareEvent(event) {
133
160
  const includeErrorProps = event.category === "error" || event.error !== undefined;
134
- const newEvent = Object.assign({}, event);
161
+ const newEvent = {
162
+ ...event,
163
+ };
135
164
  if (this.namespace !== undefined) {
136
165
  newEvent.eventName = `${this.namespace}${TelemetryLogger.eventNamespaceSeparator}${newEvent.eventName}`;
137
166
  }
167
+ return this.extendProperties(newEvent, includeErrorProps);
168
+ }
169
+ extendProperties(toExtend, includeErrorProps) {
170
+ const eventLike = toExtend;
138
171
  if (this.properties) {
139
172
  const properties = [];
140
173
  properties.push(this.properties.all);
@@ -144,32 +177,40 @@ export class TelemetryLogger {
144
177
  for (const props of properties) {
145
178
  if (props !== undefined) {
146
179
  for (const key of Object.keys(props)) {
147
- if (event[key] !== undefined) {
180
+ if (eventLike[key] !== undefined) {
148
181
  continue;
149
182
  }
150
183
  const getterOrValue = props[key];
151
184
  // If this throws, hopefully it is handled elsewhere
152
185
  const value = typeof getterOrValue === "function" ? getterOrValue() : getterOrValue;
153
186
  if (value !== undefined) {
154
- newEvent[key] = value;
187
+ eventLike[key] = value;
155
188
  }
156
189
  }
157
190
  }
158
191
  }
159
192
  }
160
- return newEvent;
193
+ return toExtend;
161
194
  }
162
195
  }
163
- TelemetryLogger.eventNamespaceSeparator = ":";
196
+ /**
197
+ * {@inheritDoc eventNamespaceSeparator}
198
+ */
199
+ TelemetryLogger.eventNamespaceSeparator = eventNamespaceSeparator;
164
200
  /**
165
201
  * @deprecated 0.56, remove TaggedLoggerAdapter once its usage is removed from
166
202
  * container-runtime. Issue: #8191
167
203
  * TaggedLoggerAdapter class can add tag handling to your logger.
204
+ *
205
+ * @internal
168
206
  */
169
207
  export class TaggedLoggerAdapter {
170
208
  constructor(logger) {
171
209
  this.logger = logger;
172
210
  }
211
+ /**
212
+ * {@inheritDoc @fluidframework/core-interfaces#ITelemetryBaseLogger.send}
213
+ */
173
214
  send(eventWithTagsMaybe) {
174
215
  const newEvent = {
175
216
  category: eventWithTagsMaybe.category,
@@ -177,55 +218,64 @@ export class TaggedLoggerAdapter {
177
218
  };
178
219
  for (const key of Object.keys(eventWithTagsMaybe)) {
179
220
  const taggableProp = eventWithTagsMaybe[key];
180
- const { value, tag } = (typeof taggableProp === "object")
221
+ const { value, tag } = typeof taggableProp === "object"
181
222
  ? taggableProp
182
223
  : { value: taggableProp, tag: undefined };
183
224
  switch (tag) {
184
- case undefined:
225
+ case undefined: {
185
226
  // No tag means we can log plainly
186
227
  newEvent[key] = value;
187
228
  break;
188
- case TelemetryDataTag.PackageData:
189
- // For Microsoft applications, PackageData is safe for now
229
+ }
230
+ case "PackageData": // For back-compat
231
+ case TelemetryDataTag.CodeArtifact: {
232
+ // For Microsoft applications, CodeArtifact is safe for now
190
233
  // (we don't load 3P code in 1P apps)
191
234
  newEvent[key] = value;
192
235
  break;
193
- case TelemetryDataTag.UserData:
194
- // Strip out anything tagged explicitly as PII.
236
+ }
237
+ case TelemetryDataTag.UserData: {
238
+ // Strip out anything tagged explicitly as UserData.
195
239
  // Alternate strategy would be to hash these props
196
240
  newEvent[key] = "REDACTED (UserData)";
197
241
  break;
198
- default:
242
+ }
243
+ default: {
199
244
  // If we encounter a tag we don't recognize
200
245
  // then we must assume we should scrub.
201
246
  newEvent[key] = "REDACTED (unknown tag)";
202
247
  break;
248
+ }
203
249
  }
204
250
  }
205
251
  this.logger.send(newEvent);
206
252
  }
207
253
  }
254
+ /**
255
+ * Create a child logger based on the provided props object.
256
+ *
257
+ * @remarks
258
+ * Passing in no props object (i.e. undefined) will return a logger that is effectively a no-op.
259
+ *
260
+ * @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.
261
+ *
262
+ * @alpha
263
+ */
264
+ export function createChildLogger(props) {
265
+ return ChildLogger.create(props?.logger, props?.namespace, props?.properties);
266
+ }
208
267
  /**
209
268
  * ChildLogger class contains various helper telemetry methods,
210
269
  * encoding in one place schemas for various types of Fluid telemetry events.
211
- * Creates sub-logger that appends properties to all events
270
+ * Creates sub-logger that appends properties to all events.
212
271
  */
213
272
  export class ChildLogger extends TelemetryLogger {
214
- constructor(baseLogger, namespace, properties) {
215
- super(namespace, properties);
216
- this.baseLogger = baseLogger;
217
- // propagate the monitoring context
218
- if (loggerIsMonitoringContext(baseLogger)) {
219
- mixinMonitoringContext(this, new CachedConfigProvider(baseLogger.config));
220
- }
221
- }
222
273
  /**
223
274
  * Create child logger
224
275
  * @param baseLogger - Base logger to use to output events. If undefined, proper child logger
225
- * is created, but it does not sends telemetry events anywhere.
276
+ * is created, but it does not send telemetry events anywhere.
226
277
  * @param namespace - Telemetry event name prefix to add to all events
227
278
  * @param properties - Base properties to add to all events
228
- * @param propertyGetters - Getters to add additional properties to all events
229
279
  */
230
280
  static create(baseLogger, namespace, properties) {
231
281
  // if we are creating a child of a child, rather than nest, which will increase
@@ -235,10 +285,16 @@ export class ChildLogger extends TelemetryLogger {
235
285
  for (const extendedProps of [baseLogger.properties, properties]) {
236
286
  if (extendedProps !== undefined) {
237
287
  if (extendedProps.all !== undefined) {
238
- combinedProperties.all = Object.assign(Object.assign({}, combinedProperties.all), extendedProps.all);
288
+ combinedProperties.all = {
289
+ ...combinedProperties.all,
290
+ ...extendedProps.all,
291
+ };
239
292
  }
240
293
  if (extendedProps.error !== undefined) {
241
- combinedProperties.error = Object.assign(Object.assign({}, combinedProperties.error), extendedProps.error);
294
+ combinedProperties.error = {
295
+ ...combinedProperties.error,
296
+ ...extendedProps.error,
297
+ };
242
298
  }
243
299
  }
244
300
  }
@@ -247,34 +303,95 @@ export class ChildLogger extends TelemetryLogger {
247
303
  : namespace === undefined
248
304
  ? baseLogger.namespace
249
305
  : `${baseLogger.namespace}${TelemetryLogger.eventNamespaceSeparator}${namespace}`;
250
- return new ChildLogger(baseLogger.baseLogger, combinedNamespace, combinedProperties);
306
+ const child = new ChildLogger(baseLogger.baseLogger, combinedNamespace, combinedProperties);
307
+ if (!loggerIsMonitoringContext(child) && loggerIsMonitoringContext(baseLogger)) {
308
+ mixinMonitoringContext(child, baseLogger.config);
309
+ }
310
+ return child;
311
+ }
312
+ return new ChildLogger(baseLogger ?? { send() { } }, namespace, properties);
313
+ }
314
+ constructor(baseLogger, namespace, properties) {
315
+ super(namespace, properties);
316
+ this.baseLogger = baseLogger;
317
+ // propagate the monitoring context
318
+ if (loggerIsMonitoringContext(baseLogger)) {
319
+ mixinMonitoringContext(this, new CachedConfigProvider(this, baseLogger.config));
251
320
  }
252
- return new ChildLogger(baseLogger ? baseLogger : new BaseTelemetryNullLogger(), namespace, properties);
321
+ }
322
+ get minLogLevel() {
323
+ return this.baseLogger.minLogLevel;
324
+ }
325
+ shouldFilterOutEvent(event, logLevel) {
326
+ const eventLogLevel = logLevel ?? LogLevel.default;
327
+ const configLogLevel = this.baseLogger.minLogLevel ?? LogLevel.default;
328
+ // Filter out in case event log level is below what is wanted in config.
329
+ return eventLogLevel < configLogLevel;
253
330
  }
254
331
  /**
255
332
  * Send an event with the logger
256
333
  *
257
334
  * @param event - the event to send
258
335
  */
259
- send(event) {
260
- this.baseLogger.send(this.prepareEvent(event));
336
+ send(event, logLevel) {
337
+ if (this.shouldFilterOutEvent(event, logLevel)) {
338
+ return;
339
+ }
340
+ this.baseLogger.send(this.prepareEvent(event), logLevel);
261
341
  }
262
342
  }
343
+ /**
344
+ * Create a logger which logs to multiple other loggers based on the provided props object.
345
+ *
346
+ * @internal
347
+ */
348
+ export function createMultiSinkLogger(props) {
349
+ return new MultiSinkLogger(props.namespace, props.properties, props.loggers?.filter((l) => l !== undefined), props.tryInheritProperties);
350
+ }
263
351
  /**
264
352
  * Multi-sink logger
265
353
  * Takes multiple ITelemetryBaseLogger objects (sinks) and logs all events into each sink
266
- * Implements ITelemetryBaseLogger (through static create() method)
267
354
  */
268
355
  export class MultiSinkLogger extends TelemetryLogger {
269
356
  /**
270
357
  * Create multiple sink logger (i.e. logger that sends events to multiple sinks)
271
358
  * @param namespace - Telemetry event name prefix to add to all events
272
359
  * @param properties - Base properties to add to all events
273
- * @param propertyGetters - Getters to add additional properties to all events
360
+ * @param loggers - The list of loggers to use as sinks
361
+ * @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
274
362
  */
275
- constructor(namespace, properties) {
276
- super(namespace, properties);
277
- this.loggers = [];
363
+ constructor(namespace, properties, loggers = [], tryInheritProperties) {
364
+ let realProperties = properties === undefined ? undefined : { ...properties };
365
+ if (tryInheritProperties === true) {
366
+ const merge = (realProperties ?? (realProperties = {}));
367
+ loggers
368
+ .filter((l) => l instanceof TelemetryLogger)
369
+ .map((l) => l.properties ?? {})
370
+ // eslint-disable-next-line unicorn/no-array-for-each
371
+ .forEach((cv) => {
372
+ // eslint-disable-next-line unicorn/no-array-for-each
373
+ Object.keys(cv).forEach((k) => {
374
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
375
+ merge[k] = { ...cv[k], ...merge?.[k] };
376
+ });
377
+ });
378
+ }
379
+ super(namespace, realProperties);
380
+ this.loggers = loggers;
381
+ this._minLogLevelOfAllLoggers = LogLevel.default;
382
+ this.calculateMinLogLevel();
383
+ }
384
+ get minLogLevel() {
385
+ return this._minLogLevelOfAllLoggers;
386
+ }
387
+ calculateMinLogLevel() {
388
+ if (this.loggers.length > 0) {
389
+ const logLevels = [];
390
+ for (const logger of this.loggers) {
391
+ logLevels.push(logger.minLogLevel ?? LogLevel.default);
392
+ }
393
+ this._minLogLevelOfAllLoggers = Math.min(...logLevels);
394
+ }
278
395
  }
279
396
  /**
280
397
  * Add logger to send all events to
@@ -283,6 +400,8 @@ export class MultiSinkLogger extends TelemetryLogger {
283
400
  addLogger(logger) {
284
401
  if (logger !== undefined && logger !== null) {
285
402
  this.loggers.push(logger);
403
+ // Update in case the logLevel of added logger is less than the current.
404
+ this.calculateMinLogLevel();
286
405
  }
287
406
  }
288
407
  /**
@@ -292,33 +411,48 @@ export class MultiSinkLogger extends TelemetryLogger {
292
411
  */
293
412
  send(event) {
294
413
  const newEvent = this.prepareEvent(event);
295
- this.loggers.forEach((logger) => {
414
+ for (const logger of this.loggers) {
296
415
  logger.send(newEvent);
297
- });
416
+ }
298
417
  }
299
418
  }
300
419
  /**
301
- * Helper class to log performance events
420
+ * Helper class to log performance events.
421
+ *
422
+ * @internal
302
423
  */
303
424
  export class PerformanceEvent {
304
- constructor(logger, event, markers = { end: true, cancel: "generic" }) {
305
- this.logger = logger;
306
- this.markers = markers;
307
- this.startTime = performance.now();
308
- this.event = Object.assign({}, event);
309
- if (this.markers.start) {
310
- this.reportEvent("start");
311
- }
312
- if (typeof window === "object" && window != null && window.performance) {
313
- this.startMark = `${event.eventName}-start`;
314
- window.performance.mark(this.startMark);
315
- }
316
- }
317
- static start(logger, event, markers) {
318
- return new PerformanceEvent(logger, event, markers);
425
+ /**
426
+ * Creates an instance of {@link PerformanceEvent} and starts measurements
427
+ * @param logger - the logger to be used for publishing events
428
+ * @param event - the logging event details which will be published with the performance measurements
429
+ * @param markers - See {@link IPerformanceEventMarkers}
430
+ * @param recordHeapSize - whether or not to also record memory performance
431
+ * @param emitLogs - should this instance emit logs. If set to false, logs will not be emitted to the logger,
432
+ * but measurements will still be performed and any specified markers will be generated.
433
+ * @returns An instance of {@link PerformanceEvent}
434
+ */
435
+ static start(logger, event, markers, recordHeapSize = false, emitLogs = true) {
436
+ return new PerformanceEvent(logger, event, markers, recordHeapSize, emitLogs);
319
437
  }
320
- static timedExec(logger, event, callback, markers) {
321
- const perfEvent = PerformanceEvent.start(logger, event, markers);
438
+ /**
439
+ * Measure a synchronous task
440
+ * @param logger - the logger to be used for publishing events
441
+ * @param event - the logging event details which will be published with the performance measurements
442
+ * @param callback - the task to be executed and measured
443
+ * @param markers - See {@link IPerformanceEventMarkers}
444
+ * @param sampleThreshold - events with the same name and category will be sent to the logger
445
+ * only when we hit this many executions of the task. If unspecified, all events will be sent.
446
+ * @returns The results of the executed task
447
+ *
448
+ * @remarks Note that if the "same" event (category + eventName) would be emitted by different
449
+ * tasks (`callback`), `sampleThreshold` is still applied only based on the event's category + eventName,
450
+ * so executing either of the tasks will increase the internal counter and they
451
+ * effectively "share" the sampling rate for the event.
452
+ */
453
+ static timedExec(logger, event, callback, markers, sampleThreshold = 1) {
454
+ const perfEvent = PerformanceEvent.start(logger, event, markers, undefined, // recordHeapSize
455
+ PerformanceEvent.shouldReport(event, sampleThreshold));
322
456
  try {
323
457
  const ret = callback(perfEvent);
324
458
  perfEvent.autoEnd();
@@ -329,8 +463,24 @@ export class PerformanceEvent {
329
463
  throw error;
330
464
  }
331
465
  }
332
- static async timedExecAsync(logger, event, callback, markers) {
333
- const perfEvent = PerformanceEvent.start(logger, event, markers);
466
+ /**
467
+ * Measure an asynchronous task
468
+ * @param logger - the logger to be used for publishing events
469
+ * @param event - the logging event details which will be published with the performance measurements
470
+ * @param callback - the task to be executed and measured
471
+ * @param markers - See {@link IPerformanceEventMarkers}
472
+ * @param recordHeapSize - whether or not to also record memory performance
473
+ * @param sampleThreshold - events with the same name and category will be sent to the logger
474
+ * only when we hit this many executions of the task. If unspecified, all events will be sent.
475
+ * @returns The results of the executed task
476
+ *
477
+ * @remarks Note that if the "same" event (category + eventName) would be emitted by different
478
+ * tasks (`callback`), `sampleThreshold` is still applied only based on the event's category + eventName,
479
+ * so executing either of the tasks will increase the internal counter and they
480
+ * effectively "share" the sampling rate for the event.
481
+ */
482
+ static async timedExecAsync(logger, event, callback, markers, recordHeapSize, sampleThreshold = 1) {
483
+ const perfEvent = PerformanceEvent.start(logger, event, markers, recordHeapSize, PerformanceEvent.shouldReport(event, sampleThreshold));
334
484
  try {
335
485
  const ret = await callback(perfEvent);
336
486
  perfEvent.autoEnd();
@@ -341,7 +491,25 @@ export class PerformanceEvent {
341
491
  throw error;
342
492
  }
343
493
  }
344
- get duration() { return performance.now() - this.startTime; }
494
+ get duration() {
495
+ return performance.now() - this.startTime;
496
+ }
497
+ constructor(logger, event, markers = { end: true, cancel: "generic" }, recordHeapSize = false, emitLogs = true) {
498
+ this.logger = logger;
499
+ this.markers = markers;
500
+ this.recordHeapSize = recordHeapSize;
501
+ this.emitLogs = emitLogs;
502
+ this.startTime = performance.now();
503
+ this.startMemoryCollection = 0;
504
+ this.event = { ...event };
505
+ if (this.markers.start) {
506
+ this.reportEvent("start");
507
+ }
508
+ if (typeof window === "object" && window?.performance?.mark) {
509
+ this.startMark = `${event.eventName}-start`;
510
+ window.performance.mark(this.startMark);
511
+ }
512
+ }
345
513
  reportProgress(props, eventNameSuffix = "update") {
346
514
  this.reportEvent(eventNameSuffix, props);
347
515
  }
@@ -368,7 +536,7 @@ export class PerformanceEvent {
368
536
  }
369
537
  cancel(props, error) {
370
538
  if (this.markers.cancel !== undefined) {
371
- this.reportEvent("cancel", Object.assign({ category: this.markers.cancel }, props), error);
539
+ this.reportEvent("cancel", { category: this.markers.cancel, ...props }, error);
372
540
  }
373
541
  this.event = undefined;
374
542
  }
@@ -382,48 +550,164 @@ export class PerformanceEvent {
382
550
  if (!this.event) {
383
551
  return;
384
552
  }
385
- const event = Object.assign(Object.assign({}, this.event), props);
553
+ if (!this.emitLogs) {
554
+ return;
555
+ }
556
+ const event = { ...this.event, ...props };
386
557
  event.eventName = `${event.eventName}_${eventNameSuffix}`;
387
558
  if (eventNameSuffix !== "start") {
388
559
  event.duration = this.duration;
560
+ if (this.startMemoryCollection) {
561
+ const currentMemory = performance?.memory
562
+ ?.usedJSHeapSize;
563
+ const differenceInKBytes = Math.floor((currentMemory - this.startMemoryCollection) / 1024);
564
+ if (differenceInKBytes > 0) {
565
+ event.usedJSHeapSize = differenceInKBytes;
566
+ }
567
+ }
568
+ }
569
+ else if (this.recordHeapSize) {
570
+ this.startMemoryCollection = performance?.memory
571
+ ?.usedJSHeapSize;
389
572
  }
390
573
  this.logger.sendPerformanceEvent(event, error);
391
574
  }
575
+ static shouldReport(event, sampleThreshold) {
576
+ const eventKey = `.${event.category}.${event.eventName}`;
577
+ const hitCount = PerformanceEvent.eventHits.get(eventKey) ?? 0;
578
+ PerformanceEvent.eventHits.set(eventKey, hitCount >= sampleThreshold ? 1 : hitCount + 1);
579
+ return hitCount % sampleThreshold === 0;
580
+ }
392
581
  }
582
+ PerformanceEvent.eventHits = new Map();
393
583
  /**
394
- * Logger that is useful for UT
395
- * It can be used in places where logger instance is required, but events should be not send over.
584
+ * Null logger that no-ops for all telemetry events passed to it.
585
+ *
586
+ * @deprecated This will be removed in a future release.
587
+ * For internal use within the FluidFramework codebase, use {@link createChildLogger} with no arguments instead.
588
+ * For external consumers we recommend writing a trivial implementation of {@link @fluidframework/core-interfaces#ITelemetryBaseLogger}
589
+ * where the send() method does nothing and using that.
590
+ *
591
+ * @internal
396
592
  */
397
- export class TelemetryUTLogger {
398
- send(event) {
399
- }
400
- sendTelemetryEvent(event, error) {
401
- }
402
- sendErrorEvent(event, error) {
403
- this.reportError("errorEvent in UT logger!", event, error);
404
- }
405
- sendPerformanceEvent(event, error) {
406
- }
407
- logGenericError(eventName, error) {
408
- this.reportError(`genericError in UT logger!`, { eventName }, error);
409
- }
410
- logException(event, exception) {
411
- this.reportError("exception in UT logger!", event, exception);
593
+ export class TelemetryNullLogger {
594
+ send(event) { }
595
+ sendTelemetryEvent(event, error) { }
596
+ sendErrorEvent(event, error) { }
597
+ sendPerformanceEvent(event, error) { }
598
+ }
599
+ /**
600
+ * Takes in an event object, and converts all of its values to a basePropertyType.
601
+ * In the case of an invalid property type, the value will be converted to an error string.
602
+ * @param event - Event with fields you want to stringify.
603
+ */
604
+ function convertToBaseEvent({ category, eventName, ...props }) {
605
+ const newEvent = { category, eventName };
606
+ for (const key of Object.keys(props)) {
607
+ newEvent[key] = convertToBasePropertyType(props[key]);
412
608
  }
413
- debugAssert(condition, event) {
414
- this.reportError("debugAssert in UT logger!");
609
+ return newEvent;
610
+ }
611
+ /**
612
+ * Takes in value, and does one of 4 things.
613
+ * if value is of primitive type - returns the original value.
614
+ * If the value is a flat array or object - returns a stringified version of the array/object.
615
+ * If the value is an object of type Tagged<TelemetryEventPropertyType> - returns the object
616
+ * with its values recursively converted to base property Type.
617
+ * If none of these cases are reached - returns an error string
618
+ * @param x - value passed in to convert to a base property type
619
+ */
620
+ export function convertToBasePropertyType(x) {
621
+ return isTaggedTelemetryPropertyValue(x)
622
+ ? {
623
+ value: convertToBasePropertyTypeUntagged(x.value),
624
+ tag: x.tag,
625
+ }
626
+ : convertToBasePropertyTypeUntagged(x);
627
+ }
628
+ function convertToBasePropertyTypeUntagged(x) {
629
+ switch (typeof x) {
630
+ case "string":
631
+ case "number":
632
+ case "boolean":
633
+ case "undefined": {
634
+ return x;
635
+ }
636
+ case "object": {
637
+ // We assume this is an array or flat object based on the input types
638
+ return JSON.stringify(x);
639
+ }
640
+ default: {
641
+ // should never reach this case based on the input types
642
+ console.error(`convertToBasePropertyTypeUntagged: INVALID PROPERTY (typed as ${typeof x})`);
643
+ return `INVALID PROPERTY (typed as ${typeof x})`;
644
+ }
415
645
  }
416
- shipAssert(condition, event) {
417
- this.reportError("shipAssert in UT logger!");
646
+ }
647
+ /**
648
+ * Tags all given `values` with the same `tag`.
649
+ *
650
+ * @param tag - The tag with which all `values` will be annotated.
651
+ * @param values - The values to be tagged.
652
+ *
653
+ * @remarks
654
+ * It supports properties of type {@link @fluidframework/core-interfaces#TelemetryBaseEventPropertyType},
655
+ * as well as callbacks that return that type.
656
+ *
657
+ * @example Sample usage
658
+ * ```typescript
659
+ * {
660
+ * // ...Other properties being added to a telemetry event
661
+ * ...tagData("someTag", {foo: 1, bar: 2}),
662
+ * // ...
663
+ * }
664
+ * ```
665
+ * This will result in `foo` and `bar` added to the event with their values tagged.
666
+ *
667
+ * @internal
668
+ */
669
+ export const tagData = (tag, values) =>
670
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
671
+ Object.entries(values)
672
+ .filter((e) => e[1] !== undefined)
673
+ // eslint-disable-next-line unicorn/no-array-reduce, unicorn/prefer-object-from-entries
674
+ .reduce((pv, cv) => {
675
+ const [key, value] = cv;
676
+ // The ternary form is less legible in this case.
677
+ // eslint-disable-next-line unicorn/prefer-ternary
678
+ if (typeof value === "function") {
679
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
680
+ pv[key] = () => {
681
+ return { tag, value: value() };
682
+ };
418
683
  }
419
- reportError(message, event, err) {
420
- const error = new Error(message);
421
- error.error = error;
422
- error.event = event;
423
- // report to console as exception can be eaten
424
- console.error(message);
425
- console.error(error);
426
- throw error;
684
+ else {
685
+ pv[key] = { tag, value };
427
686
  }
428
- }
687
+ return pv;
688
+ }, {});
689
+ /**
690
+ * Tags all provided `values` as {@link TelemetryDataTag.CodeArtifact}.
691
+ *
692
+ * @param values - The values to be tagged.
693
+ *
694
+ * @remarks
695
+ * It supports properties of type {@link @fluidframework/core-interfaces#TelemetryBaseEventPropertyType},
696
+ * as well as callbacks that return that type.
697
+ *
698
+ * @example Sample usage
699
+ * ```typescript
700
+ * {
701
+ * // ...Other properties being added to a telemetry event
702
+ * ...tagCodeArtifacts("someTag", {foo: 1, bar: 2}),
703
+ * // ...
704
+ * }
705
+ * ```
706
+ * This will result in `foo` and `bar` added to the event with their values tagged as {@link TelemetryDataTag.CodeArtifact}.
707
+ *
708
+ * @see {@link tagData}
709
+ *
710
+ * @internal
711
+ */
712
+ export const tagCodeArtifacts = (values) => tagData(TelemetryDataTag.CodeArtifact, values);
429
713
  //# sourceMappingURL=logger.js.map