Skip to content

Commit c4c1f3d

Browse files
committed
feat(logger): Add log buffer and flush method
This commit implements basic buffering logic to the logger utility, and the `flushLogger` method for flushing the buffered logs.
1 parent 618cdee commit c4c1f3d

File tree

3 files changed

+381
-116
lines changed

3 files changed

+381
-116
lines changed

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

+115-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { LogJsonIndent, LogLevelThreshold, ReservedKeys } from './constants.js';
1313
import type { LogFormatter } from './formatter/LogFormatter.js';
1414
import type { LogItem } from './formatter/LogItem.js';
1515
import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js';
16+
import { CircularMap } from './logBuffer.js';
1617
import type { ConfigServiceInterface } from './types/ConfigServiceInterface.js';
1718
import type {
1819
ConstructorOptions,
@@ -142,7 +143,7 @@ class Logger extends Utility implements LoggerInterface {
142143
* Sometimes we need to log warnings before the logger is fully initialized, however we can't log them
143144
* immediately because the logger is not ready yet. This buffer stores those logs until the logger is ready.
144145
*/
145-
#buffer: [number, Parameters<Logger['createAndPopulateLogItem']>][] = [];
146+
#initBuffer: [number, Parameters<Logger['createAndPopulateLogItem']>][] = [];
146147
/**
147148
* Flag used to determine if the logger is initialized.
148149
*/
@@ -165,6 +166,29 @@ class Logger extends Utility implements LoggerInterface {
165166
*/
166167
#jsonReplacerFn?: CustomJsonReplacerFn;
167168

169+
/**
170+
* isBufferEnabled represents whether the buffering functionality is enabled in the logger
171+
*/
172+
protected isBufferEnabled = false;
173+
174+
/**
175+
* bufferLogThreshold represents the log level threshold for the buffer
176+
* Logs with a level lower than this threshold will be buffered
177+
*/
178+
protected bufferLogThreshold: number = LogLevelThreshold.DEBUG;
179+
/**
180+
* maxBufferBytesSize is the max size of the buffer. Additions to the buffer beyond this size will
181+
* cause older logs to be evicted from the buffer
182+
*/
183+
#maxBufferBytesSize = 1024;
184+
185+
/**
186+
* buffer stores logs up to `maxBufferBytesSize`
187+
*/
188+
#buffer: CircularMap<string> = new CircularMap({
189+
maxBytesSize: this.#maxBufferBytesSize,
190+
});
191+
168192
/**
169193
* Log level used by the current instance of Logger.
170194
*
@@ -182,11 +206,11 @@ class Logger extends Utility implements LoggerInterface {
182206
// all logs are buffered until the logger is initialized
183207
this.setOptions(rest);
184208
this.#isInitialized = true;
185-
for (const [level, log] of this.#buffer) {
209+
for (const [level, log] of this.#initBuffer) {
186210
// we call the method directly and create the log item just in time
187211
this.printLog(level, this.createAndPopulateLogItem(...log));
188212
}
189-
this.#buffer = [];
213+
this.#initBuffer = [];
190214
}
191215

192216
/**
@@ -934,7 +958,33 @@ class Logger extends Utility implements LoggerInterface {
934958
this.createAndPopulateLogItem(logLevel, input, extraInput)
935959
);
936960
} else {
937-
this.#buffer.push([logLevel, [logLevel, input, extraInput]]);
961+
this.#initBuffer.push([logLevel, [logLevel, input, extraInput]]);
962+
}
963+
return;
964+
}
965+
966+
const trace_id = this.envVarsService.getXrayTraceId();
967+
if (trace_id !== undefined && this.shouldBufferLog(trace_id, logLevel)) {
968+
try {
969+
this.bufferLogItem(
970+
trace_id,
971+
this.createAndPopulateLogItem(logLevel, input, extraInput),
972+
logLevel
973+
);
974+
} catch (e) {
975+
this.printLog(
976+
LogLevelThreshold.WARN,
977+
this.createAndPopulateLogItem(
978+
LogLevelThreshold.WARN,
979+
`Unable to buffer log: ${e}`,
980+
extraInput
981+
)
982+
);
983+
984+
this.printLog(
985+
logLevel,
986+
this.createAndPopulateLogItem(logLevel, input, extraInput)
987+
);
938988
}
939989
}
940990
}
@@ -1166,6 +1216,67 @@ class Logger extends Utility implements LoggerInterface {
11661216
});
11671217
persistentKeys && this.appendPersistentKeys(persistentKeys);
11681218
}
1219+
1220+
/**
1221+
* bufferLogItem adds a log to the buffer
1222+
* @param xrayTraceId
1223+
* @param log
1224+
* @param logLevel
1225+
*/
1226+
protected bufferLogItem(
1227+
xrayTraceId: string,
1228+
log: LogItem,
1229+
logLevel: number
1230+
): void {
1231+
log.prepareForPrint();
1232+
1233+
const stringified = JSON.stringify(
1234+
log.getAttributes(),
1235+
this.getJsonReplacer(),
1236+
this.logIndentation
1237+
);
1238+
1239+
this.#buffer.setItem(xrayTraceId, stringified, logLevel);
1240+
}
1241+
1242+
/**
1243+
* flushBuffer logs the items of the respective _X_AMZN_TRACE_ID within
1244+
* the buffer.
1245+
* @returns
1246+
*/
1247+
protected flushBuffer(): void {
1248+
const trace_id = this.envVarsService.getXrayTraceId();
1249+
if (trace_id === undefined) {
1250+
return;
1251+
}
1252+
1253+
const buffer = this.#buffer.get(trace_id) || [];
1254+
1255+
for (const item of buffer) {
1256+
const consoleMethod =
1257+
item.logLevel === LogLevelThreshold.CRITICAL
1258+
? 'error'
1259+
: (this.getLogLevelNameFromNumber(
1260+
item.logLevel
1261+
).toLowerCase() as keyof Omit<LogFunction, 'critical'>);
1262+
this.console[consoleMethod](item.value);
1263+
}
1264+
1265+
this.#buffer.delete(trace_id);
1266+
}
1267+
/**
1268+
* shouldBufferLog returns true if the log meets the criteria to be buffered
1269+
* @param trace_id _X_AMZN_TRACE_ID
1270+
* @param logLevel The level of the log being considered
1271+
* @returns
1272+
*/
1273+
shouldBufferLog(trace_id: string | undefined, logLevel: number): boolean {
1274+
return (
1275+
this.isBufferEnabled &&
1276+
trace_id !== undefined &&
1277+
logLevel <= this.bufferLogThreshold
1278+
);
1279+
}
11691280
}
11701281

11711282
export { Logger };

0 commit comments

Comments
 (0)