Skip to content

Commit

Permalink
feat(logger): add silent log level to suppress the emission of all lo…
Browse files Browse the repository at this point in the history
…gs (#1347)

* feat(logger): add silent log level to suppress the emission of all logs

* test: cleanup

* docs(logger): update text and formatting, add comments

* docs(logger): update table formatting

* docs(logger): add section about silencing logs
  • Loading branch information
shdq committed Mar 3, 2023
1 parent 8b9d158 commit c82939e
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 86 deletions.
25 changes: 18 additions & 7 deletions docs/core/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ The library requires two settings. You can set them as environment variables, or

These settings will be used across all logs emitted:

| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter |
|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|--------------------------------|--------------------|-----------------------|
| **Service name** | Sets the name of service of which the Lambda function is part of, that will be present across all log statements | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline`| `serviceName` |
| **Logging level** | Sets how verbose Logger should be | `LOG_LEVEL` | `info` |`DEBUG`, `INFO`, `WARN`, `ERROR`| `ERROR` | `logLevel` |
| **Log incoming event** | Whether to log or not the incoming event when using the decorator or middleware. | `POWERTOOLS_LOGGER_LOG_EVENT` | `false` | `true`, `false` | `false` | `logEvent` |
| **Debug log sampling** | Probability that a Lambda invocation will print all the log items regardless of the log level setting. | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `0` | `0.0` to `1` | `0.5` | `sampleRateValue` |
| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter |
|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|-------------------------------------------|--------------------|-----------------------|
| **Service name** | Sets the name of service of which the Lambda function is part of, that will be present across all log statements | `POWERTOOLS_SERVICE_NAME` | `service_undefined` | Any string | `serverlessAirline`| `serviceName` |
| **Logging level** | Sets how verbose Logger should be, from the most verbose to the least verbose (no logs) | `LOG_LEVEL` | `info` |`DEBUG`, `INFO`, `WARN`, `ERROR`, `SILENT` | `ERROR` | `logLevel` |
| **Log incoming event** | Whether to log or not the incoming event when using the decorator or middleware | `POWERTOOLS_LOGGER_LOG_EVENT` | `false` | `true`, `false` | `false` | `logEvent` |
| **Debug log sampling** | Probability that a Lambda invocation will print all the log items regardless of the log level setting | `POWERTOOLS_LOGGER_SAMPLE_RATE` | `0` | `0.0` to `1` | `0.5` | `sampleRateValue` |

#### Example using AWS Serverless Application Model (SAM)

Expand Down Expand Up @@ -81,7 +81,7 @@ Your Logger will include the following keys to your structured logging (default

| Key | Example | Note |
|-----------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **level**: `string` | `INFO` | Logging level set for the Lambda function"s invocation |
| **level**: `string` | `INFO` | Logging level set for the Lambda function's invocation |
| **message**: `string` | `Query performed to DynamoDB` | A descriptive, human-readable representation of this log item |
| **sampling_rate**: `float` | `0.1` | When enabled, it prints all the logs of a percentage of invocations, e.g. 10% |
| **service**: `string` | `serverlessAirline` | A unique name identifier of the service this Lambda function belongs to, by default `service_undefined` |
Expand Down Expand Up @@ -555,6 +555,17 @@ For example, by setting the "sample rate" to `0.5`, roughly 50% of your lambda i
}
```

### Silencing logs

The `SILENT` log level provides a simple and efficient way to suppress all log messages without the need to modify your code. When you set this log level, all log messages, regardless of their severity, will be silenced.

This feature is useful when you want to have your code instrumented to produce logs, but due to some requirement or business decision, you prefer to not emit them.

By setting the log level to `SILENT`, which can be done either through the `logLevel` constructor option or by using the `LOG_LEVEL` environment variable, you can easily suppress all logs as needed.

!!! note
Use the `SILENT` log level with care, as it can make it more challenging to monitor and debug your application. Therefore, we advise using this log level judiciously.

### Custom Log formatter (Bring Your Own Formatter)

You can customize the structure (keys and values) of your log items by passing a custom log formatter, an object that implements the `LogFormatter` abstract class.
Expand Down
2 changes: 2 additions & 0 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ class Logger extends Utility implements ClassThatLogs {

private logLevel?: Uppercase<LogLevel>;

// Log levels are in ascending order from the most verbose to the least verbose (no logs)
private readonly logLevelThresholds: LogLevelThresholds = {
DEBUG: 8,
INFO: 12,
WARN: 16,
ERROR: 20,
SILENT: 24,
};

private logsSampled: boolean = false;
Expand Down
3 changes: 2 additions & 1 deletion packages/logger/src/types/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ type LogLevelDebug = 'DEBUG';
type LogLevelInfo = 'INFO';
type LogLevelWarn = 'WARN';
type LogLevelError = 'ERROR';
type LogLevelSilent = 'SILENT';

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

type LogLevelThresholds = {
[key in Uppercase<LogLevel>]: number;
Expand Down
127 changes: 62 additions & 65 deletions packages/logger/tests/unit/Logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { createLogger, Logger } from '../../src';
import { EnvironmentVariablesService } from '../../src/config';
import { PowertoolLogFormatter } from '../../src/formatter';
import { ClassThatLogs, LogJsonIndent, ConstructorOptions } from '../../src/types';
import { ClassThatLogs, LogJsonIndent, ConstructorOptions, LogLevelThresholds } from '../../src/types';
import { Context } from 'aws-lambda';
import { Console } from 'console';

Expand All @@ -22,6 +22,13 @@ describe('Class: Logger', () => {
const ENVIRONMENT_VARIABLES = process.env;
const context = dummyContext.helloworldContext;
const event = dummyEvent.Custom.CustomEvent;
const logLevelThresholds: LogLevelThresholds = {
DEBUG: 8,
INFO: 12,
WARN: 16,
ERROR: 20,
SILENT: 24,
};

beforeEach(() => {
dateSpy.mockClear();
Expand Down Expand Up @@ -60,9 +67,7 @@ describe('Class: Logger', () => {
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();

// Act
if (logger[methodOfLogger]) {
logger[methodOfLogger]('foo');
}
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(debugPrints ? 1 : 0);
Expand All @@ -87,9 +92,7 @@ describe('Class: Logger', () => {
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();

// Act
if (logger[methodOfLogger]) {
logger[methodOfLogger]('foo');
}
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(infoPrints ? 1 : 0);
Expand All @@ -114,9 +117,7 @@ describe('Class: Logger', () => {
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();

// Act
if (logger[methodOfLogger]) {
logger[methodOfLogger]('foo');
}
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(warnPrints ? 1 : 0);
Expand All @@ -141,9 +142,7 @@ describe('Class: Logger', () => {
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();

// Act
if (logger[methodOfLogger]) {
logger[methodOfLogger]('foo');
}
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(errorPrints ? 1 : 0);
Expand All @@ -159,6 +158,41 @@ describe('Class: Logger', () => {

});

test('when the Logger\'s log level is SILENT, it DOES NOT print to stdout', () => {

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

// Act
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(0);
});

test('when the Logger\'s log level is set through LOG_LEVEL env variable, it DOES print to stdout', () => {

// Prepare
process.env.LOG_LEVEL = methodOfLogger.toUpperCase();
const logger = new Logger();
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();

// Act
logger[methodOfLogger]('foo');

// Assess
expect(consoleSpy).toBeCalledTimes(1);
expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({
level: methodOfLogger.toUpperCase(),
message: 'foo',
service: 'hello-world',
timestamp: '2016-06-20T12:08:10.000Z',
xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793',
}));
});
});

describe('Feature: sample rate', () => {
Expand All @@ -169,7 +203,7 @@ describe('Class: Logger', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'ERROR',
logLevel: 'SILENT',
sampleRateValue: 0,
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
Expand All @@ -180,14 +214,14 @@ describe('Class: Logger', () => {
}

// Assess
expect(consoleSpy).toBeCalledTimes(method === 'error' ? 1 : 0);
expect(consoleSpy).toBeCalledTimes(0);
});

test('when the Logger\'s log level is higher and the current Lambda invocation IS sampled for logging, it DOES print to stdout', () => {

// Prepare
const logger: Logger = createLogger({
logLevel: 'ERROR',
logLevel: 'SILENT',
sampleRateValue: 1,
});
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
Expand Down Expand Up @@ -630,10 +664,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand Down Expand Up @@ -1396,10 +1427,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand All @@ -1422,10 +1450,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: true,
persistentLogAttributes: {},
Expand All @@ -1448,10 +1473,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: true,
persistentLogAttributes: {},
Expand Down Expand Up @@ -1512,10 +1534,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand All @@ -1538,10 +1557,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {
Expand All @@ -1566,10 +1582,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: true,
persistentLogAttributes: {},
Expand All @@ -1592,10 +1605,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'ERROR',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand Down Expand Up @@ -1641,10 +1651,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand All @@ -1667,10 +1674,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {
Expand Down Expand Up @@ -1700,10 +1704,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {
Expand Down Expand Up @@ -1746,10 +1747,7 @@ describe('Class: Logger', () => {
logFormatter: expect.any(PowertoolLogFormatter),
logLevel: 'DEBUG',
logLevelThresholds: {
DEBUG: 8,
ERROR: 20,
INFO: 12,
WARN: 16,
...logLevelThresholds
},
logsSampled: false,
persistentLogAttributes: {},
Expand Down Expand Up @@ -1980,5 +1978,4 @@ describe('Class: Logger', () => {
});

});

});
Loading

0 comments on commit c82939e

Please sign in to comment.