Skip to main content
Glama
log-parser.ts5.99 kB
/** * Build Log Parser * Unified parsing for Gradle and xcodebuild outputs */ import { Platform } from '../../models/constants.js'; import { BuildError, BuildErrorSummary, ErrorCategory, categorizeError, generateSuggestions, } from '../../models/build-result.js'; export interface ParsedLog { errors: BuildError[]; warnings: BuildError[]; summary: BuildErrorSummary; } /** * Parse build log based on platform */ export function parseBuildLog(output: string, platform: Platform): ParsedLog { const parser = platform === 'android' ? parseGradleLog : parseXcodeLog; return parser(output); } /** * Parse Gradle build log */ export function parseGradleLog(output: string): ParsedLog { const errors: BuildError[] = []; const warnings: BuildError[] = []; const lines = output.split('\n'); // Kotlin/Java error pattern: e: file:///path/file.kt:line:col message const kotlinErrorPattern = /^([ew]):\s*(?:file:\/\/)?([^:]+):(\d+):(\d+)\s*(.+)$/; // Java compiler error pattern: path.java:line: error: message const javaErrorPattern = /^(.+\.java):(\d+):\s*(error|warning):\s*(.+)$/; for (const line of lines) { const trimmed = line.trim(); // Match Kotlin compiler errors const kotlinMatch = trimmed.match(kotlinErrorPattern); if (kotlinMatch) { const [, severity, filePath, lineStr, colStr, message] = kotlinMatch; const error: BuildError = { message: message.trim(), file: filePath, line: parseInt(lineStr, 10), column: parseInt(colStr, 10), severity: severity === 'w' ? 'warning' : 'error', }; if (severity === 'w') { warnings.push(error); } else { errors.push(error); } continue; } // Match Java compiler errors const javaMatch = trimmed.match(javaErrorPattern); if (javaMatch) { const [, filePath, lineStr, severity, message] = javaMatch; const error: BuildError = { message: message.trim(), file: filePath, line: parseInt(lineStr, 10), severity: severity === 'warning' ? 'warning' : 'error', }; if (severity === 'warning') { warnings.push(error); } else { errors.push(error); } continue; } // Match generic Gradle errors if (trimmed.startsWith('FAILURE:') || trimmed.includes('Execution failed for task')) { errors.push({ message: trimmed, severity: 'error', }); } } return { errors, warnings, summary: createSummary(errors, warnings, lines), }; } /** * Parse xcodebuild log */ export function parseXcodeLog(output: string): ParsedLog { const errors: BuildError[] = []; const warnings: BuildError[] = []; const lines = output.split('\n'); // Swift/Clang error pattern: /path/file.swift:line:col: error: message const swiftErrorPattern = /^(.+?):(\d+):(\d+):\s*(error|warning):\s*(.+)$/; // Linker error pattern const linkerErrorPattern = /^(ld|clang):\s*(error|warning):\s*(.+)$/; for (const line of lines) { const trimmed = line.trim(); // Match Swift/Clang compiler errors const swiftMatch = trimmed.match(swiftErrorPattern); if (swiftMatch) { const [, filePath, lineStr, colStr, severity, message] = swiftMatch; const error: BuildError = { message: message.trim(), file: filePath, line: parseInt(lineStr, 10), column: parseInt(colStr, 10), severity: severity as 'error' | 'warning', }; if (severity === 'warning') { warnings.push(error); } else { errors.push(error); } continue; } // Match linker errors const linkerMatch = trimmed.match(linkerErrorPattern); if (linkerMatch) { const [, , severity, message] = linkerMatch; const error: BuildError = { message: message.trim(), severity: severity as 'error' | 'warning', }; if (severity === 'warning') { warnings.push(error); } else { errors.push(error); } continue; } // Match BUILD FAILED if (trimmed.includes('** BUILD FAILED **')) { // Don't add this as a separate error, it's just a marker } } return { errors, warnings, summary: createSummary(errors, warnings, lines), }; } /** * Create error summary from parsed errors */ function createSummary( errors: BuildError[], warnings: BuildError[], logLines: string[] ): BuildErrorSummary { // Categorize errors const categoryMap = new Map<string, { errors: BuildError[]; example: BuildError }>(); for (const error of errors) { const category = categorizeError(error); const existing = categoryMap.get(category); if (existing) { existing.errors.push(error); } else { categoryMap.set(category, { errors: [error], example: error }); } } const errorCategories: ErrorCategory[] = Array.from(categoryMap.entries()).map( ([category, data]) => ({ category, count: data.errors.length, example: data.example, }) ); // Sort by count descending errorCategories.sort((a, b) => b.count - a.count); return { errorCount: errors.length, warningCount: warnings.length, topErrors: errors.slice(0, 5), errorCategories, suggestions: generateSuggestions(errorCategories), logTail: logLines.slice(-50).join('\n'), }; } /** * Extract context around an error line */ export function extractErrorContext( fileContent: string, line: number, contextLines = 3 ): string { const lines = fileContent.split('\n'); const startLine = Math.max(0, line - contextLines - 1); const endLine = Math.min(lines.length, line + contextLines); return lines .slice(startLine, endLine) .map((l, i) => { const lineNum = startLine + i + 1; const marker = lineNum === line ? '>' : ' '; return `${marker} ${lineNum.toString().padStart(4)}: ${l}`; }) .join('\n'); }

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/abd3lraouf/specter-mcp'

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