Skip to content

feat(logger): Enable log buffering feature #3641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 25, 2025
60 changes: 50 additions & 10 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,23 +174,26 @@ class Logger extends Utility implements LoggerInterface {
*/
protected isBufferEnabled = false;

/**
* Whether the buffer should be flushed when an error is logged
*/
protected flushOnErrorLog = true;
/**
* Log level threshold for the buffer
* Logs with a level lower than this threshold will be buffered
* Default is DEBUG
*/
protected bufferLogThreshold: number = LogLevelThreshold.DEBUG;
protected bufferAtVerbosity: number = LogLevelThreshold.DEBUG;
/**
* Max size of the buffer. Additions to the buffer beyond this size will
* cause older logs to be evicted from the buffer
*/
readonly #maxBufferBytesSize = 1024;
#maxBufferBytesSize = 20480;

/**
* Contains buffered logs, grouped by _X_AMZN_TRACE_ID, each group with a max size of `maxBufferBytesSize`
*/
readonly #buffer: CircularMap<string> = new CircularMap({
maxBytesSize: this.#maxBufferBytesSize,
});
#buffer?: CircularMap<string>;

/**
* Log level used by the current instance of Logger.
Expand Down Expand Up @@ -330,6 +333,9 @@ class Logger extends Utility implements LoggerInterface {
* @param extraInput - The extra input to log.
*/
public error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
if (this.isBufferEnabled && this.flushOnErrorLog) {
this.flushBuffer();
}
this.processLogItem(LogLevelThreshold.ERROR, input, extraInput);
}

Expand Down Expand Up @@ -1167,6 +1173,7 @@ class Logger extends Utility implements LoggerInterface {
environment,
jsonReplacerFn,
logRecordOrder,
logBufferOptions,
} = options;

if (persistentLogAttributes && persistentKeys) {
Expand All @@ -1193,6 +1200,10 @@ class Logger extends Utility implements LoggerInterface {
this.setLogIndentation();
this.#jsonReplacerFn = jsonReplacerFn;

if (logBufferOptions !== undefined) {
this.#setLogBuffering(logBufferOptions);
}

return this;
}

Expand Down Expand Up @@ -1223,6 +1234,35 @@ class Logger extends Utility implements LoggerInterface {
persistentKeys && this.appendPersistentKeys(persistentKeys);
}

#setLogBuffering(
options: NonNullable<ConstructorOptions['logBufferOptions']>
) {
if (options.maxBytes !== undefined) {
this.#maxBufferBytesSize = options.maxBytes;
}

this.#buffer = new CircularMap({
maxBytesSize: this.#maxBufferBytesSize,
});

if (options.enabled === false) {
this.isBufferEnabled = false;
} else {
this.isBufferEnabled = true;
}

if (options.flushOnErrorLog === false) {
this.flushOnErrorLog = false;
} else {
this.flushOnErrorLog = true;
}
const bufferAtLogLevel = options.bufferAtVerbosity?.toUpperCase();

if (this.isValidLogLevel(bufferAtLogLevel)) {
this.bufferAtVerbosity = LogLevelThreshold[bufferAtLogLevel];
}
}

/**
* Add a log to the buffer
* @param xrayTraceId - _X_AMZN_TRACE_ID of the request
Expand All @@ -1242,20 +1282,20 @@ class Logger extends Utility implements LoggerInterface {
this.logIndentation
);

this.#buffer.setItem(xrayTraceId, stringified, logLevel);
this.#buffer?.setItem(xrayTraceId, stringified, logLevel);
}

/**
* Flushes all items of the respective _X_AMZN_TRACE_ID within
* the buffer.
*/
protected flushBuffer(): void {
public flushBuffer(): void {
const traceId = this.envVarsService.getXrayTraceId();
if (traceId === undefined) {
return;
}

const buffer = this.#buffer.get(traceId);
const buffer = this.#buffer?.get(traceId);
if (buffer === undefined) {
return;
}
Expand All @@ -1280,7 +1320,7 @@ class Logger extends Utility implements LoggerInterface {
);
}

this.#buffer.delete(traceId);
this.#buffer?.delete(traceId);
}
/**
* Tests if the log meets the criteria to be buffered
Expand All @@ -1294,7 +1334,7 @@ class Logger extends Utility implements LoggerInterface {
return (
this.isBufferEnabled &&
traceId !== undefined &&
logLevel <= this.bufferLogThreshold
logLevel <= this.bufferAtVerbosity
);
}
}
Expand Down
37 changes: 36 additions & 1 deletion packages/logger/src/types/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,46 @@ type LogRecordOrderOption = {
logFormatter?: never;
};

/**
* Options for the `logBuffer` constructor option.
*
* Used to configure the log buffer functionality.
*/
type LogBufferOption = {
logBufferOptions?: {
/**
* Whether logs should be buffered
*/
enabled?: boolean;
/**
* Maximum size of the buffer in bytes
* @default `20480`
*/
maxBytes?: number;
/**
* Flush the buffer when an error is logged
* @default `true`
*/
flushOnErrorLog?: boolean;
/**
* The threshold to buffer logs. Logs with a level below
* this threshold will be buffered
* @default `'DEBUG'`
*/
bufferAtVerbosity?: Omit<
LogLevel,
'ERROR' | 'error' | 'CRITICAL' | 'critical' | 'SILENT' | 'silent'
>;
};
};

/**
* Options to configure the Logger.
*/
type ConstructorOptions = BaseConstructorOptions &
(PersistentKeysOption | DeprecatedPersistentKeysOption) &
(LogFormatterOption | LogRecordOrderOption);
(LogFormatterOption | LogRecordOrderOption) &
LogBufferOption;

type LogItemMessage = string | LogAttributesWithMessage;
type LogItemExtraInput = [Error | string] | LogAttributes[];
Expand All @@ -194,6 +228,7 @@ type LoggerInterface = {
critical(input: LogItemMessage, ...extraInput: LogItemExtraInput): void;
debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void;
error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void;
flushBuffer(): void;
getLevelName(): Uppercase<LogLevel>;
getLogEvent(): boolean;
getPersistentLogAttributes(): LogAttributes;
Expand Down
Loading