Skip to content

Commit f89c795

Browse files
authored
feat(logger): align sampling debug logs feature implementation with the other runtimes (#1744)
* test(logger): remove logsSampled field, add default sampleRateValue * test(logger): add tests for sampling debug logs feature * feat(logger): change implementation to make sampling decision at per-function level * refactor(logger): remove redundant createLogger method * refactor(logger): remove getSampleRateValue method * test(logger): improve tests * refactor(logger): return createLogger() back with the detailed comment of the method importance * test(logger): add constructor/custom config/env var priority tests for sampling rate feature, improve description * refactor(logger): address review comments * feat(logger): add refreshSampleRateCalculation method and tests * test(logger): adjust end-to-end tests
1 parent 66bf1bc commit f89c795

File tree

7 files changed

+552
-175
lines changed

7 files changed

+552
-175
lines changed

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

+78-90
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,6 @@ class Logger extends Utility implements ClassThatLogs {
155155
SILENT: 28,
156156
};
157157

158-
private logsSampled = false;
159-
160158
private persistentLogAttributes?: LogAttributes = {};
161159

162160
private powertoolLogData: PowertoolLogData = <PowertoolLogData>{};
@@ -235,6 +233,7 @@ class Logger extends Utility implements ClassThatLogs {
235233
logLevel: this.getLevelName(),
236234
customConfigService: this.getCustomConfigService(),
237235
logFormatter: this.getLogFormatter(),
236+
sampleRateValue: this.powertoolLogData.sampleRateValue,
238237
};
239238
const parentsPowertoolsLogData = this.getPowertoolLogData();
240239
const childLogger = this.createLogger(
@@ -308,15 +307,6 @@ class Logger extends Utility implements ClassThatLogs {
308307
return this.logEvent;
309308
}
310309

311-
/**
312-
* It returns a boolean value, if true all the logs will be printed.
313-
*
314-
* @returns {boolean}
315-
*/
316-
public getLogsSampled(): boolean {
317-
return this.logsSampled;
318-
}
319-
320310
/**
321311
* It returns the persistent log attributes, which are the attributes
322312
* that will be logged in all log items.
@@ -456,15 +446,14 @@ class Logger extends Utility implements ClassThatLogs {
456446
}
457447

458448
/**
459-
* If the sample rate feature is enabled, the calculation that determines whether the logs
460-
* will actually be printed or not for this invocation is done when the Logger class is
461-
* initialized.
462-
* This method will repeat that calculation (with possible different outcome).
449+
* This method allows recalculating the initial sampling decision for changing
450+
* the log level to DEBUG based on a sample rate value used during initialization,
451+
* potentially yielding a different outcome.
463452
*
464453
* @returns {void}
465454
*/
466455
public refreshSampleRateCalculation(): void {
467-
this.setLogsSampled();
456+
this.setInitialSampleRate(this.powertoolLogData.sampleRateValue);
468457
}
469458

470459
/**
@@ -515,19 +504,6 @@ class Logger extends Utility implements ClassThatLogs {
515504
this.persistentLogAttributes = attributes;
516505
}
517506

518-
/**
519-
* It sets the user-provided sample rate value.
520-
*
521-
* @param {number} [sampleRateValue]
522-
* @returns {void}
523-
*/
524-
public setSampleRateValue(sampleRateValue?: number): void {
525-
this.powertoolLogData.sampleRateValue =
526-
sampleRateValue ||
527-
this.getCustomConfigService()?.getSampleRateValue() ||
528-
this.getEnvVarsService().getSampleRateValue();
529-
}
530-
531507
/**
532508
* It checks whether the current Lambda invocation event should be printed in the logs or not.
533509
*
@@ -555,36 +531,31 @@ class Logger extends Utility implements ClassThatLogs {
555531
}
556532

557533
/**
558-
* Creates a new Logger instance.
534+
* Factory method for instantiating logger instances. Used by `createChild` method.
535+
* Important for customization and subclassing. It allows subclasses, like `MyOwnLogger`,
536+
* to override its behavior while keeping the main business logic in `createChild` intact.
559537
*
560-
* @param {ConstructorOptions} [options]
561-
* @returns {Logger}
538+
* @example
539+
* ```typescript
540+
* // MyOwnLogger subclass
541+
* class MyOwnLogger extends Logger {
542+
* protected createLogger(options?: ConstructorOptions): MyOwnLogger {
543+
* return new MyOwnLogger(options);
544+
* }
545+
* // No need to re-implement business logic from `createChild` and keep track on changes
546+
* public createChild(options?: ConstructorOptions): MyOwnLogger {
547+
* return super.createChild(options) as MyOwnLogger;
548+
* }
549+
* }
550+
* ```
551+
*
552+
* @param {ConstructorOptions} [options] Logger configuration options.
553+
* @returns {Logger} A new logger instance.
562554
*/
563555
protected createLogger(options?: ConstructorOptions): Logger {
564556
return new Logger(options);
565557
}
566558

567-
/**
568-
* Decides whether the current log item should be printed or not.
569-
*
570-
* The decision is based on the log level and the sample rate value.
571-
* A log item will be printed if:
572-
* 1. The log level is greater than or equal to the Logger's log level.
573-
* 2. The log level is less than the Logger's log level, but the
574-
* current sampling value is set to `true`.
575-
*
576-
* @param {number} logLevel
577-
* @returns {boolean}
578-
* @protected
579-
*/
580-
protected shouldPrint(logLevel: number): boolean {
581-
if (logLevel >= this.logLevel) {
582-
return true;
583-
}
584-
585-
return this.getLogsSampled();
586-
}
587-
588559
/**
589560
* It stores information that is printed in all log items.
590561
*
@@ -750,20 +721,6 @@ class Logger extends Utility implements ClassThatLogs {
750721
};
751722
}
752723

753-
/**
754-
* It returns the numeric sample rate value.
755-
*
756-
* @private
757-
* @returns {number}
758-
*/
759-
private getSampleRateValue(): number {
760-
if (!this.powertoolLogData.sampleRateValue) {
761-
this.setSampleRateValue();
762-
}
763-
764-
return this.powertoolLogData.sampleRateValue as number;
765-
}
766-
767724
/**
768725
* It returns true and type guards the log level if a given log level is valid.
769726
*
@@ -777,6 +734,23 @@ class Logger extends Utility implements ClassThatLogs {
777734
return typeof logLevel === 'string' && logLevel in this.logLevelThresholds;
778735
}
779736

737+
/**
738+
* It returns true and type guards the sample rate value if a given value is valid.
739+
*
740+
* @param sampleRateValue
741+
* @private
742+
* @returns {boolean}
743+
*/
744+
private isValidSampleRate(
745+
sampleRateValue?: number
746+
): sampleRateValue is number {
747+
return (
748+
typeof sampleRateValue === 'number' &&
749+
0 <= sampleRateValue &&
750+
sampleRateValue <= 1
751+
);
752+
}
753+
780754
/**
781755
* It prints a given log with given log level.
782756
*
@@ -817,13 +791,12 @@ class Logger extends Utility implements ClassThatLogs {
817791
input: LogItemMessage,
818792
extraInput: LogItemExtraInput
819793
): void {
820-
if (!this.shouldPrint(logLevel)) {
821-
return;
794+
if (logLevel >= this.logLevel) {
795+
this.printLog(
796+
logLevel,
797+
this.createAndPopulateLogItem(logLevel, input, extraInput)
798+
);
822799
}
823-
this.printLog(
824-
logLevel,
825-
this.createAndPopulateLogItem(logLevel, input, extraInput)
826-
);
827800
}
828801

829802
/**
@@ -905,6 +878,37 @@ class Logger extends Utility implements ClassThatLogs {
905878
}
906879
}
907880

881+
/**
882+
* It sets sample rate value with the following prioprity:
883+
* 1. Constructor value
884+
* 2. Custom config service value
885+
* 3. Environment variable value
886+
* 4. Default value (zero)
887+
*
888+
* @private
889+
* @param {number} [sampleRateValue]
890+
* @returns {void}
891+
*/
892+
private setInitialSampleRate(sampleRateValue?: number): void {
893+
this.powertoolLogData.sampleRateValue = 0;
894+
const constructorValue = sampleRateValue;
895+
const customConfigValue =
896+
this.getCustomConfigService()?.getSampleRateValue();
897+
const envVarsValue = this.getEnvVarsService().getSampleRateValue();
898+
for (const value of [constructorValue, customConfigValue, envVarsValue]) {
899+
if (this.isValidSampleRate(value)) {
900+
this.powertoolLogData.sampleRateValue = value;
901+
902+
if (value && randomInt(0, 100) / 100 <= value) {
903+
this.setLogLevel('DEBUG');
904+
this.debug('Setting log level to DEBUG due to sampling rate');
905+
}
906+
907+
return;
908+
}
909+
}
910+
}
911+
908912
/**
909913
* If the log event feature is enabled via env variable, it sets a property that tracks whether
910914
* the event passed to the Lambda function handler should be logged or not.
@@ -943,20 +947,6 @@ class Logger extends Utility implements ClassThatLogs {
943947
}
944948
}
945949

946-
/**
947-
* If the sample rate feature is enabled, it sets a property that tracks whether this Lambda function invocation
948-
* will print logs or not.
949-
*
950-
* @private
951-
* @returns {void}
952-
*/
953-
private setLogsSampled(): void {
954-
const sampleRateValue = this.getSampleRateValue();
955-
this.logsSampled =
956-
sampleRateValue !== undefined &&
957-
(sampleRateValue === 1 || randomInt(0, 100) / 100 <= sampleRateValue);
958-
}
959-
960950
/**
961951
* It configures the Logger instance settings that will affect the Logger's behaviour
962952
* and the content of all logs.
@@ -981,10 +971,9 @@ class Logger extends Utility implements ClassThatLogs {
981971
this.setConsole();
982972
this.setCustomConfigService(customConfigService);
983973
this.setInitialLogLevel(logLevel);
984-
this.setSampleRateValue(sampleRateValue);
985-
this.setLogsSampled();
986974
this.setLogFormatter(logFormatter);
987975
this.setPowertoolLogData(serviceName, environment);
976+
this.setInitialSampleRate(sampleRateValue);
988977
this.setLogEvent();
989978
this.setLogIndentation();
990979

@@ -1014,7 +1003,6 @@ class Logger extends Utility implements ClassThatLogs {
10141003
environment ||
10151004
this.getCustomConfigService()?.getCurrentEnvironment() ||
10161005
this.getEnvVarsService().getCurrentEnvironment(),
1017-
sampleRateValue: this.getSampleRateValue(),
10181006
serviceName:
10191007
serviceName ||
10201008
this.getCustomConfigService()?.getServiceName() ||

Diff for: packages/logger/src/config/EnvironmentVariablesService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class EnvironmentVariablesService
100100
/**
101101
* It returns the value of the POWERTOOLS_LOGGER_SAMPLE_RATE environment variable.
102102
*
103-
* @returns {string|undefined}
103+
* @returns {number|undefined}
104104
*/
105105
public getSampleRateValue(): number | undefined {
106106
const value = this.get(this.sampleRateValueVariable);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type LambdaFunctionContext = {
4747
type PowertoolLogData = LogAttributes & {
4848
environment?: Environment;
4949
serviceName: string;
50-
sampleRateValue?: number;
50+
sampleRateValue: number;
5151
lambdaFunctionContext: LambdaFunctionContext;
5252
xRayTraceId?: string;
5353
awsRegion: string;

Diff for: packages/logger/tests/e2e/sampleRate.decorator.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ describe(`Logger E2E tests, sample rate and injectLambdaContext()`, () => {
8181

8282
if (logMessages.length === 1 && logMessages[0].includes('ERROR')) {
8383
countNotSampled++;
84-
} else if (logMessages.length === 4) {
84+
} else if (
85+
logMessages.length === 5 &&
86+
logMessages[0].includes(
87+
'Setting log level to DEBUG due to sampling rate'
88+
)
89+
) {
8590
countSampled++;
8691
} else {
8792
console.error(`Log group ${logGroupName} contains missing log`);

0 commit comments

Comments
 (0)