package research.loghunter.service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import research.loghunter.entity.Pattern; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @Service @RequiredArgsConstructor @Slf4j public class LogParserService { /** * 로그 내용에서 패턴 매칭 */ public List parseAndMatch(String content, List patterns, String filePath) { List results = new ArrayList<>(); String[] lines = content.split("\n"); for (Pattern pattern : patterns) { if (!pattern.getActive()) continue; try { java.util.regex.Pattern compiledPattern = java.util.regex.Pattern.compile(pattern.getRegex()); for (int i = 0; i < lines.length; i++) { Matcher matcher = compiledPattern.matcher(lines[i]); if (matcher.find()) { // 컨텍스트 추출 String context = extractContext(lines, i, pattern.getContextLines()); String summary = createSummary(lines[i]); results.add(new MatchResult( pattern, filePath, i + 1, // 1-based line number summary, context, pattern.getSeverity() )); } } } catch (Exception e) { log.warn("Failed to match pattern {}: {}", pattern.getName(), e.getMessage()); } } return results; } /** * 컨텍스트 추출 (에러 전후 라인) */ private String extractContext(String[] lines, int matchIndex, int contextLines) { int start = Math.max(0, matchIndex - contextLines); int end = Math.min(lines.length - 1, matchIndex + contextLines); StringBuilder context = new StringBuilder(); for (int i = start; i <= end; i++) { if (i == matchIndex) { context.append(">>> "); // 에러 라인 표시 } context.append(String.format("[%d] %s\n", i + 1, lines[i])); } return context.toString(); } /** * 요약 생성 (첫 줄, 최대 200자) */ private String createSummary(String line) { if (line == null) return ""; String trimmed = line.trim(); if (trimmed.length() <= 200) return trimmed; return trimmed.substring(0, 200) + "..."; } // DTO public record MatchResult( Pattern pattern, String filePath, int lineNumber, String summary, String context, String severity ) {} }