@crimson-education/browser-logger 5.0.0-beta.19 → 5.0.0-beta.20
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/lib/logger/index.test.d.ts +2 -0
- package/lib/logger/index.test.d.ts.map +1 -0
- package/lib/logger/index.test.js +58 -0
- package/lib/logger/index.test.js.map +1 -0
- package/lib/reporters/amplifyReporter.d.ts.map +1 -1
- package/lib/reporters/amplifyReporter.js +69 -38
- package/lib/reporters/amplifyReporter.js.map +1 -1
- package/lib/reporters/amplifyReporter.test.d.ts +2 -0
- package/lib/reporters/amplifyReporter.test.d.ts.map +1 -0
- package/lib/reporters/amplifyReporter.test.js +48 -0
- package/lib/reporters/amplifyReporter.test.js.map +1 -0
- package/lib/utils.test.d.ts +2 -0
- package/lib/utils.test.d.ts.map +1 -0
- package/lib/utils.test.js +30 -0
- package/lib/utils.test.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +64 -0
- 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 +61 -0
- package/src/reporters/amplifyReporter.ts +486 -0
- package/src/reporters/datadogReporter.ts +359 -0
- package/src/reporters/gtmReporter.ts +74 -0
- package/src/reporters/index.ts +232 -0
- 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 +180 -0
- package/src/utils.test.ts +32 -0
- package/src/utils.ts +42 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { asAttributeMap } from './amplifyReporter';
|
|
2
|
+
|
|
3
|
+
describe('amplifyReporter', () => {
|
|
4
|
+
it('should convert all attribute values to arrays of strings', () => {
|
|
5
|
+
const testMetadata = { a: 'a', d: ['e', 'f'] };
|
|
6
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
7
|
+
|
|
8
|
+
expect(attributeMap.a).toEqual(['a']);
|
|
9
|
+
expect(attributeMap.d).toEqual(['e', 'f']);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should handle undefined / null attributes', () => {
|
|
13
|
+
const testMetadata = { a: null, d: undefined };
|
|
14
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
15
|
+
|
|
16
|
+
expect(attributeMap.a).toBeNull();
|
|
17
|
+
expect(attributeMap.d).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should flatten hierarchies', () => {
|
|
21
|
+
const testMetadata = { foo: { bar: 'baz' } };
|
|
22
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
23
|
+
|
|
24
|
+
expect(attributeMap['foo.bar']).toEqual(['baz']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should stringify non-string array members', () => {
|
|
28
|
+
const testMetadata = { foo: 5, bar: [{ baz: 'maz' }] };
|
|
29
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
30
|
+
|
|
31
|
+
expect(attributeMap.foo).toEqual(['5']);
|
|
32
|
+
expect(typeof attributeMap.bar?.[0]).toEqual('string');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should truncate attribute names', () => {
|
|
36
|
+
const testMetadata = { thisIsAVeryVeryLongAttributeNameThatNeedsToBeTruncated: 5 };
|
|
37
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
38
|
+
|
|
39
|
+
expect(attributeMap.___VeryVeryLongAttributeNameThatNeedsToBeTruncated).toEqual(['5']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should truncate attribute values', () => {
|
|
43
|
+
const testMetadata = {
|
|
44
|
+
a: 'ThisIsAVeryLongStringThatNeedsToBeTruncatedTo100CharsOrElseThereWillBeAProblemWithSendingTheBeautifulDataToPinpoint',
|
|
45
|
+
};
|
|
46
|
+
const attributeMap = asAttributeMap(testMetadata);
|
|
47
|
+
|
|
48
|
+
expect(attributeMap.a).toEqual([
|
|
49
|
+
'ThisIsAVeryLongStringThatNeedsToBeTruncatedTo100CharsOrElseThereWillBeAProblemWithSendingTheBeautifu',
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should not group values into arrays when groupValues===false', () => {
|
|
54
|
+
const testMetadata = {
|
|
55
|
+
a: '5',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const attributeMap = asAttributeMap(testMetadata, false);
|
|
59
|
+
expect(attributeMap.a).toEqual('5');
|
|
60
|
+
});
|
|
61
|
+
});
|