@crimson-education/browser-logger 2.0.2 → 3.0.0

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 (71) hide show
  1. package/README.md +212 -67
  2. package/lib/index.d.ts +6 -76
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +42 -186
  5. package/lib/index.js.map +1 -1
  6. package/lib/logger/consoleTransport.d.ts +37 -0
  7. package/lib/logger/consoleTransport.d.ts.map +1 -0
  8. package/lib/logger/consoleTransport.js +81 -0
  9. package/lib/logger/consoleTransport.js.map +1 -0
  10. package/lib/logger/datadogTransport.d.ts +8 -0
  11. package/lib/logger/datadogTransport.d.ts.map +1 -0
  12. package/lib/logger/datadogTransport.js +21 -0
  13. package/lib/logger/datadogTransport.js.map +1 -0
  14. package/lib/logger/index.d.ts +16 -0
  15. package/lib/logger/index.d.ts.map +1 -0
  16. package/lib/logger/index.js +148 -0
  17. package/lib/logger/index.js.map +1 -0
  18. package/lib/logger/index.test.d.ts +2 -0
  19. package/lib/logger/index.test.d.ts.map +1 -0
  20. package/lib/logger/index.test.js +60 -0
  21. package/lib/logger/index.test.js.map +1 -0
  22. package/lib/logger/utils.d.ts +15 -0
  23. package/lib/logger/utils.d.ts.map +1 -0
  24. package/lib/logger/utils.js +32 -0
  25. package/lib/logger/utils.js.map +1 -0
  26. package/lib/reporters/amplifyReporter.d.ts +2 -2
  27. package/lib/reporters/amplifyReporter.d.ts.map +1 -1
  28. package/lib/reporters/amplifyReporter.js +13 -14
  29. package/lib/reporters/amplifyReporter.js.map +1 -1
  30. package/lib/reporters/datadogReporter.d.ts +7 -5
  31. package/lib/reporters/datadogReporter.d.ts.map +1 -1
  32. package/lib/reporters/datadogReporter.js +37 -100
  33. package/lib/reporters/datadogReporter.js.map +1 -1
  34. package/lib/reporters/gtmReporter.d.ts.map +1 -1
  35. package/lib/reporters/gtmReporter.js +2 -7
  36. package/lib/reporters/gtmReporter.js.map +1 -1
  37. package/lib/reporters/index.d.ts +66 -3
  38. package/lib/reporters/index.d.ts.map +1 -1
  39. package/lib/reporters/index.js +210 -17
  40. package/lib/reporters/index.js.map +1 -1
  41. package/lib/reporters/logReporter.d.ts +35 -0
  42. package/lib/reporters/logReporter.d.ts.map +1 -0
  43. package/lib/reporters/logReporter.js +62 -0
  44. package/lib/reporters/logReporter.js.map +1 -0
  45. package/lib/types/logger.d.ts +43 -2
  46. package/lib/types/logger.d.ts.map +1 -1
  47. package/lib/types/logger.js.map +1 -1
  48. package/lib/types/reporter.d.ts +58 -6
  49. package/lib/types/reporter.d.ts.map +1 -1
  50. package/lib/utils.d.ts +9 -8
  51. package/lib/utils.d.ts.map +1 -1
  52. package/lib/utils.js +28 -48
  53. package/lib/utils.js.map +1 -1
  54. package/lib/utils.test.js +18 -5
  55. package/lib/utils.test.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/index.ts +38 -208
  58. package/src/logger/consoleTransport.ts +101 -0
  59. package/src/logger/datadogTransport.ts +20 -0
  60. package/src/logger/index.test.ts +68 -0
  61. package/src/logger/index.ts +139 -0
  62. package/src/logger/utils.ts +28 -0
  63. package/src/reporters/amplifyReporter.ts +15 -16
  64. package/src/reporters/datadogReporter.ts +48 -116
  65. package/src/reporters/gtmReporter.ts +2 -7
  66. package/src/reporters/index.ts +216 -3
  67. package/src/reporters/logReporter.ts +86 -0
  68. package/src/types/logger.ts +49 -4
  69. package/src/types/reporter.ts +66 -6
  70. package/src/utils.test.ts +20 -6
  71. package/src/utils.ts +37 -62
