Skip to content

Commit fb896e8

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 eb19e18 commit fb896e8

File tree

3 files changed

+381
-116
lines changed

3 files changed

+381
-116
lines changed

packages/logger/src/Logger.ts

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

11741285
export { Logger };

0 commit comments

Comments
 (0)