Skip to content

Commit

Permalink
feat(logger): add CRITICAL log level (#1399)
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamorosi authored Apr 7, 2023
1 parent 2013ff2 commit a248ff0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 42 deletions.
24 changes: 19 additions & 5 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ class Logger extends Utility implements ClassThatLogs {
INFO: 12,
WARN: 16,
ERROR: 20,
SILENT: 24,
CRITICAL: 24,
SILENT: 28,
};

private logsSampled: boolean = false;
Expand Down Expand Up @@ -212,17 +213,27 @@ class Logger extends Utility implements ClassThatLogs {
};
const parentsPowertoolsLogData = this.getPowertoolLogData();
const childLogger = new Logger(merge(parentsOptions, parentsPowertoolsLogData, options));

const parentsPersistentLogAttributes = this.getPersistentLogAttributes();
childLogger.addPersistentLogAttributes(parentsPersistentLogAttributes);

if (parentsPowertoolsLogData.lambdaContext) {
childLogger.addContext(parentsPowertoolsLogData.lambdaContext as Context);
}

return childLogger;
}

/**
* It prints a log item with level CRITICAL.
*
* @param {LogItemMessage} input
* @param {Error | LogAttributes | string} extraInput
*/
public critical(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
this.processLogItem('CRITICAL', input, extraInput);
}

