UNPKG

@energica-city/shared-amplify-utils

Version:

Shared utilities for AWS Amplify projects

371 lines 12.3 kB
/* eslint-disable no-console */ /** * Defines the log levels for the logger. */ export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["NONE"] = 0] = "NONE"; LogLevel[LogLevel["ERROR"] = 1] = "ERROR"; LogLevel[LogLevel["WARN"] = 2] = "WARN"; LogLevel[LogLevel["INFO"] = 3] = "INFO"; LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG"; })(LogLevel || (LogLevel = {})); /** * A singleton Logger class that provides structured and text-based logging. * It supports different log levels, contextual information, and can adapt * its output format based on the environment (e.g., development, production, AWS Lambda). */ class Logger { static instance; level = LogLevel.INFO; // Default level context = {}; useStructuredLogging = false; environment = 'development'; // Make constructor private to enforce singleton pattern constructor() { // Detect environment using multiple methods this.environment = this.detectEnvironment(); // Enable structured logging based on environment detection this.useStructuredLogging = this.shouldUseStructuredLogging(); } /** * Detects the current runtime environment. * It checks for Amplify-specific environment variables, AWS Lambda function names, * and standard `NODE_ENV`. Defaults to 'development'. * @returns The detected environment name (e.g., 'production', 'development'). */ detectEnvironment() { // Method 1: Explicit environment variable (set in backend.ts) if (process.env.environment === 'prod') { return 'production'; } // Method 2: AWS stack name pattern (matches backend.ts logic) const functionName = process.env.AWS_LAMBDA_FUNCTION_NAME; if (functionName && functionName.includes('-main-')) { return 'production'; } // Method 3: AWS execution environment if (process.env.AWS_EXECUTION_ENV) { return 'aws-lambda'; } // Method 4: Traditional NODE_ENV if (process.env.NODE_ENV === 'production') { return 'production'; } // Default to development return 'development'; } /** * Determines whether to use structured (JSON) logging. * Structured logging is enabled if the `STRUCTURED_LOGGING` environment variable is 'true', * or if the environment is detected as 'pro' or 'aws-lambda'. * @returns `true` if structured logging should be used, otherwise `false`. */ shouldUseStructuredLogging() { // Explicit override if (process.env.STRUCTURED_LOGGING === 'true') { return true; } if (process.env.STRUCTURED_LOGGING === 'false') { return false; } // Auto-enable for production and AWS Lambda environments return (this.environment === 'production' || this.environment === 'aws-lambda'); } /** * Gets the singleton instance of the Logger. * @returns The singleton Logger instance. */ static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } /** * Sets the log level for the logger. Messages with a level lower than * the set level will not be logged. * @param level The log level to set. */ setLevel(level) { this.level = level; } /** * Gets the current log level of the logger. * @returns The current LogLevel. */ getLevel() { return this.level; } /** * Gets the name of the current log level. * @returns The name of the current log level (e.g., 'INFO', 'DEBUG'). */ getLevelName() { return LogLevel[this.level]; } /** * Gets the detected runtime environment. * @returns The name of the environment (e.g., 'production', 'development'). */ getEnvironment() { return this.environment; } /** * Enables or disables structured (JSON) logging. * @param enabled `true` to enable structured logging, `false` to disable it. */ setStructuredLogging(enabled) { this.useStructuredLogging = enabled; } /** * Checks if structured logging is currently enabled. * @returns `true` if structured logging is enabled, otherwise `false`. */ isStructuredLoggingEnabled() { return this.useStructuredLogging; } /** * Sets context that will be included in all subsequent log messages. * The new context is merged with any existing context. * @param newContext A partial LogContext object to merge into the current context. */ setContext(newContext) { this.context = { ...this.context, ...newContext }; } /** * Gets a copy of the current log context. * @returns A copy of the current LogContext. */ getContext() { return { ...this.context }; } /** * Clears the current log context. */ clearContext() { this.context = {}; } /** * Retrieves AWS Lambda-specific context from environment variables. * @returns An object containing AWS request ID, function name, trace ID, and environment. */ getAWSContext() { const awsContext = {}; if (process.env.AWS_REQUEST_ID) { awsContext.awsRequestId = process.env.AWS_REQUEST_ID; } if (process.env.AWS_LAMBDA_FUNCTION_NAME) { awsContext.functionName = process.env.AWS_LAMBDA_FUNCTION_NAME; } if (process.env._X_AMZN_TRACE_ID) { awsContext.xrayTraceId = process.env._X_AMZN_TRACE_ID; } // Include detected environment awsContext.environment = this.environment; return awsContext; } /** * Creates a structured log entry object. * @param level The log level name (e.g., 'ERROR'). * @param message The main log message. * @param data Additional data to include in the log. * @returns A StructuredLogEntry object. */ createStructuredLog(level, message, data) { const logEntry = { timestamp: new Date().toISOString(), level, message, ...this.getAWSContext(), }; // Add context if it exists if (Object.keys(this.context).length > 0) { logEntry.context = this.context; } // Add data if provided if (data !== undefined) { logEntry.data = data; } return logEntry; } /** * Formats a log message with context for plain text logging. * @param level The log level name (e.g., 'INFO'). * @param args The array of arguments to log. * @returns A formatted log string. */ formatWithContext(level, args) { const contextStr = Object.keys(this.context).length > 0 ? ` ${JSON.stringify(this.context)}` : ''; const argsStr = args .map(arg => { try { return typeof arg === 'string' ? arg : JSON.stringify(arg); } catch { return String(arg); } }) .join(' '); return `[${level}]${contextStr} ${argsStr}`; } /** * The main logging method that handles both structured and text logging. * It checks the log level and formats the message accordingly. * @param level The log level of the message. * @param levelName The name of the log level. * @param args The arguments to log. */ logMessage(level, levelName, ...args) { if (this.level < level) return; if (this.useStructuredLogging) { // Structured JSON logging const message = args.length > 0 ? String(args[0]) : ''; const data = args.length > 1 ? args.slice(1) : undefined; const structuredLog = this.createStructuredLog(levelName, message, data); const output = JSON.stringify(structuredLog) + '\n'; // Use appropriate console method if (level === LogLevel.ERROR) { console.error(output); } else if (level === LogLevel.WARN) { console.warn(output); } else if (level === LogLevel.INFO) { console.info(output); } else { console.debug(output); } } else { // Text logging (existing behavior) const message = this.formatWithContext(levelName, args); if (level === LogLevel.ERROR) { console.error(message); } else if (level === LogLevel.WARN) { console.warn(message); } else if (level === LogLevel.INFO) { console.info(message); } else { console.debug(message); } } } /** * Logs an error message. * @param args The arguments to log. */ error(...args) { this.logMessage(LogLevel.ERROR, 'ERROR', ...args); } /** * Logs a warning message. * @param args The arguments to log. */ warn(...args) { this.logMessage(LogLevel.WARN, 'WARN', ...args); } /** * Logs an informational message. * @param args The arguments to log. */ info(...args) { this.logMessage(LogLevel.INFO, 'INFO', ...args); } /** * Logs a debug message. * @param args The arguments to log. */ debug(...args) { this.logMessage(LogLevel.DEBUG, 'DEBUG', ...args); } /** * Logs a message directly to the console without level checking or formatting. * All arguments are stringified. * @param args The arguments to log. */ log(...args) { const stringifiedArgs = args.map(arg => { try { return typeof arg === 'string' ? arg : JSON.stringify(arg); } catch { return String(arg); } }); console.log(...stringifiedArgs); } } /** * # Usage in AWS Lambda with CloudWatch * * This logger is optimized for use within AWS Lambda functions, sending structured * JSON logs to Amazon CloudWatch. * * ## Structured Logging * * When running in a detected AWS Lambda environment, the logger automatically * switches to structured JSON format. This provides several advantages in CloudWatch: * * - **Searchable Logs**: Structured logs enable easy searching and filtering in CloudWatch Logs * based on JSON fields (e.g., `level`, `context.requestId`, `data.someValue`). * - **CloudWatch Logs Insights**: Structured logs can be queried with CloudWatch * Logs Insights for powerful analysis and visualization. * - **Automatic Context**: The logger automatically captures Lambda-specific context * like `awsRequestId` and `functionName`. * * ## Example Handler * * ```typescript * import { logger } from './util/log'; * * export const handler = async (event, context) => { * // Set context for all logs in this invocation for easy tracking * logger.setContext({ * awsRequestId: context.awsRequestId, * userId: event.arguments.userId, // Example * }); * * try { * logger.info('Function started', { input: event.arguments }); * * // ... business logic implementation ... * * const result = { success: true }; * logger.info('Function finished successfully', { result }); * return result; * * } catch (error) { * // Log the error with structured data * logger.error('An unhandled error occurred', { * error: error.message, * stack: error.stack, * }); * // Rethrow or handle as appropriate * throw error; * } finally { * // Clear context to avoid leaking information between invocations * logger.clearContext(); * } * }; * ``` */ // Export a singleton instance export const logger = Logger.getInstance(); let logLevel; try { // Remove the debug console.log(process) statement logLevel = parseInt(process?.env?.LOG_LEVEL || '3'); } catch { logLevel = 3; } logger.setLevel(logLevel); //# sourceMappingURL=index.js.map