@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.
- package/README.md +212 -67
- package/lib/index.d.ts +6 -76
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +42 -186
- 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 +2 -2
- package/lib/reporters/amplifyReporter.d.ts.map +1 -1
- package/lib/reporters/amplifyReporter.js +13 -14
- package/lib/reporters/amplifyReporter.js.map +1 -1
- package/lib/reporters/datadogReporter.d.ts +7 -5
- package/lib/reporters/datadogReporter.d.ts.map +1 -1
- package/lib/reporters/datadogReporter.js +37 -100
- package/lib/reporters/datadogReporter.js.map +1 -1
- package/lib/reporters/gtmReporter.d.ts.map +1 -1
- package/lib/reporters/gtmReporter.js +2 -7
- package/lib/reporters/gtmReporter.js.map +1 -1
- package/lib/reporters/index.d.ts +66 -3
- package/lib/reporters/index.d.ts.map +1 -1
- package/lib/reporters/index.js +210 -17
- 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/logger.d.ts +43 -2
- package/lib/types/logger.d.ts.map +1 -1
- package/lib/types/logger.js.map +1 -1
- package/lib/types/reporter.d.ts +58 -6
- package/lib/types/reporter.d.ts.map +1 -1
- package/lib/utils.d.ts +9 -8
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +28 -48
- package/lib/utils.js.map +1 -1
- package/lib/utils.test.js +18 -5
- package/lib/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +38 -208
- 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.ts +15 -16
- package/src/reporters/datadogReporter.ts +48 -116
- package/src/reporters/gtmReporter.ts +2 -7
- package/src/reporters/index.ts +216 -3
- package/src/reporters/logReporter.ts +86 -0
- package/src/types/logger.ts +49 -4
- package/src/types/reporter.ts +66 -6
- package/src/utils.test.ts +20 -6
- package/src/utils.ts +37 -62
package/src/index.ts
CHANGED
|
@@ -1,234 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ReportError,
|
|
9
|
-
ReportUser,
|
|
10
|
-
ServiceInfo,
|
|
11
|
-
TrackedReporterEvent,
|
|
12
|
-
} from './types';
|
|
13
|
-
import { consoleLogger } from './utils';
|
|
14
|
-
import {
|
|
15
|
-
datadogLogger,
|
|
16
|
-
datadogReporter,
|
|
17
|
-
DatadogReporterConfig,
|
|
18
|
-
gtmReporter,
|
|
19
|
-
GTMReporterConfig,
|
|
20
|
-
amplifyReporter,
|
|
21
|
-
AmplifyReporterConfig,
|
|
22
|
-
} from './reporters';
|
|
1
|
+
import { ReporterConfigurations, ServiceInfo } from './types';
|
|
2
|
+
import { setLogLevel } from './logger';
|
|
3
|
+
import { addMetadata, reporters, setEventLevel } from './reporters';
|
|
4
|
+
import { gtmReporter } from './reporters/gtmReporter';
|
|
5
|
+
import { amplifyReporter } from './reporters/amplifyReporter';
|
|
6
|
+
import { datadogReporter } from './reporters/datadogReporter';
|
|
7
|
+
import { logReporter } from './reporters/logReporter';
|
|
23
8
|
|
|
24
9
|
export * from './types';
|
|
10
|
+
export * from './reporters';
|
|
11
|
+
export * from './logger';
|
|
25
12
|
|
|
26
|
-
export
|
|
13
|
+
export type LoggerConfig = ServiceInfo & { reporters?: ReporterConfigurations };
|
|
27
14
|
|
|
28
|
-
export type ReporterType = 'datadog' | 'gtm' | 'amplify';
|
|
29
|
-
|
|
30
|
-
export type ReporterConfig = ServiceInfo & {
|
|
31
|
-
// Datadog
|
|
32
|
-
datadog?: DatadogReporterConfig;
|
|
33
|
-
|
|
34
|
-
// Amplify/Pinpoint
|
|
35
|
-
amplify?: AmplifyReporterConfig;
|
|
36
|
-
|
|
37
|
-
// Google Tag Manager
|
|
38
|
-
gtm?: true | GTMReporterConfig;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const reporters: Partial<Record<ReporterType, IReporter>> = {};
|
|
42
15
|
let initialized = false;
|
|
43
|
-
let ddInitialized = false;
|
|
44
16
|
|
|
45
17
|
/**
|
|
46
18
|
* Initializes the logger and reporters.
|
|
47
19
|
* @param config Reporter config options.
|
|
48
20
|
*/
|
|
49
|
-
export function init(config:
|
|
50
|
-
initialized
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
reporters['datadog'] = datadogReporter(config, config.datadog);
|
|
54
|
-
ddInitialized = true;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (config.amplify) {
|
|
58
|
-
reporters['amplify'] = amplifyReporter(config, config.amplify);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (config.gtm) {
|
|
62
|
-
reporters['gtm'] = gtmReporter(config, typeof config.gtm === 'boolean' ? {} : config.gtm);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (config.defaultMetadata) {
|
|
66
|
-
for (const reporter of Object.values(reporters)) {
|
|
67
|
-
reporter.addMetadata(config.defaultMetadata);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
logger = createLogger('Reporter');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Gets reporters, optionally with filters.
|
|
76
|
-
* @param filters Filters to remove or specify specific reporters.
|
|
77
|
-
* @returns Selected reporters, or all registered reporters.
|
|
78
|
-
*/
|
|
79
|
-
function getReporters(filters?: ReporterFilters) {
|
|
80
|
-
let result = Object.entries(reporters);
|
|
81
|
-
if (filters?.excludeReporters) {
|
|
82
|
-
result = result.filter(([key]) => !filters?.excludeReporters?.includes(key as ReporterType));
|
|
83
|
-
}
|
|
84
|
-
if (filters?.toReporters) {
|
|
85
|
-
result = result.filter(([key]) => filters?.toReporters?.includes(key as ReporterType));
|
|
86
|
-
}
|
|
87
|
-
return result.map(([, reporter]) => reporter);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Tracks an Analytics event.
|
|
92
|
-
*/
|
|
93
|
-
export function trackEvent(event: ReporterEvent): void {
|
|
94
|
-
for (const reporter of getReporters(event)) {
|
|
95
|
-
reporter.trackEvent(event);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Tracks an Analytics event, recording the time since the last event, if available.
|
|
101
|
-
*/
|
|
102
|
-
export function trackEventSinceLastAction(event: ReporterEvent): void {
|
|
103
|
-
const lastEvent = getLastTrackedEvent();
|
|
104
|
-
if (lastEvent) {
|
|
105
|
-
const duration = new Date().getTime() - lastEvent.occurred.getTime();
|
|
106
|
-
trackEvent({
|
|
107
|
-
...event,
|
|
108
|
-
metadata: {
|
|
109
|
-
...event.metadata,
|
|
110
|
-
lastEventName: lastEvent.message,
|
|
111
|
-
timeSinceLastEvent: duration,
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
} else {
|
|
115
|
-
trackEvent(event);
|
|
116
|
-
}
|
|
117
|
-
sessionStorage.setItem('loggerLastEvent', JSON.stringify({ ...event, occurred: new Date() }));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Gets the last tracked event, if available.
|
|
122
|
-
*/
|
|
123
|
-
export function getLastTrackedEvent(): TrackedReporterEvent | null {
|
|
124
|
-
const eventStr = sessionStorage.getItem('loggerLastEvent');
|
|
125
|
-
if (!eventStr) return null;
|
|
126
|
-
|
|
127
|
-
const event: TrackedReporterEvent = JSON.parse(eventStr);
|
|
128
|
-
event.occurred = new Date(event.occurred);
|
|
129
|
-
return event;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Breadcrumbs to create a trail of events that happened prior to an issue.
|
|
134
|
-
* These events are very similar to traditional logs, but can record more rich structured data.
|
|
135
|
-
*/
|
|
136
|
-
export function addBreadcrumb(breadcrumb: ReporterBreadcrumb): void {
|
|
137
|
-
for (const reporter of getReporters(breadcrumb)) {
|
|
138
|
-
reporter.addBreadcrumb(breadcrumb);
|
|
21
|
+
export function init(config: LoggerConfig) {
|
|
22
|
+
// Some reporters don't like being initialized multiple times.
|
|
23
|
+
if (initialized) {
|
|
24
|
+
return;
|
|
139
25
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Adds global metadata to all events.
|
|
144
|
-
* @param metadata Metadata to add.
|
|
145
|
-
* @param filters Optional filters to specify which reporters to add metadata to.
|
|
146
|
-
*/
|
|
147
|
-
export function addMetadata(metadata: Metadata, filters?: ReporterFilters): void {
|
|
148
|
-
for (const reporter of getReporters(filters)) {
|
|
149
|
-
reporter.addMetadata(metadata);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
26
|
+
initialized = true;
|
|
152
27
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
*/
|
|
158
|
-
export function setUser(user: ReportUser | null, filters?: ReporterFilters): void {
|
|
159
|
-
for (const reporter of getReporters(filters)) {
|
|
160
|
-
reporter.setUser(user);
|
|
28
|
+
// Log Reporter enabled by default.
|
|
29
|
+
const logReporterConfig = config.reporters?.log ?? true;
|
|
30
|
+
if (logReporterConfig !== false) {
|
|
31
|
+
reporters['log'] = logReporter(config, typeof logReporterConfig === 'boolean' ? {} : logReporterConfig);
|
|
161
32
|
}
|
|
162
|
-
}
|
|
163
33
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* Some Analytics services use this differentiate SPA Route vs Page changes.
|
|
167
|
-
* @param routeName Name of the Route.
|
|
168
|
-
* @param filters Optional filters to specify which reporters to set the route name for.
|
|
169
|
-
*/
|
|
170
|
-
export function setRouteName(routeName: string, filters?: ReporterFilters): void {
|
|
171
|
-
for (const reporter of getReporters(filters)) {
|
|
172
|
-
reporter.setRouteName(routeName);
|
|
34
|
+
if (config.reporters?.datadog) {
|
|
35
|
+
reporters['datadog'] = datadogReporter(config, config.reporters.datadog);
|
|
173
36
|
}
|
|
174
|
-
}
|
|
175
37
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
* @param pageName Name of the Page.
|
|
179
|
-
* @param filters Optional filters to specify which reporters to set the page name for.
|
|
180
|
-
*/
|
|
181
|
-
export function setPageName(pageName: string, filters?: ReporterFilters): void {
|
|
182
|
-
for (const reporter of getReporters(filters)) {
|
|
183
|
-
reporter.setPageName(pageName);
|
|
38
|
+
if (config.reporters?.amplify) {
|
|
39
|
+
reporters['amplify'] = amplifyReporter(config, config.reporters.amplify);
|
|
184
40
|
}
|
|
185
|
-
}
|
|
186
41
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* @param error Error data.
|
|
190
|
-
* @param metadata Metadata to add to the error.
|
|
191
|
-
* @param filters Optional filters to specify which reporters to report the error to.
|
|
192
|
-
*/
|
|
193
|
-
export function reportError(error: ReportError, metadata?: Metadata, filters?: ReporterFilters): void {
|
|
194
|
-
for (const reporter of getReporters(filters)) {
|
|
195
|
-
reporter.reportError?.(error, metadata);
|
|
42
|
+
if (config.reporters?.gtm) {
|
|
43
|
+
reporters['gtm'] = gtmReporter(config, typeof config.reporters.gtm === 'boolean' ? {} : config.reporters.gtm);
|
|
196
44
|
}
|
|
197
|
-
}
|
|
198
45
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
reporter.recordSession?.();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
46
|
+
const defaultMetadata = {
|
|
47
|
+
...(config.defaultMetadata ?? {}),
|
|
48
|
+
service: config.service,
|
|
49
|
+
environment: config.environment,
|
|
50
|
+
version: config.version,
|
|
51
|
+
};
|
|
208
52
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
*/
|
|
213
|
-
export function recordSessionStop(filters?: ReporterFilters): void {
|
|
214
|
-
for (const reporter of getReporters(filters)) {
|
|
215
|
-
reporter.recordSessionStop?.();
|
|
53
|
+
// Sets the global log level, if specified.
|
|
54
|
+
if (config.logLevel) {
|
|
55
|
+
setLogLevel(config.logLevel);
|
|
216
56
|
}
|
|
217
|
-
}
|
|
218
57
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
* @param options Logger configuration options.
|
|
223
|
-
*/
|
|
224
|
-
export function createLogger(name?: string, options?: { metadata?: Metadata }): ILogger {
|
|
225
|
-
if (!initialized) {
|
|
226
|
-
throw new Error('You must call init on BrowserLogger before creating a logger');
|
|
58
|
+
// Sets the global event level, if specified.
|
|
59
|
+
if (config.eventLevel) {
|
|
60
|
+
setEventLevel(config.eventLevel);
|
|
227
61
|
}
|
|
228
62
|
|
|
229
|
-
|
|
230
|
-
return datadogLogger(name, options);
|
|
231
|
-
} else {
|
|
232
|
-
return consoleLogger(name, options);
|
|
233
|
-
}
|
|
63
|
+
addMetadata(defaultMetadata);
|
|
234
64
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ILogTransport, LogLevel, LogTransportConfigBase } from '../types';
|
|
2
|
+
|
|
3
|
+
// from browser-util-inspect
|
|
4
|
+
const colors = {
|
|
5
|
+
bold: [1, 22],
|
|
6
|
+
italic: [3, 23],
|
|
7
|
+
underline: [4, 24],
|
|
8
|
+
inverse: [7, 27],
|
|
9
|
+
white: [37, 39],
|
|
10
|
+
grey: [90, 39],
|
|
11
|
+
black: [30, 39],
|
|
12
|
+
blue: [34, 39],
|
|
13
|
+
cyan: [36, 39],
|
|
14
|
+
green: [32, 39],
|
|
15
|
+
magenta: [35, 39],
|
|
16
|
+
red: [31, 39],
|
|
17
|
+
yellow: [33, 39],
|
|
18
|
+
};
|
|
19
|
+
export type ColorizeColor = keyof typeof colors;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Colorize a string with a specified color.
|
|
23
|
+
* @param str String to colorize
|
|
24
|
+
* @param color Color to colorize the string with
|
|
25
|
+
* @returns Colorized string
|
|
26
|
+
*/
|
|
27
|
+
export function colorize(str: string, color: ColorizeColor) {
|
|
28
|
+
const colorData = colors[color];
|
|
29
|
+
return '\u001b[' + colorData[0] + 'm' + str + '\u001b[' + colorData[1] + 'm';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Gets a stack trace from metadata or accompanying error.
|
|
34
|
+
*/
|
|
35
|
+
function getStackTrace(data: any) {
|
|
36
|
+
const stack = data?.err?.stack || data.stack || data['error.stack'];
|
|
37
|
+
delete data.stack;
|
|
38
|
+
return stack ?? '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ConsoleLogTransportConfig extends LogTransportConfigBase {
|
|
42
|
+
/**
|
|
43
|
+
* Add colors to log messages.
|
|
44
|
+
*/
|
|
45
|
+
colorize?: false | typeof colorize;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Gets the color for the log level.
|
|
49
|
+
*/
|
|
50
|
+
getLogLevelColor?: (level: LogLevel) => ColorizeColor;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function consoleTransport(config: ConsoleLogTransportConfig = {}) {
|
|
54
|
+
// Default ignore patterns
|
|
55
|
+
config.ignoreMetadataPatterns ??= ['application', 'service', 'environment', 'version'];
|
|
56
|
+
|
|
57
|
+
// Get the colorizer function, or a no-op if not enabled.
|
|
58
|
+
const colorizer = config.colorize !== false ? config.colorize ?? colorize : (str: string) => str;
|
|
59
|
+
|
|
60
|
+
// Get the log level color function, or use the default colors.
|
|
61
|
+
const getLogLevelColor =
|
|
62
|
+
config.getLogLevelColor ??
|
|
63
|
+
((level) => {
|
|
64
|
+
return {
|
|
65
|
+
error: 'red',
|
|
66
|
+
warn: 'yellow',
|
|
67
|
+
info: 'cyan',
|
|
68
|
+
debug: 'green',
|
|
69
|
+
}[level] as ColorizeColor;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const transport: ILogTransport & ConsoleLogTransportConfig = {
|
|
73
|
+
...config,
|
|
74
|
+
log: ({ level, message, timestamp, splat, ...metadata }) => {
|
|
75
|
+
let splatData = splat ?? [];
|
|
76
|
+
const stackTrace = getStackTrace(metadata);
|
|
77
|
+
|
|
78
|
+
const strippedInfo: any = { ...metadata };
|
|
79
|
+
delete strippedInfo['level'];
|
|
80
|
+
delete strippedInfo['message'];
|
|
81
|
+
delete strippedInfo['splat'];
|
|
82
|
+
delete strippedInfo['timestamp'];
|
|
83
|
+
delete strippedInfo['stack'];
|
|
84
|
+
|
|
85
|
+
if (Object.keys(strippedInfo).length > 0) {
|
|
86
|
+
splatData = [strippedInfo, ...splatData];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (message) {
|
|
90
|
+
splatData = [message, ...splatData];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (stackTrace) {
|
|
94
|
+
splatData = [...splatData, stackTrace];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`${colorizer(timestamp, 'magenta')} ${colorizer(level, getLogLevelColor(level))}: `, ...splatData);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
return transport;
|
|
101
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ILogTransport, LogTransportConfigBase } from '../types';
|
|
2
|
+
import { getLogMessage } from './utils';
|
|
3
|
+
import { datadogLogs } from '@datadog/browser-logs';
|
|
4
|
+
|
|
5
|
+
export type DatadogLogTransportConfig = LogTransportConfigBase;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A transport that sends logs to Datadog.
|
|
9
|
+
* Note: This requires the datadog reporter to be initialized for it to work.
|
|
10
|
+
*/
|
|
11
|
+
export function datadogTransport(config: DatadogLogTransportConfig = {}) {
|
|
12
|
+
const transport: ILogTransport = {
|
|
13
|
+
...config,
|
|
14
|
+
log: ({ level, message, ...metadata }) => {
|
|
15
|
+
// Datadog expects a string message.
|
|
16
|
+
datadogLogs.logger[level](getLogMessage(message), metadata);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
return transport;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createLogger, setLogLevel as setGlobalLogLevel, logTransports, consoleTransport } from '.';
|
|
2
|
+
import { init } from '..';
|
|
3
|
+
import { addMetadata, setUser } from '../reporters';
|
|
4
|
+
import { LogLevel } from '../types';
|
|
5
|
+
|
|
6
|
+
// This isn't actually asserting anything.
|
|
7
|
+
// But it ensures that the logs look correct, and no errors get thrown.
|
|
8
|
+
describe('logger', () => {
|
|
9
|
+
const logger = createLogger({
|
|
10
|
+
metadata: {
|
|
11
|
+
service: 'test',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Manually set the log transports.
|
|
16
|
+
logTransports.length = 0;
|
|
17
|
+
const transport = consoleTransport({
|
|
18
|
+
// So we can see everything.
|
|
19
|
+
ignoreMetadataPatterns: [],
|
|
20
|
+
});
|
|
21
|
+
logTransports.push(transport);
|
|
22
|
+
|
|
23
|
+
it('should log', async () => {
|
|
24
|
+
logger.log({
|
|
25
|
+
level: 'info',
|
|
26
|
+
message: 'data',
|
|
27
|
+
});
|
|
28
|
+
logger.log({ data: 'obj' });
|
|
29
|
+
logger.log(LogLevel.Debug, 'level, message', { data: 'obj' });
|
|
30
|
+
logger.log(LogLevel.Debug, 'level, message, metadata', { data: 'obj' });
|
|
31
|
+
|
|
32
|
+
init({
|
|
33
|
+
service: 'test-service',
|
|
34
|
+
environment: 'test',
|
|
35
|
+
version: 'no-version',
|
|
36
|
+
defaultMetadata: {
|
|
37
|
+
application: 'test',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const infoLogger = logger.child({ isLevel: 'info' });
|
|
42
|
+
infoLogger.info('info call, message');
|
|
43
|
+
infoLogger.info('info call, message, metadata', { data: 'metadataobj' });
|
|
44
|
+
|
|
45
|
+
const timer = logger.startTimer();
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
47
|
+
timer.done({ message: 'timer done', metadata: { id: 1 } });
|
|
48
|
+
|
|
49
|
+
setGlobalLogLevel(LogLevel.Info);
|
|
50
|
+
addMetadata({ global: 'metadata' });
|
|
51
|
+
|
|
52
|
+
logger.debug('You should not see this');
|
|
53
|
+
logger.info('You should see this with { global: "metadata" }');
|
|
54
|
+
|
|
55
|
+
setGlobalLogLevel(null);
|
|
56
|
+
setUser({
|
|
57
|
+
id: '123',
|
|
58
|
+
email: 'test@example.com',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
logger.debug('debug call, user metadata');
|
|
62
|
+
|
|
63
|
+
transport.logLevel = LogLevel.Info;
|
|
64
|
+
logger.debug('You should not see this');
|
|
65
|
+
logger.info('You should see this');
|
|
66
|
+
transport.logLevel = undefined;
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -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
|
+
}
|