Skip to content

Commit a248ff0

Browse files
authored
feat(logger): add CRITICAL log level (#1399)
1 parent 2013ff2 commit a248ff0

File tree

5 files changed

+71
-42
lines changed

5 files changed

+71
-42
lines changed

packages/logger/src/Logger.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ class Logger extends Utility implements ClassThatLogs {
136136
INFO: 12,
137137
WARN: 16,
138138
ERROR: 20,
139-
SILENT: 24,
139+
CRITICAL: 24,
140+
SILENT: 28,
140141
};
141142

142143
private logsSampled: boolean = false;
@@ -212,17 +213,27 @@ class Logger extends Utility implements ClassThatLogs {
212213
};
213214
const parentsPowertoolsLogData = this.getPowertoolLogData();
214215
const childLogger = new Logger(merge(parentsOptions, parentsPowertoolsLogData, options));
215-
216+
216217
const parentsPersistentLogAttributes = this.getPersistentLogAttributes();
217218
childLogger.addPersistentLogAttributes(parentsPersistentLogAttributes);
218-
219+
219220
if (parentsPowertoolsLogData.lambdaContext) {
220221
childLogger.addContext(parentsPowertoolsLogData.lambdaContext as Context);
221222
}
222-
223+
223224
return childLogger;
224225
}
225226

227+
/**
228+
* It prints a log item with level CRITICAL.
229+
*
230+
* @param {LogItemMessage} input
231+
* @param {Error | LogAttributes | string} extraInput
232+
*/
233+
public critical(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
234+
this.processLogItem('CRITICAL', input, extraInput);
235+
}
236+
226237
/**
227238
* It prints a log item with level DEBUG.
228239
*
@@ -645,7 +656,10 @@ class Logger extends Utility implements ClassThatLogs {
645656
private printLog(logLevel: LogLevel, log: LogItem): void {
646657
log.prepareForPrint();
647658

648-
const consoleMethod = logLevel.toLowerCase() as keyof ClassThatLogs;
659+
const consoleMethod =
660+
logLevel === 'CRITICAL' ?
661+
'error' :
662+
logLevel.toLowerCase() as keyof Omit<ClassThatLogs, 'critical'>;
649663

650664
this.console[consoleMethod](JSON.stringify(log.getAttributes(), this.getReplacer(), this.logIndentation));
651665
}

packages/logger/src/types/Log.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@ type LogLevelInfo = 'INFO';
33
type LogLevelWarn = 'WARN';
44
type LogLevelError = 'ERROR';
55
type LogLevelSilent = 'SILENT';
6+
type LogLevelCritical = 'CRITICAL';
67

7-
type LogLevel = LogLevelDebug | Lowercase<LogLevelDebug> | LogLevelInfo | Lowercase<LogLevelInfo> | LogLevelWarn | Lowercase<LogLevelWarn> | LogLevelError | Lowercase<LogLevelError> | LogLevelSilent | Lowercase<LogLevelSilent>;
8+
type LogLevel =
9+
LogLevelDebug |
10+
Lowercase<LogLevelDebug> |
11+
LogLevelInfo |
12+
Lowercase<LogLevelInfo> |
13+
LogLevelWarn |
14+
Lowercase<LogLevelWarn> |
15+
LogLevelError |
16+
Lowercase<LogLevelError> |
17+
LogLevelSilent |
18+
Lowercase<LogLevelSilent> |
19+
LogLevelCritical |
20+
Lowercase<LogLevelCritical>;
821

922
type LogLevelThresholds = {
1023
[key in Uppercase<LogLevel>]: number;

packages/logger/src/types/Logger.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ import { AsyncHandler, LambdaInterface, SyncHandler } from '@aws-lambda-powertoo
22
import { Handler } from 'aws-lambda';
33
import { ConfigServiceInterface } from '../config';
44
import { LogFormatterInterface } from '../formatter';
5-
import { Environment, LogAttributes, LogAttributesWithMessage, LogLevel } from './Log';
5+
import {
6+
Environment,
7+
LogAttributes,
8+
LogAttributesWithMessage,
9+
LogLevel,
10+
} from './Log';
611

712
type ClassThatLogs = {
8-
[key in 'debug' | 'error' | 'info' | 'warn']: (input: LogItemMessage, ...extraInput: LogItemExtraInput) => void;
13+
[key in Exclude<Lowercase<LogLevel>, 'silent'>]: (input: LogItemMessage, ...extraInput: LogItemExtraInput) => void;
914
};
1015

1116
type HandlerOptions = {

packages/logger/tests/unit/Logger.test.ts

+29-33
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import { Console } from 'console';
1616

1717
const mockDate = new Date(1466424490000);
1818
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
19+
const getConsoleMethod = (method: string): keyof Omit<ClassThatLogs, 'critical'> =>
20+
method === 'critical' ?
21+
'error' :
22+
method.toLowerCase() as keyof Omit<ClassThatLogs, 'critical'>;
1923

2024
describe('Class: Logger', () => {
2125

@@ -27,7 +31,8 @@ describe('Class: Logger', () => {
2731
INFO: 12,
2832
WARN: 16,
2933
ERROR: 20,
30-
SILENT: 24,
34+
CRITICAL: 24,
35+
SILENT: 28,
3136
};
3237

3338
beforeEach(() => {
@@ -40,6 +45,7 @@ describe('Class: Logger', () => {
4045
[ 'info', 'DOES', true, 'DOES', true, 'DOES NOT', false, 'DOES NOT', false ],
4146
[ 'warn', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES NOT', false ],
4247
[ 'error', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true ],
48+
[ 'critical', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true ],
4349
])(
4450
'Method: %p',
4551
(
@@ -54,17 +60,17 @@ describe('Class: Logger', () => {
5460
errorPrints,
5561
) => {
5662

57-
describe('Feature: log level', () => {
63+
const methodOfLogger = method as keyof ClassThatLogs;
5864

59-
const methodOfLogger = method as keyof ClassThatLogs;
65+
describe('Feature: log level', () => {
6066

6167
test('when the Logger\'s log level is DEBUG, it ' + debugAction + ' print to stdout', () => {
6268

6369
// Prepare
6470
const logger: Logger = createLogger({
6571
logLevel: 'DEBUG',
6672
});
67-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
73+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(method)).mockImplementation();
6874

6975
// Act
7076
logger[methodOfLogger]('foo');
@@ -89,7 +95,7 @@ describe('Class: Logger', () => {
8995
const logger: Logger = createLogger({
9096
logLevel: 'INFO',
9197
});
92-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
98+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
9399

94100
// Act
95101
logger[methodOfLogger]('foo');
@@ -114,7 +120,7 @@ describe('Class: Logger', () => {
114120
const logger: Logger = createLogger({
115121
logLevel: 'WARN',
116122
});
117-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
123+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
118124

119125
// Act
120126
logger[methodOfLogger]('foo');
@@ -139,7 +145,7 @@ describe('Class: Logger', () => {
139145
const logger: Logger = createLogger({
140146
logLevel: 'ERROR',
141147
});
142-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
148+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
143149

144150
// Act
145151
logger[methodOfLogger]('foo');
@@ -164,7 +170,7 @@ describe('Class: Logger', () => {
164170
const logger: Logger = createLogger({
165171
logLevel: 'SILENT',
166172
});
167-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
173+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
168174

169175
// Act
170176
logger[methodOfLogger]('foo');
@@ -178,7 +184,7 @@ describe('Class: Logger', () => {
178184
// Prepare
179185
process.env.LOG_LEVEL = methodOfLogger.toUpperCase();
180186
const logger = new Logger();
181-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
187+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
182188

183189
// Act
184190
logger[methodOfLogger]('foo');
@@ -197,16 +203,14 @@ describe('Class: Logger', () => {
197203

198204
describe('Feature: sample rate', () => {
199205

200-
const methodOfLogger = method as keyof ClassThatLogs;
201-
202206
test('when the Logger\'s log level is higher and the current Lambda invocation IS NOT sampled for logging, it DOES NOT print to stdout', () => {
203207

204208
// Prepare
205209
const logger: Logger = createLogger({
206210
logLevel: 'SILENT',
207211
sampleRateValue: 0,
208212
});
209-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
213+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
210214

211215
// Act
212216
if (logger[methodOfLogger]) {
@@ -224,7 +228,7 @@ describe('Class: Logger', () => {
224228
logLevel: 'SILENT',
225229
sampleRateValue: 1,
226230
});
227-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
231+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
228232

229233
// Act
230234
if (logger[methodOfLogger]) {
@@ -247,13 +251,11 @@ describe('Class: Logger', () => {
247251

248252
describe('Feature: inject context', () => {
249253

250-
const methodOfLogger = method as keyof ClassThatLogs;
251-
252254
test('when the Lambda context is not captured and a string is passed as log message, it should print a valid ' + method.toUpperCase() + ' log', () => {
253255

254256
// Prepare
255257
const logger: Logger = createLogger();
256-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
258+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
257259

258260
// Act
259261
if (logger[methodOfLogger]) {
@@ -279,7 +281,7 @@ describe('Class: Logger', () => {
279281
logLevel: 'DEBUG',
280282
});
281283
logger.addContext(context);
282-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
284+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
283285

284286
// Act
285287
if (logger[methodOfLogger]) {
@@ -307,15 +309,13 @@ describe('Class: Logger', () => {
307309

308310
describe('Feature: ephemeral log attributes', () => {
309311

310-
const methodOfLogger = method as keyof ClassThatLogs;
311-
312312
test('when added, they should appear in that log item only', () => {
313313

314314
// Prepare
315315
const logger: Logger = createLogger({
316316
logLevel: 'DEBUG',
317317
});
318-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
318+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
319319

320320
interface NestedObject { bool: boolean; str: string; num: number; err: Error }
321321
interface ArbitraryObject<TNested> { value: 'CUSTOM' | 'USER_DEFINED'; nested: TNested }
@@ -444,8 +444,6 @@ describe('Class: Logger', () => {
444444

445445
describe('Feature: persistent log attributes', () => {
446446

447-
const methodOfLogger = method as keyof ClassThatLogs;
448-
449447
test('when persistent log attributes are added to the Logger instance, they should appear in all logs printed by the instance', () => {
450448

451449
// Prepare
@@ -456,7 +454,7 @@ describe('Class: Logger', () => {
456454
aws_region: 'eu-west-1',
457455
},
458456
});
459-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
457+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
460458

461459
// Act
462460
if (logger[methodOfLogger]) {
@@ -481,15 +479,13 @@ describe('Class: Logger', () => {
481479

482480
describe('Feature: X-Ray Trace ID injection', () => {
483481

484-
const methodOfLogger = method as keyof ClassThatLogs;
485-
486482
test('when the `_X_AMZN_TRACE_ID` environment variable is set it parses it correctly and adds the Trace ID to the log', () => {
487483

488484
// Prepare
489485
const logger: Logger = createLogger({
490486
logLevel: 'DEBUG',
491487
});
492-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
488+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
493489

494490
// Act
495491
if (logger[methodOfLogger]) {
@@ -515,7 +511,7 @@ describe('Class: Logger', () => {
515511
const logger: Logger = createLogger({
516512
logLevel: 'DEBUG',
517513
});
518-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
514+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
519515

520516
// Act
521517
if (logger[methodOfLogger]) {
@@ -537,15 +533,13 @@ describe('Class: Logger', () => {
537533

538534
describe('Feature: handle safely unexpected errors', () => {
539535

540-
const methodOfLogger = method as keyof ClassThatLogs;
541-
542536
test('when a logged item references itself, the logger ignores the keys that cause a circular reference', () => {
543537

544538
// Prepare
545539
const logger: Logger = createLogger({
546540
logLevel: 'DEBUG',
547541
});
548-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
542+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
549543
const circularObject = {
550544
foo: 'bar',
551545
self: {},
@@ -581,7 +575,7 @@ describe('Class: Logger', () => {
581575

582576
// Prepare
583577
const logger = new Logger();
584-
jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
578+
jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
585579
const message = `This is an ${methodOfLogger} log with BigInt value`;
586580
const logItem = { value: BigInt(42) };
587581
const errorMessage = 'Do not know how to serialize a BigInt';
@@ -595,7 +589,7 @@ describe('Class: Logger', () => {
595589

596590
// Prepare
597591
const logger = new Logger();
598-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
592+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
599593
const message = `This is an ${methodOfLogger} log with BigInt value`;
600594
const logItem = { value: BigInt(42) };
601595

@@ -619,7 +613,7 @@ describe('Class: Logger', () => {
619613

620614
// Prepare
621615
const logger = new Logger();
622-
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
616+
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
623617
const message = `This is an ${methodOfLogger} log with empty, null, and undefined values`;
624618
const logItem = { value: 42, emptyValue: '', undefinedValue: undefined, nullValue: null };
625619

@@ -1050,6 +1044,7 @@ describe('Class: Logger', () => {
10501044
biz: 'baz'
10511045
}
10521046
});
1047+
jest.spyOn(logger['console'], 'debug').mockImplementation();
10531048
class LambdaFunction implements LambdaInterface {
10541049

10551050
@logger.injectLambdaContext({ clearState: true })
@@ -1091,6 +1086,7 @@ describe('Class: Logger', () => {
10911086
biz: 'baz'
10921087
}
10931088
});
1089+
jest.spyOn(logger['console'], 'debug').mockImplementation();
10941090
class LambdaFunction implements LambdaInterface {
10951091

10961092
@logger.injectLambdaContext({ clearState: true })

packages/logger/tests/unit/helpers.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ describe('Helper: createLogger function', () => {
1717
INFO: 12,
1818
WARN: 16,
1919
ERROR: 20,
20-
SILENT: 24,
20+
CRITICAL: 24,
21+
SILENT: 28,
2122
};
2223

2324
beforeEach(() => {

0 commit comments

Comments
 (0)