@@ -9,7 +9,10 @@ import {
9
9
  } from '../types';
10
10
  import { Auth } from '@aws-amplify/auth';
11
11
  import { Analytics } from '@aws-amplify/analytics';
12
- import { AttributeMap, filterAttributeMap } from '../utils';
12
+ import { logger } from '../logger';
13
+
14
+ /* eslint-disable @typescript-eslint/no-unused-vars */
15
+ type AttributeMap = Record<string, string[] | string | null>;
13
16
 
14
17
  export interface AmplifyReporterConfig extends ReporterConfigBase {
15
18
  /**
@@ -133,16 +136,11 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
133
136
  ...event.tags,
134
137
  },
135
138
  false,
136
- config.ignoreMetadataPatterns,
137
139
  ) as Record<string, string>,
138
140
  metrics: event.metrics,
139
141
  });
140
142
  },
141
143
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
142
- if (breadcrumb.category && config.ignoreBreadcrumbCategories?.includes(breadcrumb.category)) {
143
- return;
144
- }
145
-
146
144
  reporter.trackEvent({
147
145
  message: breadcrumb.message,
148
146
  metadata: {
@@ -152,7 +150,7 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
152
150
  });
153
151
  },
154
152
  addMetadata: function (metadata: Metadata): void {
155
- Object.assign(allMetadata, asAttributeMap(metadata, true, config.ignoreMetadataPatterns));
153
+ Object.assign(allMetadata, asAttributeMap(metadata, true));
156
154
  Analytics.updateEndpoint({
157
155
  attributes: allMetadata,
158
156
  }).catch(() => {
@@ -188,16 +186,10 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
188
186
  /**
189
187
  * Pinpoint has strict attribute name and value length limits
190
188
  */
191
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
- export function asAttributeMap(
193
- values: Record<string, unknown>,
194
- groupValues = true,
195
- ignorePatterns: (string | RegExp)[] = [],
196
- ): AttributeMap {
189
+ export function asAttributeMap(values: Record<string, unknown>, groupValues = true): AttributeMap {
197
190
  const attributeMap = buildAttributeMap(values, undefined, groupValues);
198
- const filteredAttributeMap = filterAttributeMap(attributeMap, ignorePatterns);
199
191
 
200
- const checkedEntries = Object.entries(filteredAttributeMap).map(([key, value]) => {
192
+ const checkedEntries = Object.entries(attributeMap).map(([key, value]) => {
201
193
  const truncatedKey = key.length > 50 ? `___${key.slice(-47)}` : key;
202
194
  const truncatedValue = Array.isArray(value)
203
195
  ? value?.map((val) => val.slice(0, 100)) ?? null
@@ -206,6 +198,14 @@ export function asAttributeMap(
206
198
  return [truncatedKey, truncatedValue];
207
199
  });
208
200
 
201
+ // Pinpoint only accepts 40 attributes
202
+ if (checkedEntries.length > 40) {
203
+ logger.error(`Amplify only allows 40 attributes per event, truncating to 40 attributes`, {
204
+ attributes: checkedEntries,
205
+ });
206
+ checkedEntries.length = 40;
207
+ }
208
+
209
209
  return Object.fromEntries(checkedEntries);
210
210
  }
211
211
 
@@ -215,7 +215,6 @@ export function asAttributeMap(
215
215
  * all of its values are of type `string[]` to appease Pinpoint.
216
216
  */
217
217
  export function buildAttributeMap(
218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
218
  values: Record<string, any>,
220
219
  parentKey: string | undefined = undefined,
221
220
  groupValues = true,
@@ -1,19 +1,17 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import {
3
2
  IReporter,
4
3
  ReporterBreadcrumb,
5
4
  ReporterEvent,
6
5
  Metadata,
7
- Profiler,
8
6
  ReportError,
9
7
  ReportUser,
10
8
  ServiceInfo,
11
9
  ReporterConfigBase,
12
10
  } from '../types';
13
- import { datadogLogs, HandlerType, StatusType } from '@datadog/browser-logs';
11
+ import { datadogLogs, HandlerType } from '@datadog/browser-logs';
14
12
  import { datadogRum, DefaultPrivacyLevel } from '@datadog/browser-rum';
15
- import { ILogger, LogLevel } from '..';
16
- import { filterAttributeMap } from '../utils';
13
+ import { logTransports } from '../logger';
14
+ import { DatadogLogTransportConfig, datadogTransport } from '../logger/datadogTransport';
17
15
 
18
16
  export interface DatadogReporterConfig extends ReporterConfigBase {
19
17
  /** The RUM application ID. */
@@ -75,6 +73,12 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
75
73
  */
76
74
  actionNameAttribute?: string;
77
75
 
76
+ /**
77
+ * Toggles/configures the Datadog Log Transport.
78
+ * Defaults to true.
79
+ */
80
+ logTransport?: boolean | DatadogLogTransportConfig;
81
+
78
82
  /**
79
83
  * By enabling Session Replay, you can automatically mask sensitive elements from being recorded through the RUM Browser SDK.
80
84
  *
@@ -87,42 +91,39 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
87
91
  allowedTracingOrigins?: (string | RegExp)[];
88
92
  }
89
93
 
90
- function getDatadogStatusType(level: LogLevel): StatusType {
91
- switch (level) {
92
- case LogLevel.Error:
93
- return StatusType.error;
94
-
95
- case LogLevel.Warn:
96
- return StatusType.warn;
97
-
98
- case LogLevel.Info:
99
- return StatusType.info;
100
-
101
- case LogLevel.Debug:
102
- default:
103
- return StatusType.debug;
104
- }
105
- }
106
-
107
94
  export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig): IReporter {
108
95
  const isLocalhost = window.location.hostname === 'localhost';
109
96
 
110
- datadogLogs.init({
111
- site: config.site,
112
- proxyUrl: config.proxyUrl,
113
- clientToken: config.clientToken,
114
- service: info.service,
115
- env: info.environment,
116
- version: config.version ?? info.version,
117
-
118
- sampleRate: config.sampleRate ?? 100,
119
-
120
- useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
121
- useCrossSiteSessionCookie: config.useCrossSiteSessionCookie ?? false,
122
- trackSessionAcrossSubdomains: config.trackSessionAcrossSubdomains,
97
+ // Don't forward error logs by default, do enable the log transport by default
98
+ // forwardErrorsToLogs incorrectly is called forwardConsoleLogs, this is for backwards compatibility
99
+ const forwardErrorsToLogs = config.forwardConsoleLogs ?? false;
100
+ const enableLogTransport = config.logTransport !== false;
101
+
102
+ // Only init datadog logs if something is using it.
103
+ if (forwardErrorsToLogs !== true && enableLogTransport !== true) {
104
+ datadogLogs.init({
105
+ site: config.site,
106
+ proxyUrl: config.proxyUrl,
107
+ clientToken: config.clientToken,
108
+ service: info.service,
109
+ env: info.environment,
110
+ version: config.version ?? info.version,
111
+
112
+ sampleRate: config.sampleRate ?? 100,
113
+
114
+ useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
115
+ useCrossSiteSessionCookie: config.useCrossSiteSessionCookie ?? false,
116
+ trackSessionAcrossSubdomains: config.trackSessionAcrossSubdomains,
117
+
118
+ forwardErrorsToLogs,
119
+ });
120
+ datadogLogs.logger.setHandler(HandlerType.http);
121
+ }
123
122
 
124
- forwardErrorsToLogs: config.forwardConsoleLogs ?? false,
125
- });
123
+ // Add the datadog log transport
124
+ if (enableLogTransport) {
125
+ logTransports.push(datadogTransport(typeof config.logTransport === 'boolean' ? {} : config.logTransport));
126
+ }
126
127
 
127
128
  datadogRum.init({
128
129
  site: config.site,
@@ -149,39 +150,33 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
149
150
  allowedTracingOrigins: config.allowedTracingOrigins,
150
151
  });
151
152
 
152
- const ignoreMetadataPatterns = config.ignoreMetadataPatterns ?? [];
153
-
154
153
  const reporter: IReporter = {
155
154
  trackEvent: function (event: ReporterEvent): void {
156
- const context = {
155
+ datadogRum.addAction(event.message, {
157
156
  level: event.level,
158
- ...filterAttributeMap(event.metadata ?? {}, ignoreMetadataPatterns),
157
+ ...event.metadata,
159
158
  ...event.tags,
160
159
  ...event.metrics,
161
- };
162
- datadogRum.addAction(event.message, context);
163
- datadogLogs.logger?.log(event.message, context, getDatadogStatusType(event.level ?? LogLevel.Info));
160
+ });
164
161
  },
165
162
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
166
- if (breadcrumb.category && config.ignoreBreadcrumbCategories?.includes(breadcrumb.category)) {
167
- return;
168
- }
169
-
170
- const context = {
163
+ datadogRum.addAction(breadcrumb.message, {
171
164
  ...breadcrumb.metadata,
172
165
  category: breadcrumb.category,
173
- };
174
- datadogRum.addAction(breadcrumb.message, context);
166
+ });
175
167
  },
176
168
  addMetadata: function (metadata: Metadata): void {
177
- metadata = filterAttributeMap(metadata, ignoreMetadataPatterns);
178
-
179
169
  for (const [key, value] of Object.entries(metadata)) {
180
170
  if (value !== null) {
181
171
  datadogRum.addRumGlobalContext(key, value);
172
+
173
+ // Note, this will add duplicate context data in logs.
174
+ // But this is valuable for logs ingested outside of the browser logger.
182
175
  datadogLogs.addLoggerGlobalContext(key, value);
183
176
  } else {
184
177
  datadogRum.removeRumGlobalContext(key);
178
+
179
+ // But this is valuable for logs ingested outside of the browser logger.
185
180
  datadogLogs.removeLoggerGlobalContext(key);
186
181
  }
187
182
  }
@@ -193,16 +188,8 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
193
188
  email: user.email,
194
189
  name: user.name ?? user.email,
195
190
  });
196
-
197
- datadogLogs.addLoggerGlobalContext('user_id', user.id);
198
- datadogLogs.addLoggerGlobalContext('user_email', user.email);
199
- datadogLogs.addLoggerGlobalContext('user_username', user.username);
200
191
  } else {
201
192
  datadogRum.removeUser();
202
-
203
- datadogLogs.removeLoggerGlobalContext('user_id');
204
- datadogLogs.removeLoggerGlobalContext('user_email');
205
- datadogLogs.removeLoggerGlobalContext('user_username');
206
193
  }
207
194
  },
208
195
  setRouteName: function (routeName: string): void {
@@ -216,9 +203,6 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
216
203
  }
217
204
  },
218
205
  reportError: function (error: ReportError, metadata?: Metadata): void {
219
- metadata = filterAttributeMap(metadata ?? {}, ignoreMetadataPatterns);
220
-
221
- // Note, datadog should pick up the console error above
222
206
  datadogRum.addError(error, metadata);
223
207
  },
224
208
  recordSession: function (): void {
@@ -230,55 +214,3 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
230
214
  };
231
215
  return reporter;
232
216
  }
233
-
234
- export function datadogLogger(name?: string, options?: { metadata?: Metadata }): ILogger {
235
- const loggerName = name ?? 'root';
236
- const ddLogger = datadogLogs.createLogger(loggerName, {
237
- context: options?.metadata,
238
- });
239
- // Send to datadog and console.
240
- ddLogger.setHandler([HandlerType.http, HandlerType.console]);
241
-
242
- const logger: ILogger = {
243
- startTimer: function (): Profiler {
244
- const start = new Date();
245
-
246
- return {
247
- logger: this,
248
- done: ({ message, level } = {}) => {
249
- const duration = new Date().getTime() - start.getTime();
250
- logger[level ?? LogLevel.Info](message ?? 'Timer Completed', { duration });
251
- return true;
252
- },
253
- };
254
- },
255
- child: function (metadata?: Metadata, name?: string): ILogger {
256
- return datadogLogger(name ?? `${loggerName}.child`, metadata);
257
- },
258
- log: (level: LogLevel, message: string, metadata?: any) => {
259
- switch (level) {
260
- case LogLevel.Debug:
261
- ddLogger.debug(message, metadata);
262
- break;
263
-
264
- case LogLevel.Info:
265
- ddLogger.info(message, metadata);
266
- break;
267
-
268
- case LogLevel.Warn:
269
- ddLogger.warn(message, metadata);
270
- break;
271
-
272
- case LogLevel.Error:
273
- ddLogger.error(message, metadata);
274
- break;
275
- }
276
- return logger;
277
- },
278
- debug: (message: string, metadata?: any) => logger.log(LogLevel.Debug, message, metadata),
279
- info: (message: string, metadata?: any) => logger.log(LogLevel.Info, message, metadata),
280
- warn: (message: string, metadata?: any) => logger.log(LogLevel.Warn, message, metadata),
281
- error: (message: string, metadata?: any) => logger.log(LogLevel.Error, message, metadata),
282
- };
283
- return logger;
284
- }
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import {
3
2
  IReporter,
4
3
  ReporterBreadcrumb,
@@ -8,7 +7,6 @@ import {
8
7
  ServiceInfo,
9
8
  ReporterConfigBase,
10
9
  } from '../types';
11
- import { filterAttributeMap } from '../utils';
12
10
 
13
11
  export type GTMReporterConfig = ReporterConfigBase;
14
12
 
@@ -28,6 +26,7 @@ export function gtmReporter(info: ServiceInfo, config: GTMReporterConfig): IRepo
28
26
  };
29
27
 
30
28
  const reporter: IReporter = {
29
+ ...config,
31
30
  addMetadata: function (metadata: Metadata): void {
32
31
  if (window.dataLayer) {
33
32
  // Lazy load base metadata, e.g. if GTM isn't ready yet.
@@ -36,7 +35,7 @@ export function gtmReporter(info: ServiceInfo, config: GTMReporterConfig): IRepo
36
35
  loadedDataLayer = true;
37
36
  }
38
37
 
39
- window.dataLayer.push(filterAttributeMap(metadata, config.ignoreMetadataPatterns ?? []));
38
+ window.dataLayer.push(metadata);
40
39
  }
41
40
  },
42
41
  trackEvent: function (event: ReporterEvent): void {
@@ -49,10 +48,6 @@ export function gtmReporter(info: ServiceInfo, config: GTMReporterConfig): IRepo
49
48
  });
50
49
  },
51
50
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
52
- if (config.ignoreBreadcrumbCategories?.includes(breadcrumb.category ?? '')) {
53
- return;
54
- }
55
-
56
51
  reporter.addMetadata({
57
52
  ...breadcrumb.metadata,
58
53
  event: breadcrumb.message,
@@ -1,3 +1,216 @@
1
- export * from './amplifyReporter';
2
- export * from './datadogReporter';
3
- export * from './gtmReporter';
1
+ import { isAboveLevel } from '../logger/utils';
2
+ import {
3
+ IReporter,
4
+ LogLevel,
5
+ Metadata,
6
+ ReporterBreadcrumb,
7
+ ReporterEvent,
8
+ ReporterFilters,
9
+ ReportError,
10
+ ReporterType,
11
+ ReportUser,
12
+ TrackedReporterEvent,
13
+ } from '../types';
14
+ import { filterReporterMetadata } from '../utils';
15
+
16
+ export const reporters: Partial<Record<ReporterType, IReporter>> = {};
17
+
18
+ export let globalEventLevel: LogLevel | undefined;
19
+ /**
20
+ * Sets the global event level.
21
+ */
22
+ export function setEventLevel(level: LogLevel | null): void {
23
+ globalEventLevel = level ?? undefined;
24
+ }
25
+
26
+ /**
27
+ * Gets reporters, optionally with filters.
28
+ * @param filters Filters to remove or specify specific reporters.
29
+ * @returns Selected reporters, or all registered reporters.
30
+ */
31
+ function getReporters(filters?: ReporterFilters) {
32
+ let result = Object.entries(reporters);
33
+ if (filters?.excludeReporters) {
34
+ result = result.filter(([key]) => !filters?.excludeReporters?.includes(key as ReporterType));
35
+ }
36
+ if (filters?.toReporters) {
37
+ result = result.filter(([key]) => filters?.toReporters?.includes(key as ReporterType));
38
+ }
39
+ return result.map(([, reporter]) => reporter);
40
+ }
41
+
42
+ /**
43
+ * Tracks an Analytics event.
44
+ */
45
+ export function trackEvent(event: ReporterEvent): void {
46
+ if (globalEventLevel && event.level && !isAboveLevel(event.level, globalEventLevel)) {
47
+ return;
48
+ }
49
+
50
+ for (const reporter of getReporters(event)) {
51
+ if (reporter.endpoints?.trackEvent === false) {
52
+ continue;
53
+ } else if (reporter.eventLevel && event.level && !isAboveLevel(event.level, reporter.eventLevel)) {
54
+ return;
55
+ }
56
+
57
+ const reporterEvent = { ...event };
58
+ reporterEvent.metadata = filterReporterMetadata(event.metadata, reporter);
59
+ reporterEvent.metrics = filterReporterMetadata(event.metrics, reporter);
60
+ reporterEvent.tags = filterReporterMetadata(event.tags, reporter);
61
+ reporter.trackEvent(reporterEvent);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Tracks an Analytics event, recording the time since the last event, if available.
67
+ */
68
+ export function trackEventSinceLastAction(event: ReporterEvent): void {
69
+ if (globalEventLevel && event.level && !isAboveLevel(event.level, globalEventLevel)) {
70
+ return;
71
+ }
72
+
73
+ const lastEvent = getLastTrackedEvent();
74
+ if (lastEvent) {
75
+ const duration = new Date().getTime() - lastEvent.occurred.getTime();
76
+ trackEvent({
77
+ ...event,
78
+ metadata: {
79
+ ...event.metadata,
80
+ lastEventName: lastEvent.message,
81
+ timeSinceLastEvent: duration,
82
+ },
83
+ });
84
+ } else {
85
+ trackEvent(event);
86
+ }
87
+ sessionStorage.setItem('loggerLastEvent', JSON.stringify({ ...event, occurred: new Date() }));
88
+ }
89
+
90
+ /**
91
+ * Gets the last tracked event, if available.
92
+ */
93
+ export function getLastTrackedEvent(): TrackedReporterEvent | null {
94
+ const eventStr = sessionStorage.getItem('loggerLastEvent');
95
+ if (!eventStr) return null;
96
+
97
+ const event: TrackedReporterEvent = JSON.parse(eventStr);
98
+ event.occurred = new Date(event.occurred);
99
+ return event;
100
+ }
101
+
102
+ /**
103
+ * Breadcrumbs to create a trail of events that happened prior to an issue.
104
+ * These events are very similar to traditional logs, but can record more rich structured data.
105
+ */
106
+ export function addBreadcrumb(breadcrumb: ReporterBreadcrumb): void {
107
+ for (const reporter of getReporters(breadcrumb)) {
108
+ if (reporter.endpoints?.addBreadcrumb === false) {
109
+ continue;
110
+ } else if (breadcrumb.category && reporter.ignoreBreadcrumbCategories?.includes(breadcrumb.category)) {
111
+ continue;
112
+ }
113
+
114
+ const reporterBreadcrumb = { ...breadcrumb };
115
+ reporterBreadcrumb.metadata = filterReporterMetadata(breadcrumb.metadata, reporter);
116
+ reporter.addBreadcrumb(reporterBreadcrumb);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Adds global metadata to all events and logs.
122
+ * @param metadata Metadata to add.
123
+ * @param filters Optional filters to specify which reporters to add metadata to.
124
+ */
125
+ export function addMetadata(metadata: Metadata, filters?: ReporterFilters): void {
126
+ for (const reporter of getReporters(filters)) {
127
+ if (reporter.endpoints?.addMetadata === false) {
128
+ continue;
129
+ }
130
+ reporter.addMetadata(filterReporterMetadata(metadata, reporter));
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Sets the user information in Analytics.
136
+ * @param user User information, or null to clear.
137
+ * @param filters Optional filters to specify which reporters to set the user for.
138
+ */
139
+ export function setUser(user: ReportUser | null, filters?: ReporterFilters): void {
140
+ for (const reporter of getReporters(filters)) {
141
+ if (reporter.endpoints?.setUser === false) {
142
+ continue;
143
+ }
144
+ reporter.setUser(user);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Sets the route name in Analytics.
150
+ * Some Analytics services use this differentiate SPA Route vs Page changes.
151
+ * @param routeName Name of the Route.
152
+ * @param filters Optional filters to specify which reporters to set the route name for.
153
+ */
154
+ export function setRouteName(routeName: string, filters?: ReporterFilters): void {
155
+ for (const reporter of getReporters(filters)) {
156
+ if (reporter.endpoints?.setRouteName === false) {
157
+ continue;
158
+ }
159
+ reporter.setRouteName(routeName);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Sets the page name in Analytics.
165
+ * @param pageName Name of the Page.
166
+ * @param filters Optional filters to specify which reporters to set the page name for.
167
+ */
168
+ export function setPageName(pageName: string, filters?: ReporterFilters): void {
169
+ for (const reporter of getReporters(filters)) {
170
+ if (reporter.endpoints?.setPageName === false) {
171
+ continue;
172
+ }
173
+ reporter.setPageName(pageName);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Reports an Error in Reporters that support error tracking.
179
+ * @param error Error data.
180
+ * @param metadata Metadata to add to the error.
181
+ * @param filters Optional filters to specify which reporters to report the error to.
182
+ */
183
+ export function reportError(error: ReportError, metadata?: Metadata, filters?: ReporterFilters): void {
184
+ for (const reporter of getReporters(filters)) {
185
+ if (reporter.endpoints?.reportError === false) {
186
+ continue;
187
+ }
188
+ reporter.reportError?.(error, filterReporterMetadata(metadata, reporter));
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Starts a session recording in Analytics, e.g. RUM Session Replay
194
+ * @param filters Optional filters to specify which reporters to start a session recording for.
195
+ */
196
+ export function recordSession(filters?: ReporterFilters): void {
197
+ for (const reporter of getReporters(filters)) {
198
+ if (reporter.endpoints?.recordSession === false) {
199
+ continue;
200
+ }
201
+ reporter.recordSession?.();
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Stops a session recording in Analytics, e.g. RUM Session Replay
207
+ * @param filters Optional filters to specify which reporters to stop a session recording for.
208
+ */
209
+ export function recordSessionStop(filters?: ReporterFilters): void {
210
+ for (const reporter of getReporters(filters)) {
211
+ if (reporter.endpoints?.recordSessionStop === false) {
212
+ continue;
213
+ }
214
+ reporter.recordSessionStop?.();
215
+ }
216
+ }
@@ -0,0 +1,86 @@
1
+ import { logger, globalMetadata as globalLogMetadata } from '../logger';
2
+ import { IReporter, LogLevel, ReporterConfigBase, ServiceInfo } from '../types';
3
+
4
+ export interface LogReporterConfig extends ReporterConfigBase {
5
+ /**
6
+ * The fallback log level to apply to all trackEvent calls (if the event's level is not set).
7
+ * Defaults to info.
8
+ */
9
+ trackEventDefaultLevel?: LogLevel;
10
+ /**
11
+ * The log level to apply to all addBreadcrumb calls.
12
+ * Defaults to info.
13
+ */
14
+ addBreadcrumbLevel?: LogLevel;
15
+ /**
16
+ * The log level to apply to all setRouteName calls.
17
+ * Defaults to debug.
18
+ */
19
+ setRouteNameLevel?: LogLevel;
20
+ /**
21
+ * The log level to apply to all setPageName calls.
22
+ * Defaults to debug.
23
+ */
24
+ setPageNameLevel?: LogLevel;
25
+ /**
26
+ * The log level to apply to all recordSession calls.
27
+ * Defaults to debug.
28
+ */
29
+ recordSessionLevel?: LogLevel;
30
+ /**
31
+ * The log level to apply to all recordSessionStop calls.
32
+ * Defaults to debug.
33
+ */
34
+ recordSessionStopLevel?: LogLevel;
35
+ }
36
+
37
+ export function logReporter(info: ServiceInfo, config: LogReporterConfig) {
38
+ const reporter: IReporter = {
39
+ ...config,
40
+ trackEvent(event) {
41
+ // config.trackEventLevel and config.level overrides event.level here, as it is an override, while config.trackEventDefaultLevel is a fallback.
42
+ const level = event.level ?? config.trackEventDefaultLevel ?? LogLevel.Info;
43
+ logger.log(level, event.message, {
44
+ ...(event.metadata ?? {}),
45
+ ...(event.metrics ?? {}),
46
+ ...(event.tags ?? {}),
47
+ });
48
+ },
49
+ addBreadcrumb(breadcrumb) {
50
+ const level = config.addBreadcrumbLevel ?? LogLevel.Info;
51
+ logger.log(level, breadcrumb.message, {
52
+ ...(breadcrumb.metadata ?? {}),
53
+ category: breadcrumb.category,
54
+ });
55
+ },
56
+ addMetadata(metadata) {
57
+ Object.assign(globalLogMetadata, metadata);
58
+ },
59
+ setUser(user) {
60
+ Object.assign(globalLogMetadata, { user });
61
+ },
62
+ setRouteName(routeName) {
63
+ Object.assign(globalLogMetadata, { routeName });
64
+ const level = config.setRouteNameLevel ?? LogLevel.Debug;
65
+ logger.log(level, `Route changed to ${routeName}`);
66
+ },
67
+ setPageName(pageName) {
68
+ Object.assign(globalLogMetadata, { pageName });
69
+ const level = config.setPageNameLevel ?? LogLevel.Debug;
70
+ logger.log(level, `Page changed to ${pageName}`);
71
+ },
72
+ reportError(error, metadata) {
73
+ logger.error(typeof error === 'string' ? error : error.message, { ...metadata, error });
74
+ },
75
+ recordSession() {
76
+ const level = config.recordSessionLevel ?? LogLevel.Debug;
77
+ logger.log(level, 'Recording Session');
78
+ },
79
+ recordSessionStop() {
80
+ const level = config.recordSessionStopLevel ?? LogLevel.Debug;
81
+ logger.log(level, 'Recording Session Stopping');
82
+ },
83
+ };
84
+
85
+ return reporter;
86
+ }