Skip to content

Commit 2692ca4

Browse files
dreamorosiam29d
andauthored
fix(logger): correctly refresh sample rate (#3722)
Co-authored-by: Alexander Schueren <[email protected]>
1 parent 847a438 commit 2692ca4

File tree

7 files changed

+162
-154
lines changed

7 files changed

+162
-154
lines changed

Diff for: docs/core/logger.md

+13-86
Original file line numberDiff line numberDiff line change
@@ -805,114 +805,41 @@ You can use values ranging from `0` to `1` (100%) when setting the `sampleRateVa
805805

806806
This feature takes into account transient issues where additional debugging information can be useful.
807807

808-
Sampling decision happens at the Logger initialization. When using the `injectLambdaContext` method either as a decorator or middleware, the sampling decision is refreshed at the beginning of each Lambda invocation for you, except for cold starts.
808+
Sampling decision happens at the Logger initialization. When using the `injectLambdaContext` method either as a decorator or Middy.js middleware, the sampling decision is refreshed at the beginning of each Lambda invocation for you, except for cold starts.
809809

810810
If you're not using either of these, you'll need to manually call the `refreshSamplingRate()` function at the start of your handler to refresh the sampling decision for each invocation.
811811

812812
=== "handler.ts"
813813

814-
```typescript hl_lines="6"
814+
```typescript hl_lines="5 9"
815815
--8<-- "examples/snippets/logger/logSampling.ts"
816816
```
817817

818-
=== "Example CloudWatch Logs excerpt - Invocation #1"
818+
1. The log level must be set to a more verbose level than `DEBUG` for log sampling to kick in.
819+
2. You need to call `logger.refreshSamplingRate()` at the start of your handler **only** if you're not using the `injectLambdaContext()` class method decorator or Middy.js middleware.
820+
821+
=== "Example Logs Request #1 (not sampled)"
819822

820823
```json
821-
{
822-
"level": "ERROR",
823-
"message": "This is an ERROR log",
824-
"sampling_rate": "0.5",
825-
"service": "serverlessAirline",
826-
"timestamp": "2021-12-12T22:59:06.334Z",
827-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
828-
}
829-
{
830-
"level": "DEBUG",
831-
"message": "This is a DEBUG log that has 50% chance of being printed",
832-
"sampling_rate": "0.5",
833-
"service": "serverlessAirline",
834-
"timestamp": "2021-12-12T22:59:06.337Z",
835-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
836-
}
837-
{
838-
"level": "INFO",
839-
"message": "This is an INFO log that has 50% chance of being printed",
840-
"sampling_rate": "0.5",
841-
"service": "serverlessAirline",
842-
"timestamp": "2021-12-12T22:59:06.338Z",
843-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
844-
}
845-
{
846-
"level": "WARN",
847-
"message": "This is a WARN log that has 50% chance of being printed",
848-
"sampling_rate": "0.5",
849-
"service": "serverlessAirline",
850-
"timestamp": "2021-12-12T22:59:06.338Z",
851-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
852-
}
824+
--8<-- "examples/snippets/logger/samples/debugLogSamplingNotSampled.json"
853825
```
854826

855-
=== "Example CloudWatch Logs excerpt - Invocation #2"
827+
=== "Example Logs Request #2 (sampled)"
856828

857829
```json
858-
{
859-
"level": "ERROR",
860-
"message": "This is an ERROR log",
861-
"sampling_rate": "0.5",
862-
"service": "serverlessAirline",
863-
"timestamp": "2021-12-12T22:59:06.334Z",
864-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
865-
}
830+
--8<-- "examples/snippets/logger/samples/debugLogSamplingSampled.json"
866831
```
867832

868-
=== "Example CloudWatch Logs excerpt - Invocation #3"
833+
=== "Example Logs Request #3 (sampled)"
869834

870835
```json
871-
{
872-
"level": "ERROR",
873-
"message": "This is an ERROR log",
874-
"sampling_rate": "0.5",
875-
"service": "serverlessAirline",
876-
"timestamp": "2021-12-12T22:59:06.334Z",
877-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
878-
}
879-
{
880-
"level": "DEBUG",
881-
"message": "This is a DEBUG log that has 50% chance of being printed",
882-
"sampling_rate": "0.5",
883-
"service": "serverlessAirline",
884-
"timestamp": "2021-12-12T22:59:06.337Z",
885-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
886-
}
887-
{
888-
"level": "INFO",
889-
"message": "This is an INFO log that has 50% chance of being printed",
890-
"sampling_rate": "0.5",
891-
"service": "serverlessAirline",
892-
"timestamp": "2021-12-12T22:59:06.338Z",
893-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
894-
}
895-
{
896-
"level": "WARN",
897-
"message": "This is a WARN log that has 50% chance of being printed",
898-
"sampling_rate": "0.5",
899-
"service": "serverlessAirline",
900-
"timestamp": "2021-12-12T22:59:06.338Z",
901-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
902-
}
836+
--8<-- "examples/snippets/logger/samples/debugLogSamplingSampled.json"
903837
```
904838

905-
=== "Example CloudWatch Logs excerpt - Invocation #4"
839+
=== "Example Logs Request #4 (not sampled)"
906840

907841
```json
908-
{
909-
"level": "ERROR",
910-
"message": "This is an ERROR log",
911-
"sampling_rate": "0.5",
912-
"service": "serverlessAirline",
913-
"timestamp": "2021-12-12T22:59:06.334Z",
914-
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
915-
}
842+
--8<-- "examples/snippets/logger/samples/debugLogSamplingNotSampled.json"
916843
```
917844

918845
### Custom Log formatter

Diff for: examples/snippets/logger/logSampling.ts

+7-18
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
import { Logger } from '@aws-lambda-powertools/logger';
22

3-
// Notice the log level set to 'ERROR'
43
const logger = new Logger({
5-
logLevel: 'ERROR',
4+
logLevel: 'ERROR', // (1)!
65
sampleRateValue: 0.5,
76
});
87

9-
export const handler = async (
10-
_event: unknown,
11-
_context: unknown
12-
): Promise<void> => {
13-
// Refresh sample rate calculation on runtime, only when not using injectLambdaContext
14-
logger.refreshSampleRateCalculation();
15-
// This log item (equal to log level 'ERROR') will be printed to standard output
16-
// in all Lambda invocations
17-
logger.error('This is an ERROR log');
8+
export const handler = async () => {
9+
logger.refreshSampleRateCalculation(); // (2)!
1810

19-
// These log items (below the log level 'ERROR') have ~50% chance
20-
// of being printed in a Lambda invocation
21-
logger.debug('This is a DEBUG log that has 50% chance of being printed');
22-
logger.info('This is an INFO log that has 50% chance of being printed');
23-
logger.warn('This is a WARN log that has 50% chance of being printed');
11+
logger.error('This log is always emitted');
2412

25-
// Optional: refresh sample rate calculation on runtime
26-
// logger.refreshSampleRateCalculation();
13+
logger.debug('This log has ~50% chance of being emitted');
14+
logger.info('This log has ~50% chance of being emitted');
15+
logger.warn('This log has ~50% chance of being emitted');
2716
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"level": "ERROR",
3+
"message": "This log is always emitted",
4+
"sampling_rate": "0.5",
5+
"service": "serverlessAirline",
6+
"timestamp": "2021-12-12T22:59:06.334Z",
7+
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[
2+
{
3+
"level": "ERROR",
4+
"message": "This log is always emitted",
5+
"sampling_rate": "0.5",
6+
"service": "serverlessAirline",
7+
"timestamp": "2021-12-12T22:59:06.334Z",
8+
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
9+
},
10+
{
11+
"level": "DEBUG",
12+
"message": "This log has ~50% chance of being emitted",
13+
"sampling_rate": "0.5",
14+
"service": "serverlessAirline",
15+
"timestamp": "2021-12-12T22:59:06.337Z",
16+
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
17+
},
18+
{
19+
"level": "INFO",
20+
"message": "This log has ~50% chance of being emitted",
21+
"sampling_rate": "0.5",
22+
"service": "serverlessAirline",
23+
"timestamp": "2021-12-12T22:59:06.338Z",
24+
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
25+
},
26+
{
27+
"level": "WARN",
28+
"message": "This log has ~50% chance of being emitted",
29+
"sampling_rate": "0.5",
30+
"service": "serverlessAirline",
31+
"timestamp": "2021-12-12T22:59:06.338Z",
32+
"xray_trace_id": "abcdef123456abcdef123456abcdef123456"
33+
}
34+
]

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

+46-14
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ class Logger extends Utility implements LoggerInterface {
140140
/**
141141
* Standard attributes managed by Powertools that will be logged in all log items.
142142
*/
143-
private powertoolsLogData: PowertoolsLogData = <PowertoolsLogData>{};
143+
private powertoolsLogData: PowertoolsLogData = <PowertoolsLogData>{
144+
sampleRateValue: 0,
145+
};
144146
/**
145147
* Temporary log attributes that can be appended with `appendKeys()` method.
146148
*/
@@ -213,6 +215,22 @@ class Logger extends Utility implements LoggerInterface {
213215
*/
214216
#buffer?: CircularMap<string>;
215217

218+
/**
219+
* The debug sampling rate configuration.
220+
*/
221+
readonly #debugLogSampling = {
222+
/**
223+
* The sampling rate value used to determine if the log level should be set to DEBUG.
224+
*/
225+
sampleRateValue: 0,
226+
/**
227+
* The number of times the debug sampling rate has been refreshed.
228+
*
229+
* We use this to determine if we should refresh it again.
230+
*/
231+
refreshedTimes: 0,
232+
};
233+
216234
/**
217235
* Log level used by the current instance of Logger.
218236
*
@@ -302,7 +320,7 @@ class Logger extends Utility implements LoggerInterface {
302320
{
303321
logLevel: this.getLevelName(),
304322
serviceName: this.powertoolsLogData.serviceName,
305-
sampleRateValue: this.powertoolsLogData.sampleRateValue,
323+
sampleRateValue: this.#debugLogSampling.sampleRateValue,
306324
logFormatter: this.getLogFormatter(),
307325
customConfigService: this.getCustomConfigService(),
308326
environment: this.powertoolsLogData.environment,
@@ -547,8 +565,18 @@ class Logger extends Utility implements LoggerInterface {
547565
* This only works for warm starts, because we don't to avoid double sampling.
548566
*/
549567
public refreshSampleRateCalculation(): void {
550-
if (!this.coldStart) {
551-
this.setInitialSampleRate(this.powertoolsLogData.sampleRateValue);
568+
if (this.#debugLogSampling.refreshedTimes === 0) {
569+
this.#debugLogSampling.refreshedTimes++;
570+
return;
571+
}
572+
if (
573+
this.#shouldEnableDebugSampling() &&
574+
this.logLevel > LogLevelThreshold.TRACE
575+
) {
576+
this.setLogLevel('DEBUG');
577+
this.debug('Setting log level to DEBUG due to sampling rate');
578+
} else {
579+
this.setLogLevel(this.getLogLevelNameFromNumber(this.#initialLogLevel));
552580
}
553581
}
554582

@@ -891,6 +919,16 @@ class Logger extends Utility implements LoggerInterface {
891919
}
892920
}
893921

922+
/**
923+
* Make a new debug log sampling decision based on the sample rate value.
924+
*/
925+
#shouldEnableDebugSampling() {
926+
return (
927+
this.#debugLogSampling.sampleRateValue &&
928+
randomInt(0, 100) / 100 <= this.#debugLogSampling.sampleRateValue
929+
);
930+
}
931+
894932
/**
895933
* Check if a given key is reserved and warn the user if it is.
896934
*
@@ -1142,30 +1180,24 @@ class Logger extends Utility implements LoggerInterface {
11421180
* @param sampleRateValue - The sample rate value
11431181
*/
11441182
private setInitialSampleRate(sampleRateValue?: number): void {
1145-
this.powertoolsLogData.sampleRateValue = 0;
11461183
const constructorValue = sampleRateValue;
11471184
const customConfigValue =
11481185
this.getCustomConfigService()?.getSampleRateValue();
11491186
const envVarsValue = this.getEnvVarsService().getSampleRateValue();
11501187
for (const value of [constructorValue, customConfigValue, envVarsValue]) {
11511188
if (this.isValidSampleRate(value)) {
1189+
this.#debugLogSampling.sampleRateValue = value;
11521190
this.powertoolsLogData.sampleRateValue = value;
11531191

11541192
if (
1155-
this.logLevel > LogLevelThreshold.DEBUG &&
1156-
value &&
1157-
randomInt(0, 100) / 100 <= value
1193+
this.#shouldEnableDebugSampling() &&
1194+
this.logLevel > LogLevelThreshold.TRACE
11581195
) {
1159-
// only change logLevel if higher than debug, i.e. don't change from e.g. tracing to debug
11601196
this.setLogLevel('DEBUG');
11611197
this.debug('Setting log level to DEBUG due to sampling rate');
1162-
} else {
1163-
this.setLogLevel(
1164-
this.getLogLevelNameFromNumber(this.#initialLogLevel)
1165-
);
11661198
}
11671199

1168-
return;
1200+
break;
11691201
}
11701202
}
11711203
}

0 commit comments

Comments
 (0)