cdk-serverless-agentic-api
Version:
CDK construct for serverless web applications with CloudFront, S3, Cognito, API Gateway, and Lambda
287 lines • 8.6 kB
JavaScript
;
/**
* Structured logging utilities for Lambda functions
* Provides consistent logging format across all Lambda functions
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetricsCollector = exports.Logger = exports.LogLevel = void 0;
exports.createLoggerFromEvent = createLoggerFromEvent;
exports.withPerformanceLogging = withPerformanceLogging;
var LogLevel;
(function (LogLevel) {
LogLevel["ERROR"] = "ERROR";
LogLevel["WARN"] = "WARN";
LogLevel["INFO"] = "INFO";
LogLevel["DEBUG"] = "DEBUG";
})(LogLevel || (exports.LogLevel = LogLevel = {}));
/**
* Structured logger class for Lambda functions
*/
class Logger {
constructor(service, context = {}, version = '1.0.0') {
this.service = service;
this.context = context;
this.version = version;
this.logLevel = this.parseLogLevel(process.env.LOG_LEVEL || 'INFO');
}
/**
* Parse log level from string
*/
parseLogLevel(level) {
const upperLevel = level.toUpperCase();
return Object.values(LogLevel).includes(upperLevel)
? upperLevel
: LogLevel.INFO;
}
/**
* Check if log level should be logged
*/
shouldLog(level) {
const levels = [LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO, LogLevel.DEBUG];
const currentLevelIndex = levels.indexOf(this.logLevel);
const messageLevelIndex = levels.indexOf(level);
return messageLevelIndex <= currentLevelIndex;
}
/**
* Create structured log entry
*/
createLogEntry(level, message, data, error, duration) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
service: this.service,
version: this.version,
context: this.context
};
if (data !== undefined) {
entry.data = data;
}
if (error) {
entry.error = {
name: error.name,
message: error.message,
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
};
}
if (duration !== undefined) {
entry.duration = duration;
}
return entry;
}
/**
* Log structured entry to console
*/
log(entry) {
if (!this.shouldLog(entry.level)) {
return;
}
const logMessage = JSON.stringify(entry);
switch (entry.level) {
case LogLevel.ERROR:
console.error(logMessage);
break;
case LogLevel.WARN:
console.warn(logMessage);
break;
case LogLevel.DEBUG:
console.debug(logMessage);
break;
default:
console.log(logMessage);
}
}
/**
* Log error message
*/
error(message, error, data) {
this.log(this.createLogEntry(LogLevel.ERROR, message, data, error));
}
/**
* Log warning message
*/
warn(message, data) {
this.log(this.createLogEntry(LogLevel.WARN, message, data));
}
/**
* Log info message
*/
info(message, data) {
this.log(this.createLogEntry(LogLevel.INFO, message, data));
}
/**
* Log debug message
*/
debug(message, data) {
this.log(this.createLogEntry(LogLevel.DEBUG, message, data));
}
/**
* Log request start
*/
logRequestStart(event) {
this.info('Request started', {
httpMethod: event.httpMethod,
path: event.path,
queryStringParameters: event.queryStringParameters,
headers: this.sanitizeHeaders(event.headers),
isBase64Encoded: event.isBase64Encoded
});
}
/**
* Log request end with duration
*/
logRequestEnd(statusCode, duration, responseSize) {
this.info('Request completed', {
statusCode,
duration,
responseSize
});
}
/**
* Log authentication event
*/
logAuthentication(success, userId, groups, reason) {
this.info('Authentication event', {
success,
userId,
groups,
reason
});
}
/**
* Log authorization event
*/
logAuthorization(success, requiredGroup, userGroups, reason) {
this.info('Authorization event', {
success,
requiredGroup,
userGroups,
reason
});
}
/**
* Sanitize headers to remove sensitive information
*/
sanitizeHeaders(headers) {
if (!headers)
return headers;
const sanitized = { ...headers };
const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token'];
// Check for case-insensitive matches
Object.keys(sanitized).forEach(key => {
const lowerKey = key.toLowerCase();
if (sensitiveHeaders.includes(lowerKey)) {
sanitized[key] = '[REDACTED]';
}
});
return sanitized;
}
/**
* Create child logger with additional context
*/
child(additionalContext) {
const newContext = { ...this.context, ...additionalContext };
return new Logger(this.service, newContext, this.version);
}
/**
* Update context for current logger
*/
updateContext(additionalContext) {
this.context = { ...this.context, ...additionalContext };
}
}
exports.Logger = Logger;
/**
* Create logger from API Gateway event
*/
function createLoggerFromEvent(event, service = 'lambda-function') {
// Handle null or undefined events
if (!event) {
return new Logger(service, {}, process.env.API_VERSION || '1.0.0');
}
const context = {
requestId: event.requestContext?.requestId,
path: event.path,
method: event.httpMethod,
stage: event.requestContext?.stage,
sourceIp: event.requestContext?.identity?.sourceIp,
userAgent: event.headers?.['User-Agent'] || event.headers?.['user-agent'],
correlationId: event.headers?.['X-Correlation-ID'] || event.headers?.['x-correlation-id']
};
// Extract user information from Cognito claims if available
const claims = event.requestContext?.authorizer?.claims;
if (claims) {
context.userId = claims['cognito:username'] || claims.sub;
context.userGroups = claims['cognito:groups'] ? claims['cognito:groups'].split(',') : [];
}
return new Logger(service, context, process.env.API_VERSION || '1.0.0');
}
/**
* Performance monitoring decorator for Lambda handlers
*/
function withPerformanceLogging(handler, logger) {
return (async (...args) => {
const startTime = Date.now();
try {
const result = await handler(...args);
const duration = Date.now() - startTime;
logger.info('Handler execution completed', { duration });
return result;
}
catch (error) {
const duration = Date.now() - startTime;
logger.error('Handler execution failed', error, { duration });
throw error;
}
});
}
/**
* Metrics collection utilities
*/
class MetricsCollector {
constructor(logger) {
this.logger = logger;
}
/**
* Record custom metric
*/
recordMetric(name, value, unit = 'Count', dimensions) {
this.logger.info('Custom metric recorded', {
metricName: name,
value,
unit,
dimensions
});
}
/**
* Record API response time
*/
recordResponseTime(endpoint, method, duration, statusCode) {
this.recordMetric('ResponseTime', duration, 'Milliseconds', {
endpoint,
method,
statusCode: statusCode.toString()
});
}
/**
* Record error count
*/
recordError(errorType, endpoint, method) {
this.recordMetric('ErrorCount', 1, 'Count', {
errorType,
endpoint: endpoint || 'unknown',
method: method || 'unknown'
});
}
/**
* Record business metric
*/
recordBusinessMetric(name, value, userId, userGroups) {
this.recordMetric(name, value, 'Count', {
userId: userId || 'anonymous',
userGroups: userGroups?.join(',') || 'none'
});
}
}
exports.MetricsCollector = MetricsCollector;
//# sourceMappingURL=logging.js.map