@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.
- package/README.md +289 -18
- package/lib/index.d.ts +10 -24
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +45 -116
- package/lib/index.js.map +1 -1
- package/lib/logger/consoleTransport.d.ts +37 -0
- package/lib/logger/consoleTransport.d.ts.map +1 -0
- package/lib/logger/consoleTransport.js +81 -0
- package/lib/logger/consoleTransport.js.map +1 -0
- package/lib/logger/datadogTransport.d.ts +8 -0
- package/lib/logger/datadogTransport.d.ts.map +1 -0
- package/lib/logger/datadogTransport.js +21 -0
- package/lib/logger/datadogTransport.js.map +1 -0
- package/lib/logger/index.d.ts +16 -0
- package/lib/logger/index.d.ts.map +1 -0
- package/lib/logger/index.js +148 -0
- package/lib/logger/index.js.map +1 -0
- package/lib/logger/index.test.d.ts +2 -0
- package/lib/logger/index.test.d.ts.map +1 -0
- package/lib/logger/index.test.js +60 -0
- package/lib/logger/index.test.js.map +1 -0
- package/lib/logger/utils.d.ts +15 -0
- package/lib/logger/utils.d.ts.map +1 -0
- package/lib/logger/utils.js +32 -0
- package/lib/logger/utils.js.map +1 -0
- package/lib/reporters/amplifyReporter.d.ts +40 -14
- package/lib/reporters/amplifyReporter.d.ts.map +1 -1
- package/lib/reporters/amplifyReporter.js +15 -23
- package/lib/reporters/amplifyReporter.js.map +1 -1
- package/lib/reporters/amplifyReporter.test.js +0 -11
- package/lib/reporters/amplifyReporter.test.js.map +1 -1
- package/lib/reporters/datadogReporter.d.ts +64 -14
- package/lib/reporters/datadogReporter.d.ts.map +1 -1
- package/lib/reporters/datadogReporter.js +46 -101
- package/lib/reporters/datadogReporter.js.map +1 -1
- package/lib/reporters/gtmReporter.d.ts +3 -2
- package/lib/reporters/gtmReporter.d.ts.map +1 -1
- package/lib/reporters/gtmReporter.js +20 -6
- package/lib/reporters/gtmReporter.js.map +1 -1
- package/lib/reporters/index.d.ts +66 -28
- package/lib/reporters/index.d.ts.map +1 -1
- package/lib/reporters/index.js +210 -0
- package/lib/reporters/index.js.map +1 -1
- package/lib/reporters/logReporter.d.ts +35 -0
- package/lib/reporters/logReporter.d.ts.map +1 -0
- package/lib/reporters/logReporter.js +62 -0
- package/lib/reporters/logReporter.js.map +1 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +19 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/logger.d.ts +78 -0
- package/lib/types/logger.d.ts.map +1 -0
- package/lib/{types.js → types/logger.js} +1 -1
- package/lib/types/logger.js.map +1 -0
- package/lib/types/reporter.d.ts +155 -0
- package/lib/types/reporter.d.ts.map +1 -0
- package/lib/types/reporter.js +3 -0
- package/lib/types/reporter.js.map +1 -0
- package/lib/utils.d.ts +9 -4
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +28 -43
- package/lib/utils.js.map +1 -1
- package/lib/utils.test.d.ts +2 -0
- package/lib/utils.test.d.ts.map +1 -0
- package/lib/utils.test.js +32 -0
- package/lib/utils.test.js.map +1 -0
- package/package.json +5 -4
- package/src/index.ts +41 -120
- package/src/logger/consoleTransport.ts +101 -0
- package/src/logger/datadogTransport.ts +20 -0
- package/src/logger/index.test.ts +68 -0
- package/src/logger/index.ts +139 -0
- package/src/logger/utils.ts +28 -0
- package/src/reporters/amplifyReporter.test.ts +1 -14
- package/src/reporters/amplifyReporter.ts +65 -36
- package/src/reporters/datadogReporter.ts +123 -115
- package/src/reporters/gtmReporter.ts +35 -8
- package/src/reporters/index.ts +208 -24
- package/src/reporters/logReporter.ts +86 -0
- package/src/types/index.ts +2 -0
- package/src/types/logger.ts +85 -0
- package/src/types/reporter.ts +167 -0
- package/src/utils.test.ts +32 -0
- package/src/utils.ts +39 -49
- package/lib/types.d.ts +0 -48
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- 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
|
|
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 {
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Modify how the reporter sends events to Amplify.
|
|
65
|
+
*/
|
|
19
66
|
buffering?: AmplifyReporterBufferingConfig;
|
|
20
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
13
|
+
import { logTransports } from '../logger';
|
|
14
|
+
import { DatadogLogTransportConfig, datadogTransport } from '../logger/datadogTransport';
|
|
7
15
|
|
|
8
|
-
export
|
|
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
|
-
|
|
25
|
+
/** Optional proxy URL */
|
|
13
26
|
proxyUrl?: string;
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|