import { ToolExecutionError } from '../../utils/error-handling.js'; import { getLogger } from '../../utils/logging.js'; import { parseTimeString, formatTimestamp } from '../../utils/time-utils.js'; import { trackToolRequest } from '../../utils/metrics.js'; import { validateWithZod } from '../../utils/validation.js'; import { CloudWatchLogsService, createCloudWatchLogsService } from '../../services/aws/cloudwatch-logs.js'; import { McpToolResponse, CloudWatchLogGroupsParams, CloudWatchLogsParams } from '../../types/tools.js'; import { LogGroup, OutputLogEvent } from '@aws-sdk/client-cloudwatch-logs'; // These imports are not currently used but kept for future reference // import { LogGroupInfo, LogEvent } from '../../types/aws.js'; import { CloudWatchLogGroupsSchema, CloudWatchLogsSchema } from '../../types/validation-schemas.js'; const logger = getLogger(); const service: CloudWatchLogsService = createCloudWatchLogsService(); /** * Format log groups for display * @param logGroups Array of log groups * @returns Formatted log groups as JSON string */ function formatLogGroups(logGroups: LogGroup[]): string { if (logGroups.length === 0) { return 'No log groups found matching the criteria.'; } return JSON.stringify(logGroups, null, 2); } /** * Format log events for display * @param events Array of log events * @returns Formatted log events as text */ function formatLogEvents(events: OutputLogEvent[]): string { if (events.length === 0) { return 'No log events found matching the criteria.'; } return events.map(event => { const timestamp = event.timestamp ? new Date(event.timestamp).toISOString() : 'unknown time'; return `[${timestamp}] ${event.message}`; }).join('\n'); } /** * Tool to list CloudWatch log groups * @param params Tool parameters * @returns MCP tool response */ export async function cloudWatchLogGroupsTool(params: CloudWatchLogGroupsParams): Promise { // Validate input parameters const validatedParams = validateWithZod( CloudWatchLogGroupsSchema, params, 'CloudWatch log groups parameters' ); const { prefix, limit } = validatedParams; const startTime = Date.now(); let success = false; logger.info('Fetching CloudWatch log groups', { prefix: prefix || 'none', limit: limit || 50 }); try { const logGroups = await service.listLogGroups({ prefix, limit }); const formattedResult = formatLogGroups(logGroups); success = true; return { content: [{ type: 'text' as const, text: formattedResult }] }; } catch (error) { logger.error('Error listing log groups:', error); // Wrap the error with tool execution context throw new ToolExecutionError( `Failed to list log groups: ${error instanceof Error ? error.message : String(error)}`, 'cloudWatchLogGroups', { prefix, limit }, error ); } finally { // Track metrics for this tool invocation const responseTime = Date.now() - startTime; trackToolRequest('cloudWatchLogGroups', responseTime, success); } } /** * Tool to retrieve CloudWatch logs * @param params Tool parameters * @returns MCP tool response */ export async function cloudWatchLogsTool(params: CloudWatchLogsParams): Promise { // Validate input parameters const validatedParams = validateWithZod( CloudWatchLogsSchema, params, 'CloudWatch logs parameters' ); const { logGroupName, logStreamName, filterPattern, startTime, endTime, limit } = validatedParams; const startAppTime = Date.now(); let success = false; try { // Process time inputs let startTimeMs: number | undefined; const endTimeMs = endTime ? parseTimeString(endTime) : undefined; startTimeMs = startTime ? parseTimeString(startTime) : undefined; // Default to 30 minutes ago if no start time if (!startTimeMs) { startTimeMs = Date.now() - 30 * 60 * 1000; } logger.info('Fetching CloudWatch logs', { logGroupName, logStreamName: logStreamName || 'all', startTime: startTimeMs ? formatTimestamp(startTimeMs) : 'undefined', endTime: endTimeMs ? formatTimestamp(endTimeMs) : 'now', limit: limit || 100 }); let logEvents: OutputLogEvent[] = []; // Use different approaches based on filtering needs if (filterPattern) { // Use FilterLogEvents for pattern filtering logEvents = await service.filterLogEvents({ logGroupName, logStreamNames: logStreamName ? [logStreamName] : undefined, filterPattern, startTime: startTimeMs, endTime: endTimeMs, limit: limit || 100 }); } else if (logStreamName) { // Get logs from a specific stream logEvents = await service.getLogEvents({ logGroupName, logStreamName, startTime: startTimeMs, endTime: endTimeMs, limit: limit || 100 }); } else { // Use FilterLogEvents without a pattern for multi-stream retrieval logEvents = await service.filterLogEvents({ logGroupName, startTime: startTimeMs, endTime: endTimeMs, limit: limit || 100 }); } const formattedLogs = formatLogEvents(logEvents); success = true; return { content: [{ type: 'text' as const, text: formattedLogs }] }; } catch (error) { logger.error('Error retrieving logs:', error); // Create a sanitized version of the parameters for error context const errorParams = { logGroupName, logStreamName: logStreamName || undefined, filterPattern: filterPattern || undefined, startTime: startTime ? 'provided' : undefined, endTime: endTime ? 'provided' : undefined, limit: limit || 100 }; // Wrap the error with tool execution context throw new ToolExecutionError( `Failed to retrieve logs: ${error instanceof Error ? error.message : String(error)}`, 'cloudWatchLogs', errorParams, error ); } finally { // Track metrics for this tool invocation const responseTime = Date.now() - startAppTime; trackToolRequest('cloudWatchLogs', responseTime, success); } }