strogger
Version:
📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations
252 lines • 10.8 kB
JavaScript
;
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