Skip to content

Commit 90d3b84

Browse files
dreamorosisthulb
andauthored
fix(logger): buffer logs emitted during init (#2277)
* fix(logger): buffer logs emitted during init * chore: remove reduntant method * chore: fix test label * chore: return type --------- Co-authored-by: Simon Thulbourn <[email protected]>
1 parent 2ee6f12 commit 90d3b84

File tree

2 files changed

+264
-195
lines changed

2 files changed

+264
-195
lines changed

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

+104-71
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,31 @@ class Logger extends Utility implements LoggerInterface {
126126
* @private
127127
*/
128128
private console!: Console;
129-
129+
/**
130+
* Custom config service instance used to configure the logger.
131+
*/
130132
private customConfigService?: ConfigServiceInterface;
131-
132-
// envVarsService is always initialized in the constructor in setOptions()
133-
private envVarsService!: EnvironmentVariablesService;
134-
133+
/**
134+
* Environment variables service instance used to fetch environment variables.
135+
*/
136+
private envVarsService = new EnvironmentVariablesService();
137+
/**
138+
* Whether to print the Lambda invocation event in the logs.
139+
*/
135140
private logEvent = false;
136-
141+
/**
142+
* Formatter used to format the log items.
143+
* @default new PowertoolsLogFormatter()
144+
*/
137145
private logFormatter?: LogFormatterInterface;
138-
146+
/**
147+
* JSON indentation used to format the logs.
148+
*/
139149
private logIndentation: number = LogJsonIndent.COMPACT;
140-
141150
/**
142151
* Log level used internally by the current instance of Logger.
143152
*/
144153
private logLevel = 12;
145-
146154
/**
147155
* Log level thresholds used internally by the current instance of Logger.
148156
*
@@ -156,10 +164,25 @@ class Logger extends Utility implements LoggerInterface {
156164
CRITICAL: 24,
157165
SILENT: 28,
158166
};
159-
167+
/**
168+
* Persistent log attributes that will be logged in all log items.
169+
*/
160170
private persistentLogAttributes: LogAttributes = {};
161-
171+
/**
172+
* Standard attributes managed by Powertools that will be logged in all log items.
173+
*/
162174
private powertoolsLogData: PowertoolsLogData = <PowertoolsLogData>{};
175+
/**
176+
* Buffer used to store logs until the logger is initialized.
177+
*
178+
* Sometimes we need to log warnings before the logger is fully initialized, however we can't log them
179+
* immediately because the logger is not ready yet. This buffer stores those logs until the logger is ready.
180+
*/
181+
#buffer: [number, Parameters<Logger['createAndPopulateLogItem']>][] = [];
182+
/**
183+
* Flag used to determine if the logger is initialized.
184+
*/
185+
#isInitialized = false;
163186

164187
/**
165188
* Log level used by the current instance of Logger.
@@ -178,7 +201,16 @@ class Logger extends Utility implements LoggerInterface {
178201
*/
179202
public constructor(options: ConstructorOptions = {}) {
180203
super();
181-
this.setOptions(options);
204+
const { customConfigService, ...rest } = options;
205+
this.setCustomConfigService(customConfigService);
206+
// all logs are buffered until the logger is initialized
207+
this.setOptions(rest);
208+
this.#isInitialized = true;
209+
for (const [level, log] of this.#buffer) {
210+
// we call the method directly and create the log item just in time
211+
this.printLog(level, this.createAndPopulateLogItem(...log));
212+
}
213+
this.#buffer = [];
182214
}
183215

184216
/**
@@ -596,41 +628,49 @@ class Logger extends Utility implements LoggerInterface {
596628
}
597629

598630
/**
599-
* It processes a particular log item so that it can be printed to stdout:
600-
* - Merges ephemeral log attributes with persistent log attributes (printed for all logs) and additional info;
601-
* - Formats all the log attributes;
631+
* Create a log item and populate it with the given log level, input, and extra input.
602632
*
603-
* @private
604-
* @param {number} logLevel
605-
* @param {LogItemMessage} input
606-
* @param {LogItemExtraInput} extraInput
607-
* @returns {LogItem}
633+
* We start with creating an object with base attributes managed by Powertools.
634+
* Then we create a second object with persistent attributes provided by customers either
635+
* directly to the log entry or through initial configuration and `appendKeys` method.
636+
*
637+
* Once we have the two objects, we pass them to the formatter that will apply the desired
638+
* formatting to the log item.
639+
*
640+
* @param logLevel The log level of the log item to be printed
641+
* @param input The main input of the log item, this can be a string or an object with additional attributes
642+
* @param extraInput Additional attributes to be added to the log item
608643
*/
609644
private createAndPopulateLogItem(
610645
logLevel: number,
611646
input: LogItemMessage,
612647
extraInput: LogItemExtraInput
613648
): LogItem {
614-
// TODO: this method's logic is hard to understand, there is an opportunity here to simplify this logic.
615-
const unformattedBaseAttributes = merge(
616-
{
617-
logLevel: this.getLogLevelNameFromNumber(logLevel),
618-
timestamp: new Date(),
619-
message: typeof input === 'string' ? input : input.message,
620-
xRayTraceId: this.envVarsService.getXrayTraceId(),
621-
},
622-
this.getPowertoolsLogData()
623-
);
624-
625-
let additionalLogAttributes: LogAttributes = {};
626-
additionalLogAttributes = merge(
627-
additionalLogAttributes,
628-
this.getPersistentLogAttributes()
629-
);
630-
if (typeof input !== 'string') {
631-
additionalLogAttributes = merge(additionalLogAttributes, input);
649+
let message = '';
650+
let otherInput: { [key: string]: unknown } = {};
651+
if (typeof input === 'string') {
652+
message = input;
653+
} else {
654+
const { message: inputMessage, ...rest } = input;
655+
message = inputMessage;
656+
otherInput = rest;
632657
}
633-
extraInput.forEach((item: Error | LogAttributes | string) => {
658+
659+
// create base attributes
660+
const unformattedBaseAttributes = {
661+
logLevel: this.getLogLevelNameFromNumber(logLevel),
662+
timestamp: new Date(),
663+
message,
664+
xRayTraceId: this.envVarsService.getXrayTraceId(),
665+
...this.getPowertoolsLogData(),
666+
};
667+
668+
// gradually merge additional attributes starting from customer-provided persistent attributes
669+
let additionalLogAttributes = { ...this.getPersistentLogAttributes() };
670+
// if the main input is not a string, then it's an object with additional attributes, so we merge it
671+
additionalLogAttributes = merge(additionalLogAttributes, otherInput);
672+
// then we merge the extra input attributes (if any)
673+
for (const item of extraInput) {
634674
const attributes: LogAttributes =
635675
item instanceof Error
636676
? { error: item }
@@ -639,14 +679,12 @@ class Logger extends Utility implements LoggerInterface {
639679
: item;
640680

641681
additionalLogAttributes = merge(additionalLogAttributes, attributes);
642-
});
682+
}
643683

644-
const logItem = this.getLogFormatter().formatAttributes(
684+
return this.getLogFormatter().formatAttributes(
645685
unformattedBaseAttributes,
646686
additionalLogAttributes
647687
);
648-
649-
return logItem;
650688
}
651689

652690
/**
@@ -816,10 +854,14 @@ class Logger extends Utility implements LoggerInterface {
816854
extraInput: LogItemExtraInput
817855
): void {
818856
if (logLevel >= this.logLevel) {
819-
this.printLog(
820-
logLevel,
821-
this.createAndPopulateLogItem(logLevel, input, extraInput)
822-
);
857+
if (this.#isInitialized) {
858+
this.printLog(
859+
logLevel,
860+
this.createAndPopulateLogItem(logLevel, input, extraInput)
861+
);
862+
} else {
863+
this.#buffer.push([logLevel, [logLevel, input, extraInput]]);
864+
}
823865
}
824866
}
825867

@@ -857,17 +899,6 @@ class Logger extends Utility implements LoggerInterface {
857899
: undefined;
858900
}
859901

860-
/**
861-
* Sets the Logger's custom config service instance, which will be used
862-
* to fetch environment variables.
863-
*
864-
* @private
865-
* @returns {void}
866-
*/
867-
private setEnvVarsService(): void {
868-
this.envVarsService = new EnvironmentVariablesService();
869-
}
870-
871902
/**
872903
* Sets the initial Logger log level based on the following order:
873904
* 1. If a log level is set using AWS Lambda Advanced Logging Controls, it sets it.
@@ -965,7 +996,7 @@ class Logger extends Utility implements LoggerInterface {
965996
}
966997

967998
/**
968-
* If the `POWERTOOLS_DEV' env variable is set,
999+
* If the `POWERTOOLS_DEV` env variable is set,
9691000
* it adds JSON indentation for pretty printing logs.
9701001
*
9711002
* @private
@@ -982,31 +1013,33 @@ class Logger extends Utility implements LoggerInterface {
9821013
* and the content of all logs.
9831014
*
9841015
* @private
985-
* @param {ConstructorOptions} options
986-
* @returns {Logger}
1016+
* @param options Options to configure the Logger instance
9871017
*/
988-
private setOptions(options: ConstructorOptions): Logger {
1018+
private setOptions(
1019+
options: Omit<ConstructorOptions, 'customConfigService'>
1020+
): this {
9891021
const {
9901022
logLevel,
9911023
serviceName,
9921024
sampleRateValue,
9931025
logFormatter,
994-
customConfigService,
9951026
persistentLogAttributes,
9961027
environment,
9971028
} = options;
9981029

999-
// order is important, EnvVarsService() is used by other methods
1000-
this.setEnvVarsService();
1001-
this.setConsole();
1002-
this.setCustomConfigService(customConfigService);
1003-
this.setInitialLogLevel(logLevel);
1004-
this.setLogFormatter(logFormatter);
1030+
// configurations that affect log content
10051031
this.setPowertoolsLogData(serviceName, environment);
1006-
this.setInitialSampleRate(sampleRateValue);
1032+
this.addPersistentLogAttributes(persistentLogAttributes);
1033+
1034+
// configurations that affect Logger behavior
10071035
this.setLogEvent();
1036+
this.setInitialLogLevel(logLevel);
1037+
this.setInitialSampleRate(sampleRateValue);
1038+
1039+
// configurations that affect how logs are printed
1040+
this.setLogFormatter(logFormatter);
1041+
this.setConsole();
10081042
this.setLogIndentation();
1009-
this.addPersistentLogAttributes(persistentLogAttributes);
10101043

10111044
return this;
10121045
}

0 commit comments

Comments
 (0)