Skip to main content
Glama
log-analyzer.ts14.2 kB
/** * Log analysis module * * Rather than simply showing logs * finds error patterns and analyzes root causes * * @author zerry */ import * as k8s from '@kubernetes/client-node'; import type { LogAnalysis, LogEntry, ErrorPattern, RepeatedError } from '../types.js'; /** * Analyze pod logs * * Finds error patterns in logs and suggests solutions * Extracts key information from thousands of log lines */ export async function analyzeLogs( logApi: k8s.Log, namespace: string, podName: string, containerName?: string, tailLines: number = 500 ): Promise<LogAnalysis> { try { const stream = new (require('stream').Writable)(); let logData = ''; stream._write = (chunk: any, encoding: string, next: Function) => { logData += chunk.toString(); next(); }; // Fetch logs await logApi.log(namespace, podName, containerName || '', stream, { tailLines, }); const lines = logData.split('\n').filter(line => line.trim()); // 1. Extract error/warning lines const errorLines = extractErrorLines(lines); const warningLines = extractWarningLines(lines); // 2. Detect error patterns const patterns = detectErrorPatterns(lines); // 3. Find repeated errors const repeatedErrors = findRepeatedErrors(errorLines); // 4. Generate summary const summary = generateLogSummary(lines.length, errorLines, patterns); // 5. Generate recommendations const recommendations = generateRecommendations(patterns, repeatedErrors); return { totalLines: lines.length, errorLines, warningLines, patterns, repeatedErrors, summary, recommendations, }; } catch (error: any) { throw new Error(`Log analysis failed: ${error.message}`); } } /** * Extract error lines * * Finds errors using keywords like ERROR, Exception, Fatal */ function extractErrorLines(lines: string[]): LogEntry[] { const errorKeywords = [ 'error', 'exception', 'fatal', 'panic', 'failed', 'failure', 'err:', 'traceback', 'stacktrace', ]; const errorLines: LogEntry[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lowerLine = line.toLowerCase(); for (const keyword of errorKeywords) { if (lowerLine.includes(keyword)) { errorLines.push({ lineNumber: i + 1, content: line, timestamp: extractTimestamp(line), level: 'ERROR', }); break; } } } return errorLines; } /** * Extract warning lines */ function extractWarningLines(lines: string[]): LogEntry[] { const warnKeywords = ['warn', 'warning', 'deprecated']; const warningLines: LogEntry[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lowerLine = line.toLowerCase(); for (const keyword of warnKeywords) { if (lowerLine.includes(keyword)) { warningLines.push({ lineNumber: i + 1, content: line, timestamp: extractTimestamp(line), level: 'WARN', }); break; } } } return warningLines; } /** * Detect error patterns * * Matches patterns of commonly occurring errors with solutions * This is the core functionality! */ function detectErrorPatterns(lines: string[]): ErrorPattern[] { const patterns: ErrorPattern[] = []; // Pattern definitions: errors frequently seen in production const knownPatterns = [ { name: 'Connection Refused', regex: /connection refused|ECONNREFUSED/i, description: 'Cannot connect to target service', causes: [ 'Target service not started yet', 'Wrong service port', 'Blocked by network policy', ], solutions: [ 'Check if service is running: kubectl get pods', 'Verify service port: kubectl get svc', 'Check network policy: kubectl get networkpolicy', ], severity: 'high' as const, }, { name: 'Database Connection Error', regex: /could not connect to.*database|ETIMEDOUT.*:5432|:3306|:27017/i, description: 'Database connection failed', causes: [ 'DB service not ready', 'Invalid connection string', 'DB authentication failed', ], solutions: [ 'Check DB Pod status', 'Verify environment variables (ConfigMap/Secret)', 'Check DB service endpoints: kubectl get endpoints', ], severity: 'critical' as const, }, { name: 'Out of Memory', regex: /out of memory|OOMKilled|cannot allocate memory/i, description: 'Insufficient memory', causes: [ 'Memory limit set too low', 'Memory leak', 'Higher memory usage than expected', ], solutions: [ 'Increase memory limit: resources.limits.memory', 'Profile application memory usage', 'Increase pod count with HPA', ], severity: 'critical' as const, }, { name: 'File Not Found', regex: /no such file|ENOENT|FileNotFoundError/i, description: 'File or directory not found', causes: [ 'ConfigMap/Secret not mounted', 'Invalid file path', 'Volume mount failed', ], solutions: [ 'Check volumeMounts configuration', 'Verify ConfigMap/Secret exists', 'Verify file path is correct', ], severity: 'high' as const, }, { name: 'Permission Denied', regex: /permission denied|EACCES|access denied/i, description: 'Permission denied', causes: [ 'SecurityContext runAsUser setting', 'Volume fsGroup not set', 'File permission issues', ], solutions: [ 'Configure securityContext:\nfsGroup: 1000\nrunAsUser: 1000', 'Check volume permissions', 'Set permissions in Dockerfile', ], severity: 'high' as const, }, { name: 'DNS Resolution Failed', regex: /dns.*failed|getaddrinfo.*ENOTFOUND|name.*not known/i, description: 'DNS lookup failed', causes: [ 'CoreDNS issues', 'Invalid service name', 'ndots configuration problem', ], solutions: [ 'Check CoreDNS Pod: kubectl get pods -n kube-system', 'Service name format: <service>.<namespace>.svc.cluster.local', 'Verify dnsPolicy setting', ], severity: 'high' as const, }, { name: 'Port Already in Use', regex: /address already in use|EADDRINUSE/i, description: 'Port already in use', causes: [ 'Multiple processes using same port', 'Previous process not terminated properly', ], solutions: [ 'Change port number', 'Implement graceful shutdown', 'Configure preStop hook', ], severity: 'medium' as const, }, { name: 'Timeout', regex: /timeout|timed out|ETIMEDOUT/i, description: 'Timeout occurred', causes: [ 'Response time too long', 'Network latency', 'Target service overloaded', ], solutions: [ 'Increase timeout value', 'Optimize service performance', 'Adjust readinessProbe timeout', ], severity: 'medium' as const, }, { name: 'Null Pointer / Undefined', regex: /null pointer|undefined is not|cannot read property.*undefined|NullPointerException/i, description: 'Null/Undefined reference', causes: [ 'Environment variable not set', 'Using uninitialized variable', ], solutions: [ 'Verify ConfigMap/Secret', 'Set default value for environment variables', 'Fix code', ], severity: 'medium' as const, }, { name: 'SSL/TLS Error', regex: /ssl.*error|certificate.*invalid|CERT_/i, description: 'SSL/TLS certificate error', causes: [ 'Expired certificate', 'Self-signed certificate', 'CA bundle missing', ], solutions: [ 'Renew certificate', 'Verify tls.crt, tls.key Secret', 'NODE_TLS_REJECT_UNAUTHORIZED=0 (development only)', ], severity: 'high' as const, }, ]; // Match each pattern for (const pattern of knownPatterns) { const matchedLines: number[] = []; for (let i = 0; i < lines.length; i++) { if (pattern.regex.test(lines[i])) { matchedLines.push(i + 1); } } if (matchedLines.length > 0) { patterns.push({ name: pattern.name, matchedLines, description: pattern.description, possibleCauses: pattern.causes, solutions: pattern.solutions, severity: pattern.severity, }); } } return patterns; } /** * Find repeated errors * * If the same error keeps occurring, there's a pattern */ function findRepeatedErrors(errorLines: LogEntry[]): RepeatedError[] { const errorMap = new Map<string, number[]>(); for (const entry of errorLines) { // Normalize error message (remove timestamps, numbers, etc.) const normalized = normalizeErrorMessage(entry.content); if (!errorMap.has(normalized)) { errorMap.set(normalized, []); } errorMap.get(normalized)!.push(entry.lineNumber); } const repeatedErrors: RepeatedError[] = []; for (const [message, lineNumbers] of errorMap.entries()) { if (lineNumbers.length >= 3) { // If repeated 3+ times repeatedErrors.push({ message, count: lineNumbers.length, firstLine: lineNumbers[0], lastLine: lineNumbers[lineNumbers.length - 1], isPattern: true, }); } } // Sort by occurrence count return repeatedErrors.sort((a, b) => b.count - a.count); } /** * Normalize error message * * Remove timestamps, IPs, ports, etc. to group similar errors */ function normalizeErrorMessage(message: string): string { return message .replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}.*?Z/g, '<timestamp>') // ISO timestamp .replace(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, '<ip>') // IP address .replace(/:\d{2,5}/g, ':<port>') // Port number .replace(/\d+/g, '<num>') // Other numbers .replace(/0x[0-9a-f]+/gi, '<hex>') // Hex values .toLowerCase() .trim(); } /** * 타임스탬프 추출 * * 로그에서 타임스탬프를 파싱 시도 */ function extractTimestamp(line: string): string | undefined { // ISO 8601 format const isoMatch = line.match(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/); if (isoMatch) return isoMatch[0]; // RFC 3339 const rfcMatch = line.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}/); if (rfcMatch) return rfcMatch[0]; return undefined; } /** * Generate log summary */ function generateLogSummary( totalLines: number, errorLines: LogEntry[], patterns: ErrorPattern[] ): string { let summary = `Analyzed ${totalLines} lines of logs.\n`; summary += `Errors: ${errorLines.length}\n`; if (patterns.length > 0) { summary += `\nDetected ${patterns.length} error patterns:\n`; for (const pattern of patterns) { summary += ` - ${pattern.name} (${pattern.matchedLines.length} occurrences)\n`; } } else { summary += '\n✅ No known error patterns detected.'; } return summary; } /** * Generate recommendations */ function generateRecommendations( patterns: ErrorPattern[], repeatedErrors: RepeatedError[] ): string[] { const recommendations: string[] = []; // Prioritize critical patterns const criticalPatterns = patterns.filter(p => p.severity === 'critical'); if (criticalPatterns.length > 0) { recommendations.push( `🔴 Resolve ${criticalPatterns.length} Critical issue(s) as top priority` ); } // If there are many repeated errors if (repeatedErrors.length > 0) { const topError = repeatedErrors[0]; recommendations.push( `⚠️ "${topError.message}" error is repeating ${topError.count} times. Find the root cause.` ); } // Pattern-specific recommendations for (const pattern of patterns.slice(0, 3)) { // Top 3 only recommendations.push(`💡 ${pattern.name}: ${pattern.solutions[0]}`); } if (recommendations.length === 0) { recommendations.push('✅ No special issues found in current logs.'); } return recommendations; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ongjin/k8s-doctor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server