UNPKG

strogger

Version:

📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations

252 lines • 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.createCloudWatchTransport = void 0; const types_1 = require("../types"); const errors_1 = require("../utils/errors"); const base_transport_1 = require("./base-transport"); const createCloudWatchTransport = (options = {}) => { const transportName = "CloudWatch"; try { let minLevel = options.level ?? types_1.LogLevel.INFO; const formatter = options.formatter || { format: (entry) => JSON.stringify(entry), }; const logGroupName = options.logGroupName || process.env.CLOUDWATCH_LOG_GROUP || "/aws/lambda/my-function"; const logStreamName = options.logStreamName || process.env.CLOUDWATCH_LOG_STREAM; const region = options.region || process.env.AWS_REGION || "us-east-1"; const maxStreamSize = options.maxStreamSize ?? 45 * 1024 * 1024; // 45MB (leave buffer) const maxStreamAge = options.maxStreamAge ?? 23 * 60 * 60 * 1000; // 23 hours (leave buffer) const batchSize = options.batchSize || 10; const flushInterval = options.flushInterval || 5000; const timeout = options.timeout || 30000; // Validate required configuration (0, errors_1.validateEnvironmentVariable)("CLOUDWATCH_LOG_GROUP", logGroupName, false); (0, errors_1.validateEnvironmentVariable)("AWS_REGION", region, false); // Validate transport configuration (0, errors_1.validateTransportConfig)(transportName, { logGroupName, region }, [ "logGroupName", "region", ]); const state = { currentStreamName: logStreamName || `${new Date().toISOString().split("T")[0]}-${Date.now()}`, currentStreamSize: 0, streamStartTime: Date.now(), sequenceToken: undefined, batch: [], }; let flushTimer = null; const shouldRotateStream = () => { const timeSinceStart = Date.now() - state.streamStartTime; return (state.currentStreamSize >= maxStreamSize || timeSinceStart >= maxStreamAge); }; const generateStreamName = () => { const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); return logStreamName ? `${logStreamName}-${timestamp}` : `${new Date().toISOString().split("T")[0]}-${timestamp}`; }; const sendToCloudWatch = async (entries) => { try { // Dynamic import of AWS SDK v3 const { CloudWatchLogsClient, PutLogEventsCommand, CreateLogStreamCommand, } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/client-cloudwatch-logs"))); const client = new CloudWatchLogsClient({ region, requestHandler: { requestTimeout: timeout, }, }); // Create log stream if it doesn't exist try { await client.send(new CreateLogStreamCommand({ logGroupName, logStreamName: state.currentStreamName, })); } catch (error) { // Stream already exists or other error if (error instanceof Error && error.name !== "ResourceAlreadyExistsException") { throw error; } } const logEvents = entries.map((entry) => ({ timestamp: new Date(entry.timestamp).getTime(), message: formatter.format(entry), })); const command = new PutLogEventsCommand({ logGroupName, logStreamName: state.currentStreamName, logEvents, sequenceToken: state.sequenceToken, }); const response = await client.send(command); // Update sequence token if (response.nextSequenceToken) { state.sequenceToken = response.nextSequenceToken; } // Update size (approximate) const batchSize = logEvents.reduce((sum, event) => sum + event.message.length, 0); state.currentStreamSize += batchSize; } catch (error) { // Handle sequence token errors if (error instanceof Error && error.name === "InvalidSequenceTokenException") { const match = error.message.match(/sequenceToken is: (.+)/); if (match) { state.sequenceToken = match[1]; await sendToCloudWatch(entries); // Retry return; } } // Handle resource not found (log group doesn't exist) if (error instanceof Error && error.name === "ResourceNotFoundException") { throw (0, errors_1.createDetailedError)("CLOUDWATCH_LOG_GROUP_NOT_FOUND", transportName, { logGroupName, region, message: "Log group does not exist. Create it first in CloudWatch.", }); } throw (0, errors_1.createDetailedError)("CLOUDWATCH_API_ERROR", transportName, { error: error instanceof Error ? error.message : String(error), logGroupName, logStreamName: state.currentStreamName, region, }); } }; const rotateStream = async () => { try { // Flush current batch await flushBatch(); // Create new stream state.currentStreamName = generateStreamName(); state.currentStreamSize = 0; state.streamStartTime = Date.now(); state.sequenceToken = undefined; console.log(`[CLOUDWATCH] Rotated to stream: ${state.currentStreamName}`); } catch (error) { (0, errors_1.handleTransportError)(error, transportName, true); } }; const flushBatch = async () => { if (state.batch.length === 0) return; const entriesToSend = [...state.batch]; state.batch = []; await sendToCloudWatch(entriesToSend); }; const startFlushTimer = () => { if (flushTimer) return; flushTimer = setInterval(() => { flushBatch().catch((error) => { (0, errors_1.handleTransportError)(error, transportName, true); }); }, flushInterval); }; // Start the flush timer startFlushTimer(); return { log: async (entry) => { if (!(0, base_transport_1.shouldLog)(entry.level, minLevel)) return; // Check if stream rotation is needed if (shouldRotateStream()) { await rotateStream(); } // Add to batch state.batch.push(entry); // Flush if batch is full if (state.batch.length >= batchSize) { await flushBatch(); } }, setLevel: (level) => { minLevel = level; }, getLevel: () => minLevel, // CloudWatch specific methods rotateStream: async () => { await rotateStream(); }, getCurrentStream: () => state.currentStreamName, getCurrentStreamSize: () => state.currentStreamSize, flush: async () => { await flushBatch(); }, close: async () => { if (flushTimer) { clearInterval(flushTimer); flushTimer = null; } await flushBatch(); }, // Get current configuration getConfig: () => ({ logGroupName, logStreamName: state.currentStreamName, region, maxStreamSize, maxStreamAge, batchSize, flushInterval, timeout, }), // Get transport statistics getStats: () => ({ currentStream: state.currentStreamName, currentStreamSize: state.currentStreamSize, streamAge: Date.now() - state.streamStartTime, batchSize: state.batch.length, sequenceToken: state.sequenceToken ? "set" : "not set", flushTimerActive: !!flushTimer, }), }; } catch (error) { (0, errors_1.handleTransportError)(error, transportName, false); throw error; // Re-throw for proper error handling } }; exports.createCloudWatchTransport = createCloudWatchTransport; //# sourceMappingURL=cloudwatch-transport.js.map