/**
* It prints a log item with level DEBUG.
*
Expand Down Expand Up @@ -645,7 +656,10 @@ class Logger extends Utility implements ClassThatLogs {
private printLog(logLevel: LogLevel, log: LogItem): void {
log.prepareForPrint();

const consoleMethod = logLevel.toLowerCase() as keyof ClassThatLogs;
const consoleMethod =
logLevel === 'CRITICAL' ?
'error' :
logLevel.toLowerCase() as keyof Omit<ClassThatLogs, 'critical'>;

this.console[consoleMethod](JSON.stringify(log.getAttributes(), this.getReplacer(), this.logIndentation));
}
Expand Down
15 changes: 14 additions & 1 deletion packages/logger/src/types/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,21 @@ type LogLevelInfo = 'INFO';
type LogLevelWarn = 'WARN';
type LogLevelError = 'ERROR';
type LogLevelSilent = 'SILENT';
type LogLevelCritical = 'CRITICAL';

type LogLevel = LogLevelDebug | Lowercase<LogLevelDebug> | LogLevelInfo | Lowercase<LogLevelInfo> | LogLevelWarn | Lowercase<LogLevelWarn> | LogLevelError | Lowercase<LogLevelError> | LogLevelSilent | Lowercase<LogLevelSilent>;
type LogLevel =
LogLevelDebug |
Lowercase<LogLevelDebug> |
LogLevelInfo |
Lowercase<LogLevelInfo> |
LogLevelWarn |
Lowercase<LogLevelWarn> |
LogLevelError |
Lowercase<LogLevelError> |
LogLevelSilent |
Lowercase<LogLevelSilent> |
LogLevelCritical |
Lowercase<LogLevelCritical>;

type LogLevelThresholds = {
[key in Uppercase<LogLevel>]: number;
Expand Down
9 changes: 7 additions & 2 deletions packages/logger/src/types/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { AsyncHandler, LambdaInterface, SyncHandler } from '@aws-lambda-powertoo
import { Handler } from 'aws-lambda';
import { ConfigServiceInterface } from '../config';
import { LogFormatterInterface } from '../formatter';
import { Environment, LogAttributes, LogAttributesWithMessage, LogLevel } from './Log';
import {
Environment,
LogAttributes,
LogAttributesWithMessage,
LogLevel,
} from './Log';

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

type HandlerOptions = {
Expand Down
62 changes: 29 additions & 33 deletions packages/logger/tests/unit/Logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { Console } from 'console';

const mockDate = new Date(1466424490000);
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const getConsoleMethod = (method: string): keyof Omit<ClassThatLogs, 'critical'> =>
method === 'critical' ?
'error' :
method.toLowerCase() as keyof Omit<ClassThatLogs, 'critical'>;

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

Expand All @@ -27,7 +31,8 @@ describe('Class: Logger', () => {
INFO: 12,
WARN: 16,
ERROR: 20,
SILENT: 24,
CRITICAL: 24,
SILENT: 28,
};

beforeEach(() => {
Expand All @@ -40,6 +45,7 @@ describe('Class: Logger', () => {
[ 'info', 'DOES', true, 'DOES', true, 'DOES NOT', false, 'DOES NOT', false ],
[ 'warn', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES NOT', false ],
[ 'error', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true ],
[ 'critical', 'DOES', true, 'DOES', true, 'DOES', true, 'DOES', true ],
])(
'Method: %p',
(
Expand All @@ -54,17 +60,17 @@ describe('Class: Logger', () => {
errorPrints,
) => {

describe('Feature: log level', () => {
const methodOfLogger = method as keyof ClassThatLogs;

const methodOfLogger = method as keyof ClassThatLogs;
describe('Feature: log level', () => {

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

// Prepare
const logger: Logger = createLogger({
logLevel: 'DEBUG',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(method)).mockImplementation();

// Act
logger[methodOfLogger]('foo');
Expand All @@ -89,7 +95,7 @@ describe('Class: Logger', () => {
const logger: Logger = createLogger({
logLevel: 'INFO',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
logger[methodOfLogger]('foo');
Expand All @@ -114,7 +120,7 @@ describe('Class: Logger', () => {
const logger: Logger = createLogger({
logLevel: 'WARN',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
logger[methodOfLogger]('foo');
Expand All @@ -139,7 +145,7 @@ describe('Class: Logger', () => {
const logger: Logger = createLogger({
logLevel: 'ERROR',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
logger[methodOfLogger]('foo');
Expand All @@ -164,7 +170,7 @@ describe('Class: Logger', () => {
const logger: Logger = createLogger({
logLevel: 'SILENT',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
logger[methodOfLogger]('foo');
Expand All @@ -178,7 +184,7 @@ describe('Class: Logger', () => {
// Prepare
process.env.LOG_LEVEL = methodOfLogger.toUpperCase();
const logger = new Logger();
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

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

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

const methodOfLogger = method as keyof ClassThatLogs;

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', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'SILENT',
sampleRateValue: 0,
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
if (logger[methodOfLogger]) {
Expand All @@ -224,7 +228,7 @@ describe('Class: Logger', () => {
logLevel: 'SILENT',
sampleRateValue: 1,
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

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

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

const methodOfLogger = method as keyof ClassThatLogs;

test('when the Lambda context is not captured and a string is passed as log message, it should print a valid ' + method.toUpperCase() + ' log', () => {

// Prepare
const logger: Logger = createLogger();
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
if (logger[methodOfLogger]) {
Expand All @@ -279,7 +281,7 @@ describe('Class: Logger', () => {
logLevel: 'DEBUG',
});
logger.addContext(context);
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

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

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

const methodOfLogger = method as keyof ClassThatLogs;

test('when added, they should appear in that log item only', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'DEBUG',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

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

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

const methodOfLogger = method as keyof ClassThatLogs;

test('when persistent log attributes are added to the Logger instance, they should appear in all logs printed by the instance', () => {

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

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

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

const methodOfLogger = method as keyof ClassThatLogs;

test('when the `_X_AMZN_TRACE_ID` environment variable is set it parses it correctly and adds the Trace ID to the log', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'DEBUG',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

// Act
if (logger[methodOfLogger]) {
Expand All @@ -515,7 +511,7 @@ describe('Class: Logger', () => {
const logger: Logger = createLogger({
logLevel: 'DEBUG',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();

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

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

const methodOfLogger = method as keyof ClassThatLogs;

test('when a logged item references itself, the logger ignores the keys that cause a circular reference', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'DEBUG',
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
const consoleSpy = jest.spyOn(logger['console'], getConsoleMethod(methodOfLogger)).mockImplementation();
const circularObject = {
foo: 'bar',
self: {},
Expand Down Expand Up @@ -581,7 +575,7 @@ describe('Class: Logger', () => {

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

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

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

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

Expand Down Expand Up @@ -1050,6 +1044,7 @@ describe('Class: Logger', () => {
biz: 'baz'
}
});
jest.spyOn(logger['console'], 'debug').mockImplementation();
class LambdaFunction implements LambdaInterface {

@logger.injectLambdaContext({ clearState: true })
Expand Down Expand Up @@ -1091,6 +1086,7 @@ describe('Class: Logger', () => {
biz: 'baz'
}
});
jest.spyOn(logger['console'], 'debug').mockImplementation();
class LambdaFunction implements LambdaInterface {

@logger.injectLambdaContext({ clearState: true })
Expand Down
3 changes: 2 additions & 1 deletion packages/logger/tests/unit/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ describe('Helper: createLogger function', () => {
INFO: 12,
WARN: 16,
ERROR: 20,
SILENT: 24,
CRITICAL: 24,
SILENT: 28,
};

beforeEach(() => {
Expand Down

0 comments on commit a248ff0

Please sign in to comment.