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