@crimson-education/browser-logger 2.0.2 → 3.0.1-pinpoint-middleware.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 (79) hide show
  1. package/README.md +212 -67
  2. package/lib/index.d.ts +12 -82
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +69 -213
  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 +83 -77
  27. package/lib/reporters/amplifyReporter.d.ts.map +1 -1
  28. package/lib/reporters/amplifyReporter.js +162 -151
  29. package/lib/reporters/amplifyReporter.js.map +1 -1
  30. package/lib/reporters/amplifyReporter.test.d.ts +1 -1
  31. package/lib/reporters/amplifyReporter.test.js +50 -50
  32. package/lib/reporters/datadogReporter.d.ts +76 -74
  33. package/lib/reporters/datadogReporter.d.ts.map +1 -1
  34. package/lib/reporters/datadogReporter.js +122 -185
  35. package/lib/reporters/datadogReporter.js.map +1 -1
  36. package/lib/reporters/gtmReporter.d.ts +8 -8
  37. package/lib/reporters/gtmReporter.d.ts.map +1 -1
  38. package/lib/reporters/gtmReporter.js +56 -61
  39. package/lib/reporters/gtmReporter.js.map +1 -1
  40. package/lib/reporters/index.d.ts +66 -3
  41. package/lib/reporters/index.d.ts.map +1 -1
  42. package/lib/reporters/index.js +212 -19
  43. package/lib/reporters/index.js.map +1 -1
  44. package/lib/reporters/logReporter.d.ts +35 -0
  45. package/lib/reporters/logReporter.d.ts.map +1 -0
  46. package/lib/reporters/logReporter.js +62 -0
  47. package/lib/reporters/logReporter.js.map +1 -0
  48. package/lib/types/index.d.ts +2 -2
  49. package/lib/types/index.js +18 -18
  50. package/lib/types/logger.d.ts +77 -36
  51. package/lib/types/logger.d.ts.map +1 -1
  52. package/lib/types/logger.js +10 -10
  53. package/lib/types/logger.js.map +1 -1
  54. package/lib/types/reporter.d.ts +154 -102
  55. package/lib/types/reporter.d.ts.map +1 -1
  56. package/lib/types/reporter.js +2 -2
  57. package/lib/utils.d.ts +9 -8
  58. package/lib/utils.d.ts.map +1 -1
  59. package/lib/utils.js +31 -51
  60. package/lib/utils.js.map +1 -1
  61. package/lib/utils.test.d.ts +1 -1
  62. package/lib/utils.test.js +31 -18
  63. package/lib/utils.test.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/index.ts +38 -208
  66. package/src/logger/consoleTransport.ts +101 -0
  67. package/src/logger/datadogTransport.ts +20 -0
  68. package/src/logger/index.test.ts +68 -0
  69. package/src/logger/index.ts +139 -0
  70. package/src/logger/utils.ts +28 -0
  71. package/src/reporters/amplifyReporter.ts +37 -17
  72. package/src/reporters/datadogReporter.ts +48 -116
  73. package/src/reporters/gtmReporter.ts +2 -7
  74. package/src/reporters/index.ts +216 -3
  75. package/src/reporters/logReporter.ts +86 -0
  76. package/src/types/logger.ts +49 -4
  77. package/src/types/reporter.ts +66 -6
  78. package/src/utils.test.ts +20 -6
  79. package/src/utils.ts +37 -62
@@ -0,0 +1,28 @@
1
+ import { LogLevel } from '../types';
2
+
3
+ /**
4
+ * Checks if a log level is above the global log level.
5
+ * @param level The log level to check
6
+ * @param checkLevel THe log level to check against
7
+ * @returns Is above the checkLevel?
8
+ */
9
+ export function isAboveLevel(level: LogLevel, checkLevel: LogLevel): boolean {
10
+ const levels = [LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error];
11
+ return levels.indexOf(level) >= levels.indexOf(checkLevel);
12
+ }
13
+
14
+ /**
15
+ * Ensures a message output is a string.
16
+ * @param message The message to assert is/convert to a string
17
+ * @returns The message as a string
18
+ */
19
+ export function getLogMessage(message: any): string {
20
+ if (typeof message === 'string') {
21
+ return message;
22
+ }
23
+ if (typeof message === 'object' && message !== null) {
24
+ const data = { ...message };
25
+ return JSON.stringify(data);
26
+ }
27
+ return String(message);
28
+ }
@@ -9,7 +9,12 @@ 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>;
16
+
17
+ type AmplifyAutoTrackSource = 'pageView' | 'event' | 'session';
13
18
 
14
19
  export interface AmplifyReporterConfig extends ReporterConfigBase {
15
20
  /**
@@ -51,6 +56,11 @@ export interface AmplifyReporterConfig extends ReporterConfigBase {
51
56
  * To keep it simple we define that the web session is active when the page is not hidden and inactive when the page is hidden.
52
57
  */
53
58
  autoTrackSessions?: boolean;
59
+ /**
60
+ * Optional function to run before autotracked analytics events are sent out.
61
+ * The returned metadata is attached to the event.
62
+ */
63
+ beforeAutoTrack?(source: AmplifyAutoTrackSource): Metadata;
54
64
 
55
65
  /**
56
66
  * The data tag prefix to use for attributing HTML elements. Defaults to data-analytics-
@@ -89,6 +99,11 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
89
99
  });
90
100
  }
91
101
 
102
+ const wrapAutoTrackMiddleware = (source: AmplifyAutoTrackSource) => {
103
+ const { beforeAutoTrack } = config;
104
+ return typeof beforeAutoTrack === 'function' ? () => beforeAutoTrack(source) : undefined;
105
+ };
106
+
92
107
  const allMetadata = asAttributeMap({
93
108
  appName: info.service,
94
109
  service: info.service,
@@ -100,19 +115,27 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
100
115
  Analytics.configure({
101
116
  region: config.region,
102
117
  appId: config.analyticsAppId,
103
- autoSessionRecord: config.autoTrackSessions,
104
118
  endpoint: {
105
119
  attributes: allMetadata,
106
120
  },
107
121
  ...config.buffering,
108
122
  });
109
123
 
124
+ // Session autotracking is enabled by default for backwards compatibility reasons, so we _must_
125
+ // call this unconditionally to ensure we opt out of session tracking when `autoTrackSessions` isn't set
126
+ // See: https://docs.amplify.aws/lib/analytics/autotrack/q/platform/js/#session-tracking
127
+ Analytics.autoTrack('session', {
128
+ enable: config.autoTrackSessions === true,
129
+ attributes: wrapAutoTrackMiddleware('session'),
130
+ });
131
+
110
132
  if (config.autoTrackPageViews) {
111
133
  Analytics.autoTrack('pageView', {
112
134
  enable: true,
113
135
  eventName: 'pageView',
114
136
  type: 'SPA',
115
137
  provider: 'AWSPinpoint',
138
+ attributes: wrapAutoTrackMiddleware('pageView'),
116
139
  });
117
140
  }
118
141
 
@@ -120,6 +143,7 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
120
143
  Analytics.autoTrack('event', {
121
144
  enable: true,
122
145
  selectorPrefix: config.selectorPrefix ?? 'data-analytics-',
146
+ attributes: wrapAutoTrackMiddleware('event'),
123
147
  });
124
148
  }
125
149
 
@@ -133,16 +157,11 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
133
157
  ...event.tags,
134
158
  },
135
159
  false,
136
- config.ignoreMetadataPatterns,
137
160
  ) as Record<string, string>,
138
161
  metrics: event.metrics,
139
162
  });
140
163
  },
141
164
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
142
- if (breadcrumb.category && config.ignoreBreadcrumbCategories?.includes(breadcrumb.category)) {
143
- return;
144
- }
145
-
146
165
  reporter.trackEvent({
147
166
  message: breadcrumb.message,
148
167
  metadata: {
@@ -152,7 +171,7 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
152
171
  });
153
172
  },
154
173
  addMetadata: function (metadata: Metadata): void {
155
- Object.assign(allMetadata, asAttributeMap(metadata, true, config.ignoreMetadataPatterns));
174
+ Object.assign(allMetadata, asAttributeMap(metadata, true));
156
175
  Analytics.updateEndpoint({
157
176
  attributes: allMetadata,
158
177
  }).catch(() => {
@@ -188,16 +207,10 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
188
207
  /**
189
208
  * Pinpoint has strict attribute name and value length limits
190
209
  */
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 {
210
+ export function asAttributeMap(values: Record<string, unknown>, groupValues = true): AttributeMap {
197
211
  const attributeMap = buildAttributeMap(values, undefined, groupValues);
198
- const filteredAttributeMap = filterAttributeMap(attributeMap, ignorePatterns);
199
212
 
200
- const checkedEntries = Object.entries(filteredAttributeMap).map(([key, value]) => {
213
+ const checkedEntries = Object.entries(attributeMap).map(([key, value]) => {
201
214
  const truncatedKey = key.length > 50 ? `___${key.slice(-47)}` : key;
202
215
  const truncatedValue = Array.isArray(value)
203
216
  ? value?.map((val) => val.slice(0, 100)) ?? null
@@ -206,6 +219,14 @@ export function asAttributeMap(
206
219
  return [truncatedKey, truncatedValue];
207
220
  });
208
221
 
222
+ // Pinpoint only accepts 40 attributes
223
+ if (checkedEntries.length > 40) {
224
+ logger.error(`Amplify only allows 40 attributes per event, truncating to 40 attributes`, {
225
+ attributes: checkedEntries,
226
+ });
227
+ checkedEntries.length = 40;
228
+ }
229
+
209
230
  return Object.fromEntries(checkedEntries);
210
231
  }
211
232
 
@@ -215,7 +236,6 @@ export function asAttributeMap(
215
236
  * all of its values are of type `string[]` to appease Pinpoint.
216
237
  */
217
238
  export function buildAttributeMap(
218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
239
  values: Record<string, any>,
220
240
  parentKey: string | undefined = undefined,
221
241
  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
+ }