Skip to content

Commit c82939e

Browse files
authored
feat(logger): add silent log level to suppress the emission of all logs (#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
1 parent 8b9d158 commit c82939e

File tree

5 files changed

+95
-86
lines changed

5 files changed

+95
-86
lines changed

Diff for: docs/core/logger.md

+18-7
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ The library requires two settings. You can set them as environment variables, or
4646

4747
These settings will be used across all logs emitted:
4848

49-
| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter |
50-
|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|--------------------------------|--------------------|-----------------------|
51-
| **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` |
52-
| **Logging level** | Sets how verbose Logger should be | `LOG_LEVEL` | `info` |`DEBUG`, `INFO`, `WARN`, `ERROR`| `ERROR` | `logLevel` |
53-
| **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` |
54-
| **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` |
49+
| Setting | Description | Environment variable | Default Value | Allowed Values | Example Value | Constructor parameter |
50+
|-------------------------|------------------------------------------------------------------------------------------------------------------|---------------------------------|---------------------|-------------------------------------------|--------------------|-----------------------|
51+
| **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` |
52+
| **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` |
53+
| **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` |
54+
| **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` |
5555

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

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

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

558+
### Silencing logs
559+
560+
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.
561+
562+
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.
563+
564+
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.
565+
566+
!!! note
567+
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.
568+
558569
### Custom Log formatter (Bring Your Own Formatter)
559570

560571
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.

Diff for: packages/logger/src/Logger.ts

+2
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,13 @@ class Logger extends Utility implements ClassThatLogs {
130130

131131
private logLevel?: Uppercase<LogLevel>;
132132

133+
// Log levels are in ascending order from the most verbose to the least verbose (no logs)
133134
private readonly logLevelThresholds: LogLevelThresholds = {
134135
DEBUG: 8,
135136
INFO: 12,
136137
WARN: 16,
137138
ERROR: 20,
139+
SILENT: 24,
138140
};
139141

140142
private logsSampled: boolean = false;

Diff for: packages/logger/src/types/Log.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ type LogLevelDebug = 'DEBUG';
22
type LogLevelInfo = 'INFO';
33
type LogLevelWarn = 'WARN';
44
type LogLevelError = 'ERROR';
5+
type LogLevelSilent = 'SILENT';
56

6-
type LogLevel = LogLevelDebug | Lowercase<LogLevelDebug> | LogLevelInfo | Lowercase<LogLevelInfo> | LogLevelWarn | Lowercase<LogLevelWarn> | LogLevelError | Lowercase<LogLevelError>;
7+
type LogLevel = LogLevelDebug | Lowercase<LogLevelDebug> | LogLevelInfo | Lowercase<LogLevelInfo> | LogLevelWarn | Lowercase<LogLevelWarn> | LogLevelError | Lowercase<LogLevelError> | LogLevelSilent | Lowercase<LogLevelSilent>;
78

89
type LogLevelThresholds = {
910
[key in Uppercase<LogLevel>]: number;

Diff for: packages/logger/tests/unit/Logger.test.ts

+62-65
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { createLogger, Logger } from '../../src';
1111
import { EnvironmentVariablesService } from '../../src/config';
1212
import { PowertoolLogFormatter } from '../../src/formatter';
13-
import { ClassThatLogs, LogJsonIndent, ConstructorOptions } from '../../src/types';
13+
import { ClassThatLogs, LogJsonIndent, ConstructorOptions, LogLevelThresholds } from '../../src/types';
1414
import { Context } from 'aws-lambda';
1515
import { Console } from 'console';
1616

@@ -22,6 +22,13 @@ describe('Class: Logger', () => {
2222
const ENVIRONMENT_VARIABLES = process.env;
2323
const context = dummyContext.helloworldContext;
2424
const event = dummyEvent.Custom.CustomEvent;
25+
const logLevelThresholds: LogLevelThresholds = {
26+
DEBUG: 8,
27+
INFO: 12,
28+
WARN: 16,
29+
ERROR: 20,
30+
SILENT: 24,
31+
};
2532

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

6269
// Act
63-
if (logger[methodOfLogger]) {
64-
logger[methodOfLogger]('foo');
65-
}
70+
logger[methodOfLogger]('foo');
6671

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

8994
// Act
90-
if (logger[methodOfLogger]) {
91-
logger[methodOfLogger]('foo');
92-
}
95+
logger[methodOfLogger]('foo');
9396

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

116119
// Act
117-
if (logger[methodOfLogger]) {
118-
logger[methodOfLogger]('foo');
119-
}
120+
logger[methodOfLogger]('foo');
120121

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

143144
// Act
144-
if (logger[methodOfLogger]) {
145-
logger[methodOfLogger]('foo');
146-
}
145+
logger[methodOfLogger]('foo');
147146

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

160159
});
161160

161+
test('when the Logger\'s log level is SILENT, it DOES NOT print to stdout', () => {
162+
163+
// Prepare
164+
const logger: Logger = createLogger({
165+
logLevel: 'SILENT',
166+
});
167+
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
168+
169+
// Act
170+
logger[methodOfLogger]('foo');
171+
172+
// Assess
173+
expect(consoleSpy).toBeCalledTimes(0);
174+
});
175+
176+
test('when the Logger\'s log level is set through LOG_LEVEL env variable, it DOES print to stdout', () => {
177+
178+
// Prepare
179+
process.env.LOG_LEVEL = methodOfLogger.toUpperCase();
180+
const logger = new Logger();
181+
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
182+
183+
// Act
184+
logger[methodOfLogger]('foo');
185+
186+
// Assess
187+
expect(consoleSpy).toBeCalledTimes(1);
188+
expect(consoleSpy).toHaveBeenNthCalledWith(1, JSON.stringify({
189+
level: methodOfLogger.toUpperCase(),
190+
message: 'foo',
191+
service: 'hello-world',
192+
timestamp: '2016-06-20T12:08:10.000Z',
193+
xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793',
194+
}));
195+
});
162196
});
163197

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

170204
// Prepare
171205
const logger: Logger = createLogger({
172-
logLevel: 'ERROR',
206+
logLevel: 'SILENT',
173207
sampleRateValue: 0,
174208
});
175209
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
@@ -180,14 +214,14 @@ describe('Class: Logger', () => {
180214
}
181215

182216
// Assess
183-
expect(consoleSpy).toBeCalledTimes(method === 'error' ? 1 : 0);
217+
expect(consoleSpy).toBeCalledTimes(0);
184218
});
185219

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

188222
// Prepare
189223
const logger: Logger = createLogger({
190-
logLevel: 'ERROR',
224+
logLevel: 'SILENT',
191225
sampleRateValue: 1,
192226
});
193227
const consoleSpy = jest.spyOn(logger['console'], methodOfLogger).mockImplementation();
@@ -630,10 +664,7 @@ describe('Class: Logger', () => {
630664
logFormatter: expect.any(PowertoolLogFormatter),
631665
logLevel: 'DEBUG',
632666
logLevelThresholds: {
633-
DEBUG: 8,
634-
ERROR: 20,
635-
INFO: 12,
636-
WARN: 16,
667+
...logLevelThresholds
637668
},
638669
logsSampled: false,
639670
persistentLogAttributes: {},
@@ -1396,10 +1427,7 @@ describe('Class: Logger', () => {
13961427
logFormatter: expect.any(PowertoolLogFormatter),
13971428
logLevel: 'DEBUG',
13981429
logLevelThresholds: {
1399-
DEBUG: 8,
1400-
ERROR: 20,
1401-
INFO: 12,
1402-
WARN: 16,
1430+
...logLevelThresholds
14031431
},
14041432
logsSampled: false,
14051433
persistentLogAttributes: {},
@@ -1422,10 +1450,7 @@ describe('Class: Logger', () => {
14221450
logFormatter: expect.any(PowertoolLogFormatter),
14231451
logLevel: 'DEBUG',
14241452
logLevelThresholds: {
1425-
DEBUG: 8,
1426-
ERROR: 20,
1427-
INFO: 12,
1428-
WARN: 16,
1453+
...logLevelThresholds
14291454
},
14301455
logsSampled: true,
14311456
persistentLogAttributes: {},
@@ -1448,10 +1473,7 @@ describe('Class: Logger', () => {
14481473
logFormatter: expect.any(PowertoolLogFormatter),
14491474
logLevel: 'DEBUG',
14501475
logLevelThresholds: {
1451-
DEBUG: 8,
1452-
ERROR: 20,
1453-
INFO: 12,
1454-
WARN: 16,
1476+
...logLevelThresholds
14551477
},
14561478
logsSampled: true,
14571479
persistentLogAttributes: {},
@@ -1512,10 +1534,7 @@ describe('Class: Logger', () => {
15121534
logFormatter: expect.any(PowertoolLogFormatter),
15131535
logLevel: 'DEBUG',
15141536
logLevelThresholds: {
1515-
DEBUG: 8,
1516-
ERROR: 20,
1517-
INFO: 12,
1518-
WARN: 16,
1537+
...logLevelThresholds
15191538
},
15201539
logsSampled: false,
15211540
persistentLogAttributes: {},
@@ -1538,10 +1557,7 @@ describe('Class: Logger', () => {
15381557
logFormatter: expect.any(PowertoolLogFormatter),
15391558
logLevel: 'DEBUG',
15401559
logLevelThresholds: {
1541-
DEBUG: 8,
1542-
ERROR: 20,
1543-
INFO: 12,
1544-
WARN: 16,
1560+
...logLevelThresholds
15451561
},
15461562
logsSampled: false,
15471563
persistentLogAttributes: {
@@ -1566,10 +1582,7 @@ describe('Class: Logger', () => {
15661582
logFormatter: expect.any(PowertoolLogFormatter),
15671583
logLevel: 'DEBUG',
15681584
logLevelThresholds: {
1569-
DEBUG: 8,
1570-
ERROR: 20,
1571-
INFO: 12,
1572-
WARN: 16,
1585+
...logLevelThresholds
15731586
},
15741587
logsSampled: true,
15751588
persistentLogAttributes: {},
@@ -1592,10 +1605,7 @@ describe('Class: Logger', () => {
15921605
logFormatter: expect.any(PowertoolLogFormatter),
15931606
logLevel: 'ERROR',
15941607
logLevelThresholds: {
1595-
DEBUG: 8,
1596-
ERROR: 20,
1597-
INFO: 12,
1598-
WARN: 16,
1608+
...logLevelThresholds
15991609
},
16001610
logsSampled: false,
16011611
persistentLogAttributes: {},
@@ -1641,10 +1651,7 @@ describe('Class: Logger', () => {
16411651
logFormatter: expect.any(PowertoolLogFormatter),
16421652
logLevel: 'DEBUG',
16431653
logLevelThresholds: {
1644-
DEBUG: 8,
1645-
ERROR: 20,
1646-
INFO: 12,
1647-
WARN: 16,
1654+
...logLevelThresholds
16481655
},
16491656
logsSampled: false,
16501657
persistentLogAttributes: {},
@@ -1667,10 +1674,7 @@ describe('Class: Logger', () => {
16671674
logFormatter: expect.any(PowertoolLogFormatter),
16681675
logLevel: 'DEBUG',
16691676
logLevelThresholds: {
1670-
DEBUG: 8,
1671-
ERROR: 20,
1672-
INFO: 12,
1673-
WARN: 16,
1677+
...logLevelThresholds
16741678
},
16751679
logsSampled: false,
16761680
persistentLogAttributes: {
@@ -1700,10 +1704,7 @@ describe('Class: Logger', () => {
17001704
logFormatter: expect.any(PowertoolLogFormatter),
17011705
logLevel: 'DEBUG',
17021706
logLevelThresholds: {
1703-
DEBUG: 8,
1704-
ERROR: 20,
1705-
INFO: 12,
1706-
WARN: 16,
1707+
...logLevelThresholds
17071708
},
17081709
logsSampled: false,
17091710
persistentLogAttributes: {
@@ -1746,10 +1747,7 @@ describe('Class: Logger', () => {
17461747
logFormatter: expect.any(PowertoolLogFormatter),
17471748
logLevel: 'DEBUG',
17481749
logLevelThresholds: {
1749-
DEBUG: 8,
1750-
ERROR: 20,
1751-
INFO: 12,
1752-
WARN: 16,
1750+
...logLevelThresholds
17531751
},
17541752
logsSampled: false,
17551753
persistentLogAttributes: {},
@@ -1980,5 +1978,4 @@ describe('Class: Logger', () => {
19801978
});
19811979

19821980
});
1983-
19841981
});

0 commit comments

Comments
 (0)