UNPKG

@logtape/cloudwatch-logs

Version:

AWS CloudWatch Logs sink for LogTape

112 lines (110 loc) 3.87 kB
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs'); const __aws_sdk_client_cloudwatch_logs = require_rolldown_runtime.__toESM(require("@aws-sdk/client-cloudwatch-logs")); const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape")); //#region sink.ts const MAX_BATCH_SIZE_EVENTS = 1e4; const MAX_BATCH_SIZE_BYTES = 1048576; const OVERHEAD_PER_EVENT = 26; /** * Gets a CloudWatch Logs sink that sends log records to AWS CloudWatch Logs. * * @param options Configuration options for the CloudWatch Logs sink. * @returns A sink that sends log records to CloudWatch Logs. * @since 1.0.0 */ function getCloudWatchLogsSink(options) { const client = options.client ?? new __aws_sdk_client_cloudwatch_logs.CloudWatchLogsClient({ region: options.region ?? "us-east-1", credentials: options.credentials }); const batchSize = Math.min(Math.max(options.batchSize ?? 1e3, 1), MAX_BATCH_SIZE_EVENTS); const flushInterval = options.flushInterval ?? 1e3; const maxRetries = Math.max(options.maxRetries ?? 3, 0); const retryDelay = Math.max(options.retryDelay ?? 100, 0); const defaultFormatter = (record) => { let result = ""; for (let i = 0; i < record.message.length; i++) if (i % 2 === 0) result += record.message[i]; else result += JSON.stringify(record.message[i]); return result; }; const formatter = options.formatter ?? defaultFormatter; const logEvents = []; let currentBatchSize = 0; let flushTimer = null; let disposed = false; function scheduleFlush() { if (flushInterval <= 0 || flushTimer !== null) return; flushTimer = setTimeout(() => { flushTimer = null; if (logEvents.length > 0) flushEvents(); }, flushInterval); } async function flushEvents() { if (logEvents.length === 0 || disposed) return; const events = logEvents.splice(0); currentBatchSize = 0; if (flushTimer !== null) { clearTimeout(flushTimer); flushTimer = null; } await sendEventsWithRetry(events, maxRetries); } async function sendEventsWithRetry(events, remainingRetries) { try { const command = new __aws_sdk_client_cloudwatch_logs.PutLogEventsCommand({ logGroupName: options.logGroupName, logStreamName: options.logStreamName, logEvents: events }); await client.send(command); } catch (error) { if (remainingRetries > 0) { const attemptNumber = maxRetries - remainingRetries; const delay = retryDelay * Math.pow(2, attemptNumber); await new Promise((resolve) => setTimeout(resolve, delay)); await sendEventsWithRetry(events, remainingRetries - 1); } else { const metaLogger = (0, __logtape_logtape.getLogger)([ "logtape", "meta", "cloudwatch-logs" ]); metaLogger.error("Failed to send log events to CloudWatch Logs after {maxRetries} retries: {error}", { maxRetries, error }); } } } function formatLogMessage(record) { return formatter(record); } const sink = (record) => { if (disposed) return; if (record.category[0] === "logtape" && record.category[1] === "meta" && record.category[2] === "cloudwatch-logs") return; const message = formatLogMessage(record); const messageBytes = new TextEncoder().encode(message).length; const eventSize = messageBytes + OVERHEAD_PER_EVENT; const logEvent = { timestamp: record.timestamp, message }; logEvents.push(logEvent); currentBatchSize += eventSize; const shouldFlushBySize = currentBatchSize > MAX_BATCH_SIZE_BYTES; const shouldFlushByCount = logEvents.length >= batchSize; if (shouldFlushBySize || shouldFlushByCount) flushEvents(); else scheduleFlush(); }; sink[Symbol.asyncDispose] = async () => { if (flushTimer !== null) { clearTimeout(flushTimer); flushTimer = null; } await flushEvents(); disposed = true; }; return sink; } //#endregion exports.getCloudWatchLogsSink = getCloudWatchLogsSink;