@chinchillaenterprises/mcp-amplify
Version:
AWS Amplify MCP server with intelligent deployment automation, specialized logging suite, and recursive resource discovery
1,130 lines • 50.6 kB
JavaScript
import { DescribeLogGroupsCommand, FilterLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
import { getCurrentClients } from './account-handlers.js';
import { handleAmplifyDiscoverResources } from './resource-discovery-handlers.js';
// Helper function to parse time ranges
function parseTimeRange(timeRange) {
const now = new Date();
const endTime = new Date(now);
let startTime;
switch (timeRange) {
case '5m':
startTime = new Date(now.getTime() - 5 * 60 * 1000);
break;
case '15m':
startTime = new Date(now.getTime() - 15 * 60 * 1000);
break;
case '1h':
startTime = new Date(now.getTime() - 60 * 60 * 1000);
break;
case '6h':
startTime = new Date(now.getTime() - 6 * 60 * 60 * 1000);
break;
case '24h':
startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
break;
default:
startTime = new Date(now.getTime() - 15 * 60 * 1000); // Default to 15 minutes
}
return { startTime, endTime };
}
// Helper function to build log level filter patterns
function buildLogLevelFilter(logLevel) {
switch (logLevel.toUpperCase()) {
case 'ERROR':
return '[ERROR]';
case 'WARN':
return '[WARN]';
case 'INFO':
return '[INFO]';
case 'DEBUG':
return '[DEBUG]';
case 'ALL':
default:
return '';
}
}
export async function handleAmplifyGetLambdaLogs(args) {
const { appId, branchName, functionName, logLevel = 'all', timeRange = '15m', customStartTime, customEndTime, maxEvents = 100, includeMetrics = false, filterPattern } = args;
if (!appId) {
throw new Error('appId is required');
}
try {
const clients = getCurrentClients();
// First, discover Lambda resources to get accurate function names and log groups
console.error('🔍 Discovering Lambda resources...');
const discoveryResult = await handleAmplifyDiscoverResources({
appId,
branchName,
resourceType: 'lambda'
});
if (!discoveryResult.success || !discoveryResult.lambdaFunctions || discoveryResult.lambdaFunctions.length === 0) {
return {
success: false,
error: 'No Lambda functions found for this app/branch',
appId,
branchName
};
}
// Filter functions based on functionName parameter
let targetFunctions = discoveryResult.lambdaFunctions;
if (functionName && functionName !== 'all') {
targetFunctions = discoveryResult.lambdaFunctions.filter((func) => func.functionName.includes(functionName) || func.logicalId.includes(functionName));
if (targetFunctions.length === 0) {
return {
success: false,
error: `No Lambda functions found matching: ${functionName}`,
appId,
branchName,
availableFunctions: discoveryResult.lambdaFunctions.map((f) => f.functionName)
};
}
}
// Parse time range
let startTime, endTime;
if (customStartTime && customEndTime) {
startTime = new Date(customStartTime);
endTime = new Date(customEndTime);
}
else {
({ startTime, endTime } = parseTimeRange(timeRange));
}
// Build filter pattern
let combinedFilter = '';
const logLevelFilter = buildLogLevelFilter(logLevel);
if (logLevelFilter) {
combinedFilter = logLevelFilter;
}
if (filterPattern) {
combinedFilter = combinedFilter ? `${combinedFilter} ${filterPattern}` : filterPattern;
}
console.error(`📋 Fetching logs for ${targetFunctions.length} Lambda function(s)...`);
// Fetch logs for each function
const functionLogs = [];
for (const func of targetFunctions) {
console.error(` 📄 Processing ${func.functionName}...`);
try {
const filterCommand = new FilterLogEventsCommand({
logGroupName: func.logGroupName,
startTime: startTime.getTime(),
endTime: endTime.getTime(),
filterPattern: combinedFilter || undefined,
limit: maxEvents
});
const logResponse = await clients.cloudwatchLogs.send(filterCommand);
const logs = logResponse.events?.map((event) => ({
timestamp: new Date(event.timestamp).toISOString(),
message: event.message,
logStream: event.logStreamName,
requestId: extractRequestId(event.message || '')
})) || [];
functionLogs.push({
functionName: func.functionName,
logicalId: func.logicalId,
logGroupName: func.logGroupName,
logsFound: logs.length,
logs: logs.slice(0, maxEvents)
});
}
catch (error) {
console.error(`❌ Error fetching logs for ${func.functionName}:`, error);
functionLogs.push({
functionName: func.functionName,
logicalId: func.logicalId,
logGroupName: func.logGroupName,
logsFound: 0,
logs: [],
error: error instanceof Error ? error.message : String(error)
});
}
}
// Calculate summary statistics
const totalLogs = functionLogs.reduce((sum, func) => sum + func.logsFound, 0);
const functionsWithLogs = functionLogs.filter(func => func.logsFound > 0).length;
const functionsWithErrors = functionLogs.filter(func => func.error).length;
return {
success: true,
appId,
branchName,
timeRange: customStartTime ? 'custom' : timeRange,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
logLevel,
filterPattern: combinedFilter || null,
totalFunctions: targetFunctions.length,
functionsWithLogs,
functionsWithErrors,
totalLogs,
functionLogs,
summary: {
queryInfo: {
timeRange: customStartTime ? 'custom' : timeRange,
logLevel,
filterPattern: combinedFilter || null,
maxEvents
},
results: {
totalLogs,
functionsWithLogs,
functionsWithErrors
}
}
};
}
catch (error) {
console.error('❌ Error in handleAmplifyGetLambdaLogs:', error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
appId,
branchName
};
}
}
// Helper function to extract request ID from log message
function extractRequestId(message) {
const requestIdMatch = message.match(/\[REQUEST ID: ([^\]]+)\]/);
if (requestIdMatch) {
return requestIdMatch[1];
}
const awsRequestIdMatch = message.match(/RequestId: ([a-f0-9-]+)/);
if (awsRequestIdMatch) {
return awsRequestIdMatch[1];
}
return null;
}
// Helper function to build AppSync-specific filter patterns
function buildAppSyncFilter(resolverType, resolverName, logLevel) {
let filters = [];
// Add resolver type filter
if (resolverType !== 'all') {
filters.push(`[timestamp, requestId, level, message, resolver="${resolverType}"]`);
}
// Add resolver name filter
if (resolverName) {
filters.push(`[timestamp, requestId, level, message, resolver~"${resolverName}"]`);
}
// Add log level filter
if (logLevel && logLevel !== 'all') {
filters.push(`[timestamp, requestId, level="${logLevel.toUpperCase()}"]`);
}
return filters.length > 0 ? filters.join(' ') : '';
}
// Helper function to extract GraphQL operation details from AppSync log messages
function extractGraphQLDetails(message) {
const details = {};
// Extract operation type (Query, Mutation, Subscription)
const operationMatch = message.match(/\b(Query|Mutation|Subscription)\b/);
if (operationMatch) {
details.operationType = operationMatch[1];
}
// Extract operation name
const nameMatch = message.match(/operation\s+(\w+)/i);
if (nameMatch) {
details.operationName = nameMatch[1];
}
// Extract field being resolved
const fieldMatch = message.match(/field\s+(\w+)/i);
if (fieldMatch) {
details.field = fieldMatch[1];
}
// Extract execution time
const timeMatch = message.match(/(\d+(?:\.\d+)?)\s*ms/);
if (timeMatch) {
details.executionTime = parseFloat(timeMatch[1]);
}
return details;
}
export async function handleAmplifyGetAppSyncLogs(args) {
const { appId, branchName, resolverType = 'all', resolverName, timeRange = '15m', customStartTime, customEndTime, includePerformance = false, includeRequestResponse = false, logLevel = 'all', maxEvents = 100 } = args;
if (!appId) {
throw new Error('appId is required');
}
try {
const clients = getCurrentClients();
// First, discover AppSync resources to get accurate API IDs and log groups
console.error('🔍 Discovering AppSync resources...');
const discoveryResult = await handleAmplifyDiscoverResources({
appId,
branchName,
resourceType: 'all' // We need to discover all resources to find AppSync APIs
});
if (!discoveryResult.success) {
return {
success: false,
error: 'Failed to discover AppSync resources',
appId,
branchName
};
}
// Look for AppSync APIs in the discovered resources
const appSyncApis = [];
if (discoveryResult.apiGateways) {
// Check if any API Gateways are actually AppSync APIs
for (const api of discoveryResult.apiGateways) {
if (api.apiType === 'GRAPHQL' || api.logicalId.toLowerCase().includes('appsync')) {
let apiId = api.apiId;
// Extract API ID from ARN if needed
if (apiId && apiId.includes('arn:aws:appsync')) {
apiId = apiId.split('/').pop();
}
appSyncApis.push({
apiId,
logicalId: api.logicalId,
logGroupName: `/aws/appsync/apis/${apiId}`,
name: api.name || api.logicalId
});
}
}
}
// Also try to find AppSync APIs directly by looking for known patterns
try {
const describeCommand = new DescribeLogGroupsCommand({
logGroupNamePrefix: '/aws/appsync/apis/',
limit: 50
});
const logGroupsResponse = await clients.cloudwatchLogs.send(describeCommand);
for (const logGroup of logGroupsResponse.logGroups || []) {
if (logGroup.logGroupName && logGroup.logGroupName.includes(appId)) {
const apiIdMatch = logGroup.logGroupName.match(/\/aws\/appsync\/apis\/([^\/]+)/);
if (apiIdMatch) {
const apiId = apiIdMatch[1];
const existing = appSyncApis.find(api => api.apiId === apiId);
if (!existing) {
appSyncApis.push({
apiId,
logicalId: `AppSync-${apiId}`,
logGroupName: logGroup.logGroupName,
name: `AppSync API ${apiId}`
});
}
}
}
}
}
catch (error) {
console.warn('⚠️ Could not scan for AppSync log groups:', error);
}
if (appSyncApis.length === 0) {
return {
success: false,
error: 'No AppSync APIs found for this app/branch',
appId,
branchName
};
}
// Parse time range
let startTime, endTime;
if (customStartTime && customEndTime) {
startTime = new Date(customStartTime);
endTime = new Date(customEndTime);
}
else {
({ startTime, endTime } = parseTimeRange(timeRange));
}
// Build filter pattern
const filterPattern = buildAppSyncFilter(resolverType, resolverName, logLevel);
console.error(`📋 Fetching AppSync logs for ${appSyncApis.length} API(s)...`);
// Fetch logs for each AppSync API
const apiLogs = [];
for (const api of appSyncApis) {
console.error(` 📄 Processing ${api.name}...`);
try {
const filterCommand = new FilterLogEventsCommand({
logGroupName: api.logGroupName,
startTime: startTime.getTime(),
endTime: endTime.getTime(),
filterPattern: filterPattern || undefined,
limit: maxEvents
});
const logResponse = await clients.cloudwatchLogs.send(filterCommand);
const logs = logResponse.events?.map((event) => {
const graphqlDetails = extractGraphQLDetails(event.message || '');
const baseLog = {
timestamp: new Date(event.timestamp).toISOString(),
message: event.message,
logStream: event.logStreamName,
requestId: extractRequestId(event.message || ''),
...graphqlDetails
};
// Add performance data if requested
if (includePerformance && graphqlDetails.executionTime) {
baseLog.performance = {
executionTime: graphqlDetails.executionTime,
isSlowQuery: graphqlDetails.executionTime > 1000 // Consider >1s as slow
};
}
// Add request/response data if requested (simplified for now)
if (includeRequestResponse) {
const hasRequest = event.message?.includes('request') || event.message?.includes('variables');
const hasResponse = event.message?.includes('response') || event.message?.includes('data');
baseLog.requestResponse = {
hasRequestData: hasRequest,
hasResponseData: hasResponse
};
}
return baseLog;
}) || [];
// Calculate performance statistics if requested
let performanceStats = {};
if (includePerformance) {
const timings = logs.filter((log) => log.executionTime).map((log) => log.executionTime);
if (timings.length > 0) {
performanceStats = {
averageExecutionTime: timings.reduce((sum, time) => sum + time, 0) / timings.length,
maxExecutionTime: Math.max(...timings),
minExecutionTime: Math.min(...timings),
slowQueries: timings.filter((time) => time > 1000).length,
totalQueriesWithTiming: timings.length
};
}
}
apiLogs.push({
apiId: api.apiId,
logicalId: api.logicalId,
name: api.name,
logGroupName: api.logGroupName,
logsFound: logs.length,
logs: logs.slice(0, maxEvents),
performanceStats: includePerformance ? performanceStats : undefined
});
}
catch (error) {
console.error(`❌ Error fetching logs for ${api.name}:`, error);
apiLogs.push({
apiId: api.apiId,
logicalId: api.logicalId,
name: api.name,
logGroupName: api.logGroupName,
logsFound: 0,
logs: [],
error: error instanceof Error ? error.message : String(error)
});
}
}
// Calculate summary statistics
const totalLogs = apiLogs.reduce((sum, api) => sum + api.logsFound, 0);
const apisWithLogs = apiLogs.filter(api => api.logsFound > 0).length;
const apisWithErrors = apiLogs.filter(api => api.error).length;
// Extract operation type statistics
const operationStats = {};
apiLogs.forEach(api => {
api.logs.forEach((log) => {
if (log.operationType) {
operationStats[log.operationType] = (operationStats[log.operationType] || 0) + 1;
}
});
});
return {
success: true,
appId,
branchName,
timeRange: customStartTime ? 'custom' : timeRange,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
resolverType,
resolverName: resolverName || null,
logLevel,
includePerformance,
includeRequestResponse,
filterPattern: filterPattern || null,
totalApis: appSyncApis.length,
apisWithLogs,
apisWithErrors,
totalLogs,
operationStats,
apiLogs,
summary: {
queryInfo: {
timeRange: customStartTime ? 'custom' : timeRange,
resolverType,
resolverName: resolverName || null,
logLevel,
maxEvents
},
results: {
totalLogs,
apisWithLogs,
apisWithErrors,
operationBreakdown: operationStats
},
recommendations: [
totalLogs === 0 ? "No logs found. Check if AppSync API is receiving traffic." : null,
includePerformance ? "Performance data included. Check for slow queries (>1s)." : "Consider enabling includePerformance for query performance insights.",
includeRequestResponse ? "Request/response data included." : "Consider enabling includeRequestResponse for detailed request analysis."
].filter(Boolean)
}
};
}
catch (error) {
console.error('❌ Error in handleAmplifyGetAppSyncLogs:', error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
appId,
branchName
};
}
}
// Helper function to build API Gateway-specific filter patterns
function buildApiGatewayFilter(httpMethod, endpoint, statusCode) {
let filters = [];
// Add HTTP method filter
if (httpMethod && httpMethod !== 'all') {
filters.push(`[timestamp, requestId, level, message, method="${httpMethod}"]`);
}
// Add endpoint filter
if (endpoint) {
filters.push(`[timestamp, requestId, level, message, path~"${endpoint}"]`);
}
// Add status code filter
if (statusCode) {
filters.push(`[timestamp, requestId, level, message, status="${statusCode}"]`);
}
return filters.length > 0 ? filters.join(' ') : '';
}
// Helper function to extract API Gateway request details from log messages
function extractApiGatewayDetails(message) {
const details = {};
// Extract HTTP method
const methodMatch = message.match(/\b(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/);
if (methodMatch) {
details.httpMethod = methodMatch[1];
}
// Extract endpoint/path
const pathMatch = message.match(/\/[^\s]*\s/);
if (pathMatch) {
details.endpoint = pathMatch[0].trim();
}
// Extract status code
const statusMatch = message.match(/\s(\d{3})\s/);
if (statusMatch) {
details.statusCode = parseInt(statusMatch[1]);
}
// Extract response time/latency
const timeMatch = message.match(/(\d+(?:\.\d+)?)\s*ms/);
if (timeMatch) {
details.responseTime = parseFloat(timeMatch[1]);
}
// Extract request ID
const requestIdMatch = message.match(/\[([a-f0-9-]+)\]/);
if (requestIdMatch) {
details.requestId = requestIdMatch[1];
}
// Extract user agent
const userAgentMatch = message.match(/User-Agent:\s*([^"]+)/);
if (userAgentMatch) {
details.userAgent = userAgentMatch[1];
}
// Extract IP address
const ipMatch = message.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/);
if (ipMatch) {
details.ip = ipMatch[0];
}
return details;
}
export async function handleAmplifyGetApiGatewayLogs(args) {
const { appId, branchName, httpMethod = 'all', endpoint, includeRequestResponse = false, includeLatency = false, statusCode, timeRange = '15m', customStartTime, customEndTime, maxEvents = 100 } = args;
if (!appId) {
throw new Error('appId is required');
}
try {
const clients = getCurrentClients();
// First, discover API Gateway resources to get accurate API IDs and log groups
console.error('🔍 Discovering API Gateway resources...');
const discoveryResult = await handleAmplifyDiscoverResources({
appId,
branchName,
resourceType: 'apigateway'
});
if (!discoveryResult.success) {
return {
success: false,
error: 'Failed to discover API Gateway resources',
appId,
branchName
};
}
// Look for API Gateway APIs in the discovered resources
const apiGatewayApis = [];
if (discoveryResult.apiGateways) {
for (const api of discoveryResult.apiGateways) {
// Include both REST and HTTP APIs
if (api.apiType === 'REST' || api.apiType === 'HTTP' || !api.apiType) {
apiGatewayApis.push({
apiId: api.apiId,
logicalId: api.logicalId,
name: api.name || api.logicalId,
apiType: api.apiType || 'REST',
// API Gateway log groups can have different patterns
logGroupName: `/aws/apigateway/${api.apiId}/prod`,
executionLogGroupName: `API-Gateway-Execution-Logs_${api.apiId}/prod`
});
}
}
}
// Also try to find API Gateway log groups directly by scanning for known patterns
try {
const patterns = [
'/aws/apigateway/',
'API-Gateway-Execution-Logs_'
];
for (const pattern of patterns) {
const describeCommand = new DescribeLogGroupsCommand({
logGroupNamePrefix: pattern,
limit: 50
});
const logGroupsResponse = await clients.cloudwatchLogs.send(describeCommand);
for (const logGroup of logGroupsResponse.logGroups || []) {
if (logGroup.logGroupName && logGroup.logGroupName.includes(appId)) {
let apiIdMatch;
let logGroupType = 'access';
if (logGroup.logGroupName.startsWith('/aws/apigateway/')) {
apiIdMatch = logGroup.logGroupName.match(/\/aws\/apigateway\/([^\/]+)/);
}
else if (logGroup.logGroupName.startsWith('API-Gateway-Execution-Logs_')) {
apiIdMatch = logGroup.logGroupName.match(/API-Gateway-Execution-Logs_([^\/]+)/);
logGroupType = 'execution';
}
if (apiIdMatch) {
const apiId = apiIdMatch[1];
const existing = apiGatewayApis.find(api => api.apiId === apiId);
if (existing) {
if (logGroupType === 'execution') {
existing.executionLogGroupName = logGroup.logGroupName;
}
}
else {
apiGatewayApis.push({
apiId,
logicalId: `ApiGateway-${apiId}`,
name: `API Gateway ${apiId}`,
apiType: 'REST',
logGroupName: logGroupType === 'access' ? logGroup.logGroupName : `/aws/apigateway/${apiId}/prod`,
executionLogGroupName: logGroupType === 'execution' ? logGroup.logGroupName : `API-Gateway-Execution-Logs_${apiId}/prod`
});
}
}
}
}
}
}
catch (error) {
console.warn('⚠️ Could not scan for API Gateway log groups:', error);
}
if (apiGatewayApis.length === 0) {
return {
success: false,
error: 'No API Gateway APIs found for this app/branch',
appId,
branchName
};
}
// Parse time range
let startTime, endTime;
if (customStartTime && customEndTime) {
startTime = new Date(customStartTime);
endTime = new Date(customEndTime);
}
else {
({ startTime, endTime } = parseTimeRange(timeRange));
}
// Build filter pattern
const filterPattern = buildApiGatewayFilter(httpMethod, endpoint, statusCode);
console.error(`📋 Fetching API Gateway logs for ${apiGatewayApis.length} API(s)...`);
// Fetch logs for each API Gateway API
const apiLogs = [];
for (const api of apiGatewayApis) {
console.error(` 📄 Processing ${api.name}...`);
// Try both access logs and execution logs
const logGroupsToCheck = [
{ name: 'access', logGroup: api.logGroupName },
{ name: 'execution', logGroup: api.executionLogGroupName }
];
const combinedLogs = [];
let totalLogsForApi = 0;
for (const logGroupInfo of logGroupsToCheck) {
try {
const filterCommand = new FilterLogEventsCommand({
logGroupName: logGroupInfo.logGroup,
startTime: startTime.getTime(),
endTime: endTime.getTime(),
filterPattern: filterPattern || undefined,
limit: Math.floor(maxEvents / 2) // Split between access and execution logs
});
const logResponse = await clients.cloudwatchLogs.send(filterCommand);
const logs = logResponse.events?.map((event) => {
const apiGatewayDetails = extractApiGatewayDetails(event.message || '');
const baseLog = {
timestamp: new Date(event.timestamp).toISOString(),
message: event.message,
logStream: event.logStreamName,
logType: logGroupInfo.name,
requestId: apiGatewayDetails.requestId || extractRequestId(event.message || ''),
...apiGatewayDetails
};
// Add latency data if requested
if (includeLatency && apiGatewayDetails.responseTime) {
baseLog.latency = {
responseTime: apiGatewayDetails.responseTime,
isSlowRequest: apiGatewayDetails.responseTime > 2000 // Consider >2s as slow
};
}
// Add request/response data if requested (simplified for now)
if (includeRequestResponse) {
const hasRequest = event.message?.includes('request') || event.message?.includes('body');
const hasResponse = event.message?.includes('response') || event.message?.includes('status');
baseLog.requestResponse = {
hasRequestData: hasRequest,
hasResponseData: hasResponse,
method: apiGatewayDetails.httpMethod,
endpoint: apiGatewayDetails.endpoint,
statusCode: apiGatewayDetails.statusCode
};
}
return baseLog;
}) || [];
combinedLogs.push(...logs);
totalLogsForApi += logs.length;
}
catch (error) {
console.warn(`⚠️ Could not fetch ${logGroupInfo.name} logs for ${api.name}:`, error);
}
}
// Sort combined logs by timestamp
combinedLogs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
// Calculate performance statistics if requested
let performanceStats = {};
if (includeLatency) {
const timings = combinedLogs.filter(log => log.responseTime).map(log => log.responseTime);
if (timings.length > 0) {
performanceStats = {
averageResponseTime: timings.reduce((sum, time) => sum + time, 0) / timings.length,
maxResponseTime: Math.max(...timings),
minResponseTime: Math.min(...timings),
slowRequests: timings.filter(time => time > 2000).length,
totalRequestsWithTiming: timings.length
};
}
}
// Calculate status code distribution
const statusCodeStats = {};
combinedLogs.forEach(log => {
if (log.statusCode) {
statusCodeStats[log.statusCode] = (statusCodeStats[log.statusCode] || 0) + 1;
}
});
apiLogs.push({
apiId: api.apiId,
logicalId: api.logicalId,
name: api.name,
apiType: api.apiType,
logGroupName: api.logGroupName,
executionLogGroupName: api.executionLogGroupName,
logsFound: totalLogsForApi,
logs: combinedLogs.slice(0, maxEvents),
performanceStats: includeLatency ? performanceStats : undefined,
statusCodeStats: Object.keys(statusCodeStats).length > 0 ? statusCodeStats : undefined,
error: totalLogsForApi === 0 ? 'No logs found in either access or execution log groups' : undefined
});
}
// Calculate summary statistics
const totalLogs = apiLogs.reduce((sum, api) => sum + api.logsFound, 0);
const apisWithLogs = apiLogs.filter(api => api.logsFound > 0).length;
const apisWithErrors = apiLogs.filter(api => api.error).length;
// Extract HTTP method statistics
const methodStats = {};
apiLogs.forEach(api => {
api.logs.forEach((log) => {
if (log.httpMethod) {
methodStats[log.httpMethod] = (methodStats[log.httpMethod] || 0) + 1;
}
});
});
// Extract endpoint statistics
const endpointStats = {};
apiLogs.forEach(api => {
api.logs.forEach((log) => {
if (log.endpoint) {
endpointStats[log.endpoint] = (endpointStats[log.endpoint] || 0) + 1;
}
});
});
return {
success: true,
appId,
branchName,
timeRange: customStartTime ? 'custom' : timeRange,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
httpMethod,
endpoint: endpoint || null,
statusCode: statusCode || null,
includeLatency,
includeRequestResponse,
filterPattern: filterPattern || null,
totalApis: apiGatewayApis.length,
apisWithLogs,
apisWithErrors,
totalLogs,
methodStats,
endpointStats,
apiLogs,
summary: {
queryInfo: {
timeRange: customStartTime ? 'custom' : timeRange,
httpMethod,
endpoint: endpoint || null,
statusCode: statusCode || null,
maxEvents
},
results: {
totalLogs,
apisWithLogs,
apisWithErrors,
methodBreakdown: methodStats,
endpointBreakdown: endpointStats
},
recommendations: [
totalLogs === 0 ? "No logs found. Check if API Gateway is receiving traffic or if logging is enabled." : null,
includeLatency ? "Performance data included. Check for slow requests (>2s)." : "Consider enabling includeLatency for request performance insights.",
includeRequestResponse ? "Request/response data included." : "Consider enabling includeRequestResponse for detailed request analysis.",
Object.keys(endpointStats).length > 5 ? "Many endpoints detected. Consider filtering by specific endpoint for focused analysis." : null
].filter(Boolean)
}
};
}
catch (error) {
console.error('❌ Error in handleAmplifyGetApiGatewayLogs:', error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
appId,
branchName
};
}
}
// Helper function to build Cognito-specific filter patterns
function buildCognitoFilter(eventType, userId, includeFailedAttempts) {
let filters = [];
// Add event type filter
if (eventType !== 'all') {
switch (eventType) {
case 'signin':
filters.push('["signin", "sign_in", "authentication", "InitiateAuth", "RespondToAuthChallenge"]');
break;
case 'signup':
filters.push('["signup", "sign_up", "registration", "SignUp", "ConfirmSignUp"]');
break;
case 'forgot_password':
filters.push('["forgot_password", "reset_password", "ForgotPassword", "ConfirmForgotPassword"]');
break;
case 'token_refresh':
filters.push('["token_refresh", "RefreshToken", "refresh_token"]');
break;
}
}
// Add user ID filter if specified
if (userId) {
filters.push(`[message~"${userId}"]`);
}
// Include failed attempts filter
if (includeFailedAttempts === false) {
filters.push('[level != "ERROR" && message != ~"[Ff]ailed" && message != ~"[Ee]rror"]');
}
return filters.length > 0 ? filters.join(' ') : '';
}
// Helper function to extract Cognito event details from log messages
function extractCognitoEventDetails(message) {
const details = {};
// Extract event type
if (message.includes('signin') || message.includes('sign_in') || message.includes('InitiateAuth')) {
details.eventType = 'signin';
}
else if (message.includes('signup') || message.includes('sign_up') || message.includes('SignUp')) {
details.eventType = 'signup';
}
else if (message.includes('forgot_password') || message.includes('ForgotPassword')) {
details.eventType = 'forgot_password';
}
else if (message.includes('token_refresh') || message.includes('RefreshToken')) {
details.eventType = 'token_refresh';
}
// Extract user ID
const userIdMatch = message.match(/user[_-]?id["\s]*:?\s*["']?([a-f0-9-]+)["']?/i);
if (userIdMatch) {
details.userId = userIdMatch[1];
}
// Extract username
const usernameMatch = message.match(/username["\s]*:?\s*["']?([^"'\s,}]+)["']?/i);
if (usernameMatch) {
details.username = usernameMatch[1];
}
// Extract client ID
const clientIdMatch = message.match(/client[_-]?id["\s]*:?\s*["']?([a-z0-9]+)["']?/i);
if (clientIdMatch) {
details.clientId = clientIdMatch[1];
}
// Extract IP address
const ipMatch = message.match(/ip[_-]?address["\s]*:?\s*["']?(\d+\.\d+\.\d+\.\d+)["']?/i);
if (ipMatch) {
details.ipAddress = ipMatch[1];
}
// Extract user agent
const userAgentMatch = message.match(/user[_-]?agent["\s]*:?\s*["']?([^"'\n\r]+)["']?/i);
if (userAgentMatch) {
details.userAgent = userAgentMatch[1];
}
// Determine success/failure
const isError = message.toLowerCase().includes('error') ||
message.toLowerCase().includes('failed') ||
message.toLowerCase().includes('denied');
const isSuccess = message.toLowerCase().includes('success') ||
message.toLowerCase().includes('confirmed') ||
message.toLowerCase().includes('authenticated');
if (isError) {
details.success = false;
// Extract error code
const errorCodeMatch = message.match(/error[_-]?code["\s]*:?\s*["']?([A-Z_]+)["']?/i);
if (errorCodeMatch) {
details.errorCode = errorCodeMatch[1];
}
}
else if (isSuccess) {
details.success = true;
}
return details;
}
export async function handleAmplifyGetCognitoLogs(args) {
const { appId, branchName, eventType = 'all', userId, includeFailedAttempts = true, timeRange = '15m', customStartTime, customEndTime, logLevel = 'all', maxEvents = 100 } = args;
if (!appId) {
throw new Error('appId is required');
}
try {
const clients = getCurrentClients();
// First, discover Cognito resources to get accurate User Pool IDs and log groups
console.error('🔍 Discovering Cognito resources...');
const discoveryResult = await handleAmplifyDiscoverResources({
appId,
branchName,
resourceType: 'cognito'
});
if (!discoveryResult.success) {
return {
success: false,
error: 'Failed to discover Cognito resources',
appId,
branchName
};
}
// Look for Cognito User Pools in the discovered resources
const cognitoUserPools = [];
if (discoveryResult.cognitoUserPools) {
for (const userPool of discoveryResult.cognitoUserPools) {
cognitoUserPools.push({
userPoolId: userPool.userPoolId,
logicalId: userPool.logicalId,
logGroupName: `/aws/cognito/userpools/${userPool.userPoolId}`,
name: userPool.name || userPool.logicalId
});
}
}
// Also try to find Cognito User Pools directly by looking for known patterns
try {
const describeCommand = new DescribeLogGroupsCommand({
logGroupNamePrefix: '/aws/cognito/userpools/',
limit: 50
});
const logGroupsResponse = await clients.cloudwatchLogs.send(describeCommand);
for (const logGroup of logGroupsResponse.logGroups || []) {
if (logGroup.logGroupName && logGroup.logGroupName.includes(appId)) {
const userPoolIdMatch = logGroup.logGroupName.match(/\/aws\/cognito\/userpools\/([^\/]+)/);
if (userPoolIdMatch) {
const userPoolId = userPoolIdMatch[1];
const existing = cognitoUserPools.find(pool => pool.userPoolId === userPoolId);
if (!existing) {
cognitoUserPools.push({
userPoolId,
logicalId: `Cognito-${userPoolId}`,
logGroupName: logGroup.logGroupName,
name: `Cognito User Pool ${userPoolId}`
});
}
}
}
}
}
catch (error) {
console.warn('⚠️ Could not scan for Cognito log groups:', error);
}
if (cognitoUserPools.length === 0) {
return {
success: false,
error: 'No Cognito User Pools found for this app/branch',
appId,
branchName
};
}
// Parse time range
let startTime, endTime;
if (customStartTime && customEndTime) {
startTime = new Date(customStartTime);
endTime = new Date(customEndTime);
}
else {
({ startTime, endTime } = parseTimeRange(timeRange));
}
// Build filter pattern
let combinedFilter = '';
const cognitoFilter = buildCognitoFilter(eventType, userId, includeFailedAttempts);
const logLevelFilter = buildLogLevelFilter(logLevel);
if (logLevelFilter) {
combinedFilter = logLevelFilter;
}
if (cognitoFilter) {
combinedFilter = combinedFilter ? `${combinedFilter} ${cognitoFilter}` : cognitoFilter;
}
console.error(`📋 Fetching Cognito logs for ${cognitoUserPools.length} User Pool(s)...`);
// Fetch logs for each Cognito User Pool
const userPoolLogs = [];
for (const userPool of cognitoUserPools) {
console.error(` 📄 Processing ${userPool.name}...`);
try {
const filterCommand = new FilterLogEventsCommand({
logGroupName: userPool.logGroupName,
startTime: startTime.getTime(),
endTime: endTime.getTime(),
filterPattern: combinedFilter || undefined,
limit: maxEvents
});
const logResponse = await clients.cloudwatchLogs.send(filterCommand);
const logs = logResponse.events?.map((event) => {
const cognitoDetails = extractCognitoEventDetails(event.message || '');
return {
timestamp: new Date(event.timestamp).toISOString(),
message: event.message,
logStream: event.logStreamName,
requestId: extractRequestId(event.message || ''),
...cognitoDetails
};
}) || [];
// Calculate event type statistics
const eventStats = {};
logs.forEach((log) => {
if (log.eventType) {
eventStats[log.eventType] = (eventStats[log.eventType] || 0) + 1;
}
});
// Calculate success/failure statistics
const successCount = logs.filter((log) => log.success === true).length;
const failureCount = logs.filter((log) => log.success === false).length;
const unknownCount = logs.filter((log) => log.success === undefined).length;
userPoolLogs.push({
userPoolId: userPool.userPoolId,
logicalId: userPool.logicalId,
name: userPool.name,
logGroupName: userPool.logGroupName,
logsFound: logs.length,
logs: logs.slice(0, maxEvents),
eventStats,
successFailureStats: {
successful: successCount,
failed: failureCount,
unknown: unknownCount
}
});
}
catch (error) {
console.error(`❌ Error fetching logs for ${userPool.name}:`, error);
userPoolLogs.push({
userPoolId: userPool.userPoolId,
logicalId: userPool.logicalId,
name: userPool.name,
logGroupName: userPool.logGroupName,
logsFound: 0,
logs: [],
error: error instanceof Error ? error.message : String(error)
});
}
}
// Calculate summary statistics
const totalLogs = userPoolLogs.reduce((sum, pool) => sum + pool.logsFound, 0);
const userPoolsWithLogs = userPoolLogs.filter(pool => pool.logsFound > 0).length;
const userPoolsWithErrors = userPoolLogs.filter(pool => pool.error).length;
// Calculate overall event type statistics
const overallEventStats = {};
const overallSuccessFailureStats = {
successful: 0,
failed: 0,
unknown: 0
};
userPoolLogs.forEach(pool => {
if (pool.eventStats) {
Object.entries(pool.eventStats).forEach(([eventType, count]) => {
overallEventStats[eventType] = (overallEventStats[eventType] || 0) + count;
});
}
if (pool.successFailureStats) {
overallSuccessFailureStats.successful += pool.successFailureStats.successful;
overallSuccessFailureStats.failed += pool.successFailureStats.failed;
overallSuccessFailureStats.unknown += pool.successFailureStats.unknown;
}
});
// Extract unique users
const uniqueUsers = new Set();
userPoolLogs.forEach(pool => {
pool.logs.forEach((log) => {
if (log.userId)
uniqueUsers.add(log.userId);
if (log.username)
uniqueUsers.add(log.username);
});
});
return {
success: true,
appId,
branchName,
timeRange: customStartTime ? 'custom' : timeRange,
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
eventType,
userId: userId || null,
includeFailedAttempts,
logLevel,
filterPattern: combinedFilter || null,
totalUserPools: cognitoUserPools.length,
userPoolsWithLogs,
userPoolsWithErrors,
totalLogs,
uniqueUsers: uniqueUsers.size,
overallEventStats,
overallSuccessFailureStats,
userPoolLogs,
summary: {
queryInfo: {
timeRange: customStartTime ? 'custom' : timeRange,
eventType,
userId: userId || null,
includeFailedAttempts,
logLevel,
maxEvents
},
results: {
totalLogs,
userPoolsWithLogs,
userPoolsWithErrors,
uniqueUsers: uniqueUsers.size,
eventBreakdown: overallEventStats,
successRate: overallSuccessFailureStats.successful + overallSuccessFailureStats.failed > 0 ?
(overallSuccessFailureStats.successful / (overallSuccessFailureStats.successful + overallSuccessFailureStats.failed) * 100).toFixed(1) + '%' : 'N/A'
},
securityInsights: {
hasFailedAttempts: overallSuccessFailureStats.failed > 0,
failedAttemptRate: overallSuccessFailureStats.failed + overallSuccessFailureStats.successful > 0 ?
(overallSuccessFailureStats.failed / (overallSuccessFailureStats.failed + overallSuccessFailureStats.successful) * 100).toFixed(1) + '%' : 'N/A',
uniqueUsersActive: uniqueUsers.size,
mostCommonEvent: Object.entries(overallEventStats).sort((a, b) => b[1] - a[1])[0]?.[0] || 'none'
},
recommendations: [
totalLogs === 0 ? "No Cognito logs found. Check if authentication is active." : null,
overallSuccessFailureStats.failed > 0 ? "Failed authentication attempts detected. Monitor for potential security issues." : null,
uniqueUsers.size > 0 ? `${uniqueUsers.size}