localstack-logs-analysis
Analyze LocalStack logs to diagnose issues and understand AWS service interactions. Filter by service, operation, or error type to identify problems in local development environments.
Instructions
LocalStack log analyzer that helps developers quickly diagnose issues and understand their LocalStack interactions
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| analysisType | No | The analysis to perform: 'summary' (default), 'errors', 'requests', or 'logs' for raw output. | summary |
| lines | No | Number of recent log lines to fetch and analyze. | |
| service | No | Filter by AWS service (e.g., 's3', 'lambda'). Used with 'errors' and 'requests' modes. | |
| operation | No | Filter by a specific API operation (e.g., 'CreateBucket'). Requires 'service'. Used with 'requests' mode. | |
| filter | No | Raw keyword filter. Only used with 'logs' mode. |
Implementation Reference
- Main tool handler function that runs preflight checks, retrieves LocalStack logs using LocalStackLogRetriever, and dispatches to mode-specific analysis functions based on the 'analysisType' parameter.export default async function localstackLogsAnalysis({ analysisType, lines, service, operation, filter, }: InferSchema<typeof schema>) { const preflightError = await runPreflights([requireLocalStackCli()]); if (preflightError) return preflightError; const retriever = new LocalStackLogRetriever(); const retrievalFilter = analysisType === "logs" ? filter : undefined; const logResult = await retriever.retrieveLogs(lines, retrievalFilter); if (!logResult.success) { return ResponseBuilder.error("Log Retrieval Failed", logResult.errorMessage || "Unknown error"); } switch (analysisType) { case "summary": return await handleSummaryAnalysis(logResult.logs, logResult.totalLines); case "errors": return await handleErrorAnalysis(retriever, logResult.logs, service); case "requests": return await handleRequestAnalysis(retriever, logResult.logs, service, operation); case "logs": return await handleRawLogsAnalysis( logResult.logs, logResult.totalLines, logResult.filteredLines, filter ); default: return ResponseBuilder.error( "Unknown analysis type", `ā Unknown analysis type: ${analysisType}` ); } }
- Zod schema defining the input parameters for the tool, including analysisType, lines, service, operation, and filter.export const schema = { analysisType: z .enum(["summary", "errors", "requests", "logs"]) .default("summary") .describe( "The analysis to perform: 'summary' (default), 'errors', 'requests', or 'logs' for raw output." ), lines: z .number() .int() .positive() .default(2000) .describe("Number of recent log lines to fetch and analyze."), service: z .string() .optional() .describe( "Filter by AWS service (e.g., 's3', 'lambda'). Used with 'errors' and 'requests' modes." ), operation: z .string() .optional() .describe( "Filter by a specific API operation (e.g., 'CreateBucket'). Requires 'service'. Used with 'requests' mode." ), filter: z.string().optional().describe("Raw keyword filter. Only used with 'logs' mode."), };
- src/tools/localstack-logs-analysis.ts:36-46 (registration)Tool metadata object that registers the tool with its name, description, and annotations indicating it's read-only, non-destructive, and idempotent.export const metadata: ToolMetadata = { name: "localstack-logs-analysis", description: "LocalStack log analyzer that helps developers quickly diagnose issues and understand their LocalStack interactions", annotations: { title: "LocalStack Logs Analysis", readOnlyHint: true, destructiveHint: false, idempotentHint: true, }, };
- src/lib/logs/log-retriever.ts:36-98 (helper)Core helper method in LocalStackLogRetriever class that retrieves recent LocalStack logs using the CLI command and applies optional filtering.async retrieveLogs(lines: number = 10000, filter?: string): Promise<LogRetrievalResult> { try { const cmd = await runCommand("localstack", ["logs", "--tail", String(lines)], { timeout: 30000, }); if (!cmd.stdout && cmd.stderr) { return { success: false, logs: [], totalLines: 0, errorMessage: `Failed to retrieve logs: ${cmd.stderr}`, }; } const rawLines = (cmd.stdout || "").split("\n").filter((line) => line.trim()); let filteredLines = rawLines; if (filter) { filteredLines = rawLines.filter((line) => line.toLowerCase().includes(filter.toLowerCase()) ); } const logs = filteredLines.map((line) => this.parseLogLine(line)); return { success: true, logs, totalLines: rawLines.length, filteredLines: filter ? filteredLines.length : undefined, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes("timeout")) { return { success: false, logs: [], totalLines: 0, errorMessage: "Log retrieval timed out. LocalStack may be generating large amounts of logs. Try reducing the number of lines or check if LocalStack is experiencing issues.", }; } if (errorMessage.includes("Command failed") || errorMessage.includes("not found")) { return { success: false, logs: [], totalLines: 0, errorMessage: "Unable to execute 'localstack logs' command. Please ensure LocalStack CLI is installed and LocalStack is running.", }; } return { success: false, logs: [], totalLines: 0, errorMessage: `Failed to retrieve logs: ${errorMessage}`, }; } }
- Helper function for 'summary' analysis mode that generates a high-level dashboard of log stats, errors, warnings, API calls, and service activity.async function handleSummaryAnalysis(logs: LogEntry[], totalLines: number) { const errors = logs.filter((log) => log.isError); const warnings = logs.filter((log) => log.isWarning); const apiStats = new LocalStackLogRetriever().analyzeApiCalls(logs); let result = `# š LocalStack Summary\n\n`; result += `**Lines Analyzed:** ${totalLines}\n`; result += `**API Calls:** ${apiStats.totalCalls}\n`; result += `**Errors:** ${errors.length} | **Warnings:** ${warnings.length}\n\n`; // Quick health check if (apiStats.failedCalls > 0) { result += `## ā Recent Failures (${apiStats.failedCalls})\n\n`; const recentFailures = apiStats.failedCallDetails.slice(-5).reverse(); for (const call of recentFailures) { const service = call.service || "unknown"; const operation = call.operation || "unknown"; const status = call.statusCode || "N/A"; result += `- **${service}.${operation}** ā ${status} ${call.message}\n`; } result += `\nš” Use \`errors\` mode for detailed analysis\n\n`; } // Service breakdown if there are API calls if (apiStats.callsByService.size > 0) { result += `## š§ Service Activity\n\n`; for (const [svc, count] of Array.from(apiStats.callsByService.entries()).sort( (a, b) => b[1] - a[1] )) { const serviceErrors = apiStats.failedCallDetails.filter( (call) => call.service === svc ).length; const status = serviceErrors === 0 ? "ā " : "ā"; result += `- **${svc}**: ${count} calls ${status}`; if (serviceErrors > 0) result += ` (${serviceErrors} failed)`; result += `\n`; } result += `\n`; } if (errors.length === 0 && apiStats.failedCalls === 0) { result += `## ā All Clear\n\nNo errors detected in recent LocalStack activity.\n\n`; } result += `**Drill down:** \`errors\` | \`requests\` | \`logs\`\n`; return ResponseBuilder.markdown(result); }