AWS Powertools for Lambda의 최신 릴리스를 사용하면 사용자 지정 기능으로 Logger를 더 쉽게 확장할 수 있습니다.
Logger 클래스의 확장성이 더욱 향상되었습니다
이제 이전에 비공개였던 Logger 메서드 createAndPopulateLogItem, printLog 및 processLogItem을 덮어쓸 수 있습니다. 이를 통해 Logger를 확장하고 새로운 기능을 추가할 수 있습니다(예: 자체 메시지 버퍼 구현).
v2.12.0 출시
저는 이 새로운 확장성을 사용하여 로그 버퍼링 및 플러시라는 유용한 기능을 구현하고 있습니다. 아이디어는 간단합니다. 프로덕션 환경에서는 일반적으로 경고 및 오류와 같은 중요한 정보만 기록합니다. 로그 공간은 비용이 많이 들고 소음이 발생할 수 있기 때문입니다. 그러나 오류가 발생하면 가능한 모든 정보가 필요합니다. 즉, 함수 전체에 흩어져 있는 모든 디버그 및 정보 로그를 사용할 수 있어야 합니다. 하지만 로그 수준을 너무 낮게 설정해서 그런 것은 아닙니다.
이러한 디버그 및 정보 로그를 모두 내부적으로 수집하고, 오류와 같은 중요한 이벤트가 발생하면 콘솔에 인쇄하면 어떻게 될까요? 저는 로그를 하위 로그와 상위 로그라는 두 가지 범주로 분류합니다. 구성된 로그 수준이 WARN인 경우 DEBUG 또는 INFO 로그는 낮은 수준이고 ERROR는 높은 수준 로그입니다.
이제 낮은 수준의 로그를 인쇄할 때 지금처럼 버리는 대신 내부 목록에 로그를 버퍼링합니다. 높은 수준의 로그가 있으면 즉시 버퍼링된 모든 로그를 콘솔로 플러시합니다.
이 기능을 추가하기 위해 Powertools에서 Logger 클래스를 확장하고 processLogItem()을 재정의하는 새 클래스를 생성합니다. 이는 logger.debug()와 같은 다양한 로그 메소드에 의해 호출되는 중심 메소드입니다. 원래 구현에서는 로그 항목이 올바른 수준에 있는 경우 콘솔에 인쇄합니다. 이 방법을 재정의하면 로그 수준에 따라 로그를 버퍼링하고 플러시하는 특수 논리를 추가할 수 있습니다.
import { LogItem, Logger as PowertoolsLogger } from '@aws-lambda-powertools/logger'; import type { LogItemExtraInput, LogItemMessage } from '@aws-lambda-powertools/logger/types'; export class Logger extends PowertoolsLogger { #buffer: Record<string, Array<[number, string]>> = {}; get buffer(): Record<string, Array<[number, string]>> { return this.#buffer; } protected override processLogItem(logLevel: number, input: LogItemMessage, extraInput: LogItemExtraInput): void { const xRayTraceId = this['envVarsService'].getXrayTraceId() as string; // Flush buffer when log level is higher than the configured log level if (logLevel > this.level && xRayTraceId) { const buffer = this.#buffer[xRayTraceId] ?? []; // Print all log items in the buffer if (buffer.length) this.info(`Flushing buffer with ${buffer.length} log items`); for (const [bufferLogLevel, bufferLogItem] of buffer) { // Create a new LogItem from the stringified log item this.printLog(bufferLogLevel, new LogItem(JSON.parse(bufferLogItem))); } // Clear the buffer after flushing // This also removes entries from other X-Ray trace IDs this.#buffer = {}; } // Buffer the log item when log level is lower than the configured log level if (logLevel < this.level && xRayTraceId) { const buffer = this.#buffer[xRayTraceId] ?? []; // Add the stringified log item to the buffer // Serializing the log item ensures it is not mutated after being added to the buffer buffer.push([logLevel, JSON.stringify(this.createAndPopulateLogItem(logLevel, input, extraInput))]); // Update the buffer with the new log item // This also removes other X-Ray trace IDs from the buffer this.#buffer = { [xRayTraceId]: buffer, }; } // Call the parent method to ensure the log item is processed super.processLogItem(logLevel, input, extraInput); } }
여기서 X-Ray Trace ID를 사용하는 이유가 궁금하실 수도 있습니다. 핸들러 함수 외부에서 Logger를 인스턴스화하는 것이 일반적입니다. 그러나 Lambda 실행 환경은 잠재적으로 여러 호출에 재사용되므로 버퍼에는 이전 호출의 로그 항목이 포함될 수 있습니다. 이것이 바로 버퍼가 단순한 배열이 아닌 객체로 구현되는 이유입니다. X-Ray 추적 ID를 식별자로 사용하여 동일한 호출의 로그 항목만 버퍼링합니다.
버퍼는 단순한 배열이 아닌 객체로 구현됩니다. 버퍼가 플러시되면 간단히 객체를 재설정하여 다른 호출에서 항목을 제거할 수 있습니다.
로컬에서 구현을 빠르게 검증해 보겠습니다.
// set X-Ray Trace ID manually if running locally process.env._X_AMZN_TRACE_ID = '1-abcdef12-3456abcdef123456abcdef12'; // log level = WARN const logger = new Logger({ logLevel: 'WARN' }); logger.debug('debug'); // < log level logger.info('info'); // < log level logger.warn('warn'); // = log level logger.error('error'); // > log level
우리가 얻은 결과는 다음과 같습니다.
import { LogItem, Logger as PowertoolsLogger } from '@aws-lambda-powertools/logger'; import type { LogItemExtraInput, LogItemMessage } from '@aws-lambda-powertools/logger/types'; export class Logger extends PowertoolsLogger { #buffer: Record<string, Array<[number, string]>> = {}; get buffer(): Record<string, Array<[number, string]>> { return this.#buffer; } protected override processLogItem(logLevel: number, input: LogItemMessage, extraInput: LogItemExtraInput): void { const xRayTraceId = this['envVarsService'].getXrayTraceId() as string; // Flush buffer when log level is higher than the configured log level if (logLevel > this.level && xRayTraceId) { const buffer = this.#buffer[xRayTraceId] ?? []; // Print all log items in the buffer if (buffer.length) this.info(`Flushing buffer with ${buffer.length} log items`); for (const [bufferLogLevel, bufferLogItem] of buffer) { // Create a new LogItem from the stringified log item this.printLog(bufferLogLevel, new LogItem(JSON.parse(bufferLogItem))); } // Clear the buffer after flushing // This also removes entries from other X-Ray trace IDs this.#buffer = {}; } // Buffer the log item when log level is lower than the configured log level if (logLevel < this.level && xRayTraceId) { const buffer = this.#buffer[xRayTraceId] ?? []; // Add the stringified log item to the buffer // Serializing the log item ensures it is not mutated after being added to the buffer buffer.push([logLevel, JSON.stringify(this.createAndPopulateLogItem(logLevel, input, extraInput))]); // Update the buffer with the new log item // This also removes other X-Ray trace IDs from the buffer this.#buffer = { [xRayTraceId]: buffer, }; } // Call the parent method to ensure the log item is processed super.processLogItem(logLevel, input, extraInput); } }
디버그 및 정보 로그가 버퍼링되었기 때문에 경고가 첫 번째 메시지입니다. 오류가 기록되면 오류가 실제로 인쇄되기 전에 버퍼링된 로그를 플러시하고 정보를 인쇄했습니다.
저의 단순한 구현에는 몇 가지 주의 사항이 있습니다. 가장 중요한 것은 버퍼 크기가 제한되지 않는다는 것입니다. 즉, 버퍼가 너무 커지면 메모리 문제가 발생할 수 있습니다. 예를 들어 가장 최근 로그만 유지하는 슬라이딩 창으로 버퍼를 구현하거나 총 버퍼 크기를 제한하는 등의 방법으로 이 문제를 완화할 수 있는 몇 가지 접근 방식이 있습니다.
또한 버퍼링된 로그는 logger.error()와 같이 제어된 경우에만 플러시되고 처리되지 않은 오류에서는 플러시되지 않습니다. 이 동작은 버퍼를 공개하고 Middy.js와 같은 미들웨어를 사용하면 쉽게 달성할 수 있습니다. Middy는 버퍼를 플러시하는 데 활용할 수 있는 onError 이벤트를 노출합니다.
공식 AWS Powertools for Lambda 리포지토리에 대한 이 의견 요청에서 이에 대해 더 광범위하게 작성했습니다.
이 기능이 Powertools for Lambda의 일부가 되는 것을 보고 싶으시면 거기에 아이디어와 피드백을 공유해 주세요.
위 내용은 Powertools for Lambda를 사용하여 오류 발생 시 자동으로 로그 버퍼링 및 플러시의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!