@crimson-education/browser-logger 2.0.2-cognito.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 (89) hide show
  1. package/README.md +289 -18
  2. package/lib/index.d.ts +10 -24
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +45 -116
  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 +40 -14
  27. package/lib/reporters/amplifyReporter.d.ts.map +1 -1
  28. package/lib/reporters/amplifyReporter.js +15 -23
  29. package/lib/reporters/amplifyReporter.js.map +1 -1
  30. package/lib/reporters/amplifyReporter.test.js +0 -11
  31. package/lib/reporters/amplifyReporter.test.js.map +1 -1
  32. package/lib/reporters/datadogReporter.d.ts +64 -14
  33. package/lib/reporters/datadogReporter.d.ts.map +1 -1
  34. package/lib/reporters/datadogReporter.js +46 -101
  35. package/lib/reporters/datadogReporter.js.map +1 -1
  36. package/lib/reporters/gtmReporter.d.ts +3 -2
  37. package/lib/reporters/gtmReporter.d.ts.map +1 -1
  38. package/lib/reporters/gtmReporter.js +20 -6
  39. package/lib/reporters/gtmReporter.js.map +1 -1
  40. package/lib/reporters/index.d.ts +66 -28
  41. package/lib/reporters/index.d.ts.map +1 -1
  42. package/lib/reporters/index.js +210 -0
  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 +3 -0
  49. package/lib/types/index.d.ts.map +1 -0
  50. package/lib/types/index.js +19 -0
  51. package/lib/types/index.js.map +1 -0
  52. package/lib/types/logger.d.ts +78 -0
  53. package/lib/types/logger.d.ts.map +1 -0
  54. package/lib/{types.js → types/logger.js} +1 -1
  55. package/lib/types/logger.js.map +1 -0
  56. package/lib/types/reporter.d.ts +155 -0
  57. package/lib/types/reporter.d.ts.map +1 -0
  58. package/lib/types/reporter.js +3 -0
  59. package/lib/types/reporter.js.map +1 -0
  60. package/lib/utils.d.ts +9 -4
  61. package/lib/utils.d.ts.map +1 -1
  62. package/lib/utils.js +28 -43
  63. package/lib/utils.js.map +1 -1
  64. package/lib/utils.test.d.ts +2 -0
  65. package/lib/utils.test.d.ts.map +1 -0
  66. package/lib/utils.test.js +32 -0
  67. package/lib/utils.test.js.map +1 -0
  68. package/package.json +5 -4
  69. package/src/index.ts +41 -120
  70. package/src/logger/consoleTransport.ts +101 -0
  71. package/src/logger/datadogTransport.ts +20 -0
  72. package/src/logger/index.test.ts +68 -0
  73. package/src/logger/index.ts +139 -0
  74. package/src/logger/utils.ts +28 -0
  75. package/src/reporters/amplifyReporter.test.ts +1 -14
  76. package/src/reporters/amplifyReporter.ts +65 -36
  77. package/src/reporters/datadogReporter.ts +123 -115
  78. package/src/reporters/gtmReporter.ts +35 -8
  79. package/src/reporters/index.ts +208 -24
  80. package/src/reporters/logReporter.ts +86 -0
  81. package/src/types/index.ts +2 -0
  82. package/src/types/logger.ts +85 -0
  83. package/src/types/reporter.ts +167 -0
  84. package/src/utils.test.ts +32 -0
  85. package/src/utils.ts +39 -49
  86. package/lib/types.d.ts +0 -48
  87. package/lib/types.d.ts.map +0 -1
  88. package/lib/types.js.map +0 -1
  89. package/src/types.ts +0 -50
@@ -0,0 +1,139 @@
1
+ /* eslint-disable prefer-rest-params */
2
+ import { ILogger, ILogTransport, LoggerOptions, LogLevel, Metadata, Profiler, StructuredLog } from '../types';
3
+ import { filterReporterMetadata } from '../utils';
4
+ import { consoleTransport } from './consoleTransport';
5
+ import { getLogMessage, isAboveLevel } from './utils';
6
+
7
+ export * from './consoleTransport';
8
+ export * from './datadogTransport';
9
+
10
+ export const globalMetadata: Metadata = {};
11
+
12
+ export const logTransports: ILogTransport[] = [consoleTransport()];
13
+
14
+ export let globalLogLevel: LogLevel | undefined;
15
+ /**
16
+ * Sets the global log level.
17
+ */
18
+ export function setLogLevel(level: LogLevel | null): void {
19
+ globalLogLevel = level ?? undefined;
20
+ }
21
+
22
+ /**
23
+ * Creates a Logger Instance.
24
+ */
25
+ export function createLogger(options: LoggerOptions = {}): ILogger {
26
+ const rootMetadata = options.metadata ?? {};
27
+
28
+ const logger: ILogger = {
29
+ // impl loosely based on Winston Logger's dynamic log function
30
+ // https://github.com/winstonjs/winston/blob/master/lib/winston/logger.js
31
+ log() {
32
+ let logInfo: StructuredLog;
33
+
34
+ // Optimize for the hotpath of logging JSON literals
35
+ if (arguments.length === 1) {
36
+ logInfo = {
37
+ level: LogLevel.Info,
38
+ ...arguments[0],
39
+ };
40
+ }
41
+ // Slightly less hotpath, but worth optimizing for.
42
+ else if (arguments.length === 2) {
43
+ const [level, message] = Array.from(arguments);
44
+ if (message && typeof message === 'object') {
45
+ logInfo = {
46
+ ...message,
47
+ level: message.level ?? level,
48
+ };
49
+ } else {
50
+ logInfo = {
51
+ level,
52
+ message,
53
+ };
54
+ }
55
+ } else {
56
+ const [level, message, ...splat] = Array.from(arguments);
57
+
58
+ const [meta] = splat;
59
+ if (typeof meta === 'object' && meta !== null) {
60
+ logInfo = {
61
+ ...meta,
62
+ level,
63
+ splat: splat.slice(1),
64
+ message,
65
+ };
66
+
67
+ if (meta.message) {
68
+ logInfo.message = `${getLogMessage(logInfo.message)} ${getLogMessage(meta.message)}`;
69
+ }
70
+ if (meta.stack) {
71
+ logInfo.stack = meta.stack;
72
+ }
73
+ } else {
74
+ logInfo = {
75
+ level,
76
+ splat,
77
+ message,
78
+ };
79
+ }
80
+ }
81
+
82
+ // Check if we should log this message
83
+ const minLevel = globalLogLevel ?? options.logLevel;
84
+ if (minLevel && !isAboveLevel(logInfo.level, minLevel)) {
85
+ return logger;
86
+ }
87
+
88
+ // Add timestamp and root metadata.
89
+ // Ensure there is a message property.
90
+ logInfo = {
91
+ ...globalMetadata,
92
+ ...rootMetadata,
93
+ timestamp: new Date().toISOString(),
94
+ ...logInfo,
95
+ message: logInfo.message ?? '',
96
+ };
97
+
98
+ for (const transport of logTransports) {
99
+ if (transport.logLevel) {
100
+ if (!isAboveLevel(logInfo.level, transport.logLevel)) {
101
+ continue;
102
+ }
103
+ }
104
+ transport.log({
105
+ ...filterReporterMetadata(logInfo, transport),
106
+ // These would break the transport if excluded.
107
+ level: logInfo.level,
108
+ message: logInfo.message,
109
+ splat: logInfo.splat,
110
+ });
111
+ }
112
+ return logger;
113
+ },
114
+ child(metadata: Metadata): ILogger {
115
+ return createLogger({ ...rootMetadata, metadata });
116
+ },
117
+ startTimer(): Profiler {
118
+ const start = new Date();
119
+
120
+ return {
121
+ logger: this,
122
+ done: ({ message, level, metadata } = {}) => {
123
+ const duration = new Date().getTime() - start.getTime();
124
+ logger.log(level ?? LogLevel.Info, message ?? 'Timer Completed', { duration, ...(metadata ?? {}) });
125
+ return true;
126
+ },
127
+ };
128
+ },
129
+ // Forward the log levels into the log function, essentially shortcuts.
130
+ debug: (...args) => logger.log(LogLevel.Debug, ...args),
131
+ info: (...args) => logger.log(LogLevel.Info, ...args),
132
+ warn: (...args) => logger.log(LogLevel.Warn, ...args),
133
+ error: (...args) => logger.log(LogLevel.Error, ...args),
134
+ };
135
+
136
+ return logger;
137
+ }
138
+
139
+ export const logger = createLogger();
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { asAttributeMap, filterAttributeMap } from './amplifyReporter';
1
+ import { asAttributeMap } from './amplifyReporter';
2
2
 
3
3
  describe('amplifyReporter', () => {
4
4
  it('should convert all attribute values to arrays of strings', () => {
@@ -58,17 +58,4 @@ describe('amplifyReporter', () => {
58
58
  const attributeMap = asAttributeMap(testMetadata, false);
59
59
  expect(attributeMap.a).toEqual('5');
60
60
  });
61
-
62
- it('should remove attributes which match the ignore patterns', () => {
63
- const inputAttributeMap = {
64
- includeme: '5',
65
- excludeme: 'false',
66
- differentProp: 'boo',
67
- };
68
-
69
- const filtered = filterAttributeMap(inputAttributeMap, [/exclude/g, /Prop/g]);
70
- expect(filtered['includeme']).toBeTruthy();
71
- expect(filtered['excludeme']).toBeFalsy();
72
- expect(filtered['differentProp']).toBeFalsy();
73
- });
74
61
  });
@@ -1,26 +1,70 @@
1
- import { IReporter, ReporterBreadcrumb, ReporterEvent } from '.';
2
- import { Metadata, ReportUser, ServiceInfo } from '../types';
1
+ import {
2
+ IReporter,
3
+ Metadata,
4
+ ReporterBreadcrumb,
5
+ ReporterConfigBase,
6
+ ReporterEvent,
7
+ ReportUser,
8
+ ServiceInfo,
9
+ } from '../types';
3
10
  import { Auth } from '@aws-amplify/auth';
4
11
  import { Analytics } from '@aws-amplify/analytics';
12
+ import { logger } from '../logger';
5
13
 
6
- export type AmplifyReporterConfig = {
14
+ /* eslint-disable @typescript-eslint/no-unused-vars */
15
+ type AttributeMap = Record<string, string[] | string | null>;
16
+
17
+ export interface AmplifyReporterConfig extends ReporterConfigBase {
18
+ /**
19
+ * AWS Region for Amplify.
20
+ */
7
21
  region: string;
8
22
  /**
9
- * The Identity Pool ID to use for reporting, if set to false, Auth.configure is not called.
23
+ * The Identity Pool Id to use for reporting, if set to false, Auth.configure is not called.
10
24
  * This must be called manually for the reporter to work.
11
25
  */
12
26
  identityPoolId: string | false;
27
+ /**
28
+ * The Pinpoint App Id to report to.
29
+ */
13
30
  analyticsAppId: string;
31
+ /**
32
+ * The Cognito User Pool to configure in Auth.configure.
33
+ * If you are using Cognito, it is better to set identityPoolId to false and configure Auth manually.
34
+ */
35
+ userPoolId?: string;
36
+ /**
37
+ * The Cognito Web Client Id to configure in Auth.configure.
38
+ * If you are using Cognito, it is better to set identityPoolId to false and configure Auth manually.
39
+ */
40
+ userPoolWebClientId?: string;
41
+
42
+ /**
43
+ * If you want to track which page/url in your webapp is the most frequently viewed one, you can use this feature.
44
+ * It will automatically send events containing url information when the page is visited.
45
+ */
14
46
  autoTrackPageViews?: boolean;
47
+ /**
48
+ * If you want to track user interactions with elements on the page, you can use this feature.
49
+ * All you need to do is attach the specified selectors to your dom element and turn on the auto tracking.
50
+ */
15
51
  autoTrackEvents?: boolean;
52
+ /**
53
+ * A web session can be defined in different ways.
54
+ * 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.
55
+ */
16
56
  autoTrackSessions?: boolean;
57
+
58
+ /**
59
+ * The data tag prefix to use for attributing HTML elements. Defaults to data-analytics-
60
+ */
17
61
  selectorPrefix?: string;
18
- ignoreBreadcrumbCategories?: string[];
62
+
63
+ /**
64
+ * Modify how the reporter sends events to Amplify.
65
+ */
19
66
  buffering?: AmplifyReporterBufferingConfig;
20
- userPoolId?: string;
21
- userPoolWebClientId?: string;
22
- ignoreMetadataPatterns?: RegExp[];
23
- };
67
+ }
24
68
 
25
69
  /**
26
70
  * Configuration options for the buffering behavior of Pinpoint's event tracker.
@@ -52,7 +96,7 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
52
96
  appName: info.service,
53
97
  service: info.service,
54
98
  domain: window.location.host,
55
- environment: info.service,
99
+ environment: info.environment,
56
100
  version: info.version,
57
101
  });
58
102
 
@@ -92,16 +136,11 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
92
136
  ...event.tags,
93
137
  },
94
138
  false,
95
- config.ignoreMetadataPatterns,
96
139
  ) as Record<string, string>,
97
140
  metrics: event.metrics,
98
141
  });
99
142
  },
100
143
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
101
- if (breadcrumb.category && config.ignoreBreadcrumbCategories?.includes(breadcrumb.category)) {
102
- return;
103
- }
104
-
105
144
  reporter.trackEvent({
106
145
  message: breadcrumb.message,
107
146
  metadata: {
@@ -111,7 +150,7 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
111
150
  });
112
151
  },
113
152
  addMetadata: function (metadata: Metadata): void {
114
- Object.assign(allMetadata, asAttributeMap(metadata, true, config.ignoreMetadataPatterns));
153
+ Object.assign(allMetadata, asAttributeMap(metadata, true));
115
154
  Analytics.updateEndpoint({
116
155
  attributes: allMetadata,
117
156
  }).catch(() => {
@@ -139,29 +178,18 @@ export function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig
139
178
  setPageName: function (pageName: string): void {
140
179
  reporter.addMetadata({ pageName });
141
180
  },
142
- reportError: function (): void {},
143
- recordSession: function (): void {},
144
- recordSessionStop: function (): void {},
145
181
  };
146
182
 
147
183
  return reporter;
148
184
  }
149
185
 
150
- type AttributeMap = Record<string, string[] | string | null>;
151
-
152
186
  /**
153
187
  * Pinpoint has strict attribute name and value length limits
154
188
  */
155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- export function asAttributeMap(
157
- values: Record<string, any>,
158
- groupValues = true,
159
- ignorePatterns: RegExp[] = [],
160
- ): AttributeMap {
189
+ export function asAttributeMap(values: Record<string, unknown>, groupValues = true): AttributeMap {
161
190
  const attributeMap = buildAttributeMap(values, undefined, groupValues);
162
- const filteredAttributeMap = filterAttributeMap(attributeMap, ignorePatterns);
163
191
 
164
- const checkedEntries = Object.entries(filteredAttributeMap).map(([key, value]) => {
192
+ const checkedEntries = Object.entries(attributeMap).map(([key, value]) => {
165
193
  const truncatedKey = key.length > 50 ? `___${key.slice(-47)}` : key;
166
194
  const truncatedValue = Array.isArray(value)
167
195
  ? value?.map((val) => val.slice(0, 100)) ?? null
@@ -170,6 +198,14 @@ export function asAttributeMap(
170
198
  return [truncatedKey, truncatedValue];
171
199
  });
172
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
+
173
209
  return Object.fromEntries(checkedEntries);
174
210
  }
175
211
 
@@ -179,7 +215,6 @@ export function asAttributeMap(
179
215
  * all of its values are of type `string[]` to appease Pinpoint.
180
216
  */
181
217
  export function buildAttributeMap(
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
218
  values: Record<string, any>,
184
219
  parentKey: string | undefined = undefined,
185
220
  groupValues = true,
@@ -206,9 +241,3 @@ export function buildAttributeMap(
206
241
 
207
242
  return valuesWithStringArrays;
208
243
  }
209
-
210
- export function filterAttributeMap(attributes: AttributeMap | Record<string, string>, ignorePatterns: RegExp[]) {
211
- const entries = Object.entries(attributes);
212
-
213
- return Object.fromEntries(entries.filter(([key]) => !ignorePatterns.some((pattern) => pattern.test(key))));
214
- }
@@ -1,63 +1,129 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { IReporter, ReporterBreadcrumb, ReporterEvent } from '.';
3
- import { Metadata, Profiler, ReportError, ReportUser, ServiceInfo } from '../types';
4
- import { datadogLogs, HandlerType, StatusType } from '@datadog/browser-logs';
1
+ import {
2
+ IReporter,
3
+ ReporterBreadcrumb,
4
+ ReporterEvent,
5
+ Metadata,
6
+ ReportError,
7
+ ReportUser,
8
+ ServiceInfo,
9
+ ReporterConfigBase,
10
+ } from '../types';
11
+ import { datadogLogs, HandlerType } from '@datadog/browser-logs';
5
12
  import { datadogRum, DefaultPrivacyLevel } from '@datadog/browser-rum';
6
- import { ILogger, LogLevel } from '..';
13
+ import { logTransports } from '../logger';
14
+ import { DatadogLogTransportConfig, datadogTransport } from '../logger/datadogTransport';
7
15
 
8
- export type DatadogReporterConfig = {
16
+ export interface DatadogReporterConfig extends ReporterConfigBase {
17
+ /** The RUM application ID. */
9
18
  applicationId: string;
19
+ /** A Datadog client token (Generated in the RUM Page) */
10
20
  clientToken: string;
21
+ /** The Datadog site parameter of your organization. */
22
+ site: string;
23
+ /** The Application's version (Overrides the browser logger version) */
11
24
  version?: string;
12
- site?: string;
25
+ /** Optional proxy URL */
13
26
  proxyUrl?: string;
14
- forwardConsoleLogs?: boolean;
15
- trackInteractions?: boolean;
27
+
28
+ /**
29
+ * Controls the percentage of overall sessions being tracked. It defaults to 100, so every session is tracked by default.
30
+ */
16
31
  sampleRate?: number;
32
+ /**
33
+ * Applied after the overall sample rate, and controls the percentage of sessions tracked as Browser RUM & Session Replay.
34
+ * It defaults to 100, so every session is tracked as Browser RUM & Session Replay by default.
35
+ */
17
36
  replaySampleRate?: number;
18
- defaultPrivacyLevel?: DefaultPrivacyLevel;
37
+
38
+ /**
39
+ * Use a secure session cookie. This disables RUM events sent on insecure (non-HTTPS) connections.
40
+ */
19
41
  useSecureSessionCookie?: boolean;
42
+ /**
43
+ * Use a secure cross-site session cookie.
44
+ * This allows the RUM Browser SDK to run when the site is loaded from another one (iframe). Implies `useSecureSessionCookie`
45
+ */
20
46
  useCrossSiteSessionCookie?: boolean;
21
- allowedTracingOrigins?: (string | RegExp)[];
47
+ /**
48
+ * Preserve the session across subdomains for the same site.
49
+ */
22
50
  trackSessionAcrossSubdomains?: boolean;
23
- actionNameAttribute?: string;
24
- trackViewsManually?: boolean;
25
- };
26
-
27
- function getDatadogStatusType(level: LogLevel): StatusType {
28
- switch (level) {
29
- case LogLevel.Error:
30
- return StatusType.error;
31
-
32
- case LogLevel.Warn:
33
- return StatusType.warn;
34
51
 
35
- case LogLevel.Info:
36
- return StatusType.info;
52
+ /**
53
+ * Forward console.error logs, uncaught exceptions and network errors to Datadog.
54
+ * Defaults to false.
55
+ */
56
+ forwardConsoleLogs?: boolean;
57
+ /**
58
+ * Enables automatic collection of users actions.
59
+ */
60
+ trackInteractions?: boolean;
61
+ /**
62
+ * Enables automatic collection of user frustrations. Implies `trackInteractions`: true.
63
+ */
64
+ trackFrustrations?: boolean;
65
+ /**
66
+ * Allows you to control RUM views creation.
67
+ * You will need to call `Logger.setPageName()` to create a new view.
68
+ */
69
+ trackViewsManually?: boolean;
70
+ /**
71
+ * Specify your own attribute to be used to name actions.
72
+ * Defaults to `data-analytics-name` to coincide with analytics attributes used across reporters.
73
+ */
74
+ actionNameAttribute?: string;
37
75
 
38
- case LogLevel.Debug:
39
- default:
40
- return StatusType.debug;
41
- }
76
+ /**
77
+ * Toggles/configures the Datadog Log Transport.
78
+ * Defaults to true.
79
+ */
80
+ logTransport?: boolean | DatadogLogTransportConfig;
81
+
82
+ /**
83
+ * By enabling Session Replay, you can automatically mask sensitive elements from being recorded through the RUM Browser SDK.
84
+ *
85
+ * See https://docs.datadoghq.com/real_user_monitoring/session_replay/privacy_options
86
+ */
87
+ defaultPrivacyLevel?: DefaultPrivacyLevel;
88
+ /**
89
+ * A list of request origins used to inject tracing headers, to be able to connect RUM and backend tracing.
90
+ */
91
+ allowedTracingOrigins?: (string | RegExp)[];
42
92
  }
43
93
 
44
94
  export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig): IReporter {
45
95
  const isLocalhost = window.location.hostname === 'localhost';
46
96
 
47
- datadogLogs.init({
48
- site: config.site,
49
- proxyUrl: config.proxyUrl,
50
- clientToken: config.clientToken,
51
- service: info.service,
52
- env: info.environment,
53
- version: config.version ?? info.version,
54
-
55
- useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
56
- useCrossSiteSessionCookie: config.useCrossSiteSessionCookie ?? false,
57
- 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
+ }
58
122
 
59
- forwardErrorsToLogs: config.forwardConsoleLogs ?? false,
60
- });
123
+ // Add the datadog log transport
124
+ if (enableLogTransport) {
125
+ logTransports.push(datadogTransport(typeof config.logTransport === 'boolean' ? {} : config.logTransport));
126
+ }
61
127
 
62
128
  datadogRum.init({
63
129
  site: config.site,
@@ -68,46 +134,49 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
68
134
  env: info.environment,
69
135
  version: config.version ?? info.version,
70
136
 
137
+ sampleRate: config.sampleRate ?? 100,
138
+ replaySampleRate: config.replaySampleRate ?? 100,
139
+
71
140
  useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
72
141
  useCrossSiteSessionCookie: config.useCrossSiteSessionCookie ?? false,
73
142
  trackSessionAcrossSubdomains: config.trackSessionAcrossSubdomains,
74
143
 
75
- // Set sampleRate to 100 to capture 100%
76
- // of transactions for performance monitoring.
77
- sampleRate: config.sampleRate ?? 100,
78
- replaySampleRate: config.replaySampleRate ?? 100,
79
144
  trackInteractions: config.trackInteractions ?? false,
145
+ trackFrustrations: config.trackFrustrations ?? false,
146
+ trackViewsManually: config.trackViewsManually ?? false,
147
+ actionNameAttribute: config.actionNameAttribute ?? 'data-analytics-name',
148
+
80
149
  defaultPrivacyLevel: config.defaultPrivacyLevel ?? 'mask-user-input',
81
150
  allowedTracingOrigins: config.allowedTracingOrigins,
82
- actionNameAttribute: config.actionNameAttribute ?? 'data-analytics-name',
83
- trackViewsManually: config.trackViewsManually ?? false,
84
151
  });
85
152
 
86
153
  const reporter: IReporter = {
87
154
  trackEvent: function (event: ReporterEvent): void {
88
- const context = {
155
+ datadogRum.addAction(event.message, {
89
156
  level: event.level,
90
157
  ...event.metadata,
91
158
  ...event.tags,
92
159
  ...event.metrics,
93
- };
94
- datadogRum.addAction(event.message, context);
95
- datadogLogs.logger?.log(event.message, context, getDatadogStatusType(event.level ?? LogLevel.Info));
160
+ });
96
161
  },
97
162
  addBreadcrumb: function (breadcrumb: ReporterBreadcrumb): void {
98
- const context = {
163
+ datadogRum.addAction(breadcrumb.message, {
99
164
  ...breadcrumb.metadata,
100
165
  category: breadcrumb.category,
101
- };
102
- datadogRum.addAction(breadcrumb.message, context);
166
+ });
103
167
  },
104
168
  addMetadata: function (metadata: Metadata): void {
105
169
  for (const [key, value] of Object.entries(metadata)) {
106
170
  if (value !== null) {
107
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.
108
175
  datadogLogs.addLoggerGlobalContext(key, value);
109
176
  } else {
110
177
  datadogRum.removeRumGlobalContext(key);
178
+
179
+ // But this is valuable for logs ingested outside of the browser logger.
111
180
  datadogLogs.removeLoggerGlobalContext(key);
112
181
  }
113
182
  }
@@ -119,16 +188,8 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
119
188
  email: user.email,
120
189
  name: user.name ?? user.email,
121
190
  });
122
-
123
- datadogLogs.addLoggerGlobalContext('user_id', user.id);
124
- datadogLogs.addLoggerGlobalContext('user_email', user.email);
125
- datadogLogs.addLoggerGlobalContext('user_username', user.username);
126
191
  } else {
127
192
  datadogRum.removeUser();
128
-
129
- datadogLogs.removeLoggerGlobalContext('user_id');
130
- datadogLogs.removeLoggerGlobalContext('user_email');
131
- datadogLogs.removeLoggerGlobalContext('user_username');
132
193
  }
133
194
  },
134
195
  setRouteName: function (routeName: string): void {
@@ -142,7 +203,6 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
142
203
  }
143
204
  },
144
205
  reportError: function (error: ReportError, metadata?: Metadata): void {
145
- // Note, datadog should pick up the console error above
146
206
  datadogRum.addError(error, metadata);
147
207
  },
148
208
  recordSession: function (): void {
@@ -154,55 +214,3 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
154
214
  };
155
215
  return reporter;
156
216
  }
157
-
158
- export function datadogLogger(name?: string, options?: { metadata?: Metadata }): ILogger {
159
- const loggerName = name ?? 'root';
160
- const ddLogger = datadogLogs.createLogger(loggerName, {
161
- context: options?.metadata,
162
- });
163
- // Send to datadog and console.
164
- ddLogger.setHandler([HandlerType.http, HandlerType.console]);
165
-
166
- const logger: ILogger = {
167
- startTimer: function (): Profiler {
168
- const start = new Date();
169
-
170
- return {
171
- logger: this,
172
- done: ({ message, level } = {}) => {
173
- const duration = new Date().getTime() - start.getTime();
174
- logger[level ?? LogLevel.Info](message ?? 'Timer Completed', { duration });
175
- return true;
176
- },
177
- };
178
- },
179
- child: function (metadata?: Metadata, name?: string): ILogger {
180
- return datadogLogger(name ?? `${loggerName}.child`, metadata);
181
- },
182
- log: (level: LogLevel, message: string, metadata?: any) => {
183
- switch (level) {
184
- case LogLevel.Debug:
185
- ddLogger.debug(message, metadata);
186
- break;
187
-
188
- case LogLevel.Info:
189
- ddLogger.info(message, metadata);
190
- break;
191
-
192
- case LogLevel.Warn:
193
- ddLogger.warn(message, metadata);
194
- break;
195
-
196
- case LogLevel.Error:
197
- ddLogger.error(message, metadata);
198
- break;
199
- }
200
- return logger;
201
- },
202
- debug: (message: string, metadata?: any) => logger.log(LogLevel.Debug, message, metadata),
203
- info: (message: string, metadata?: any) => logger.log(LogLevel.Info, message, metadata),
204
- warn: (message: string, metadata?: any) => logger.log(LogLevel.Warn, message, metadata),
205
- error: (message: string, metadata?: any) => logger.log(LogLevel.Error, message, metadata),
206
- };
207
- return logger;
208
- }