Files
log-hunter/src/main/java/research/loghunter/service/ErrorLogService.java
2026-01-07 01:14:51 +09:00

196 lines
7.4 KiB
Java

package research.loghunter.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import research.loghunter.dto.ErrorLogDto;
import research.loghunter.dto.FileTreeDto;
import research.loghunter.entity.ErrorLog;
import research.loghunter.entity.Server;
import research.loghunter.repository.ErrorLogRepository;
import research.loghunter.repository.ScannedFileRepository;
import research.loghunter.repository.ServerRepository;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ErrorLogService {
private final ErrorLogRepository errorLogRepository;
private final ScannedFileRepository scannedFileRepository;
private final ServerRepository serverRepository;
public Page<ErrorLogDto> search(
Long serverId,
Long patternId,
String severity,
String filePath,
LocalDateTime startDate,
LocalDateTime endDate,
String keyword,
int page,
int size
) {
Pageable pageable = PageRequest.of(page, size);
Page<ErrorLog> errorLogs = errorLogRepository.searchErrors(
serverId, patternId, severity, filePath, startDate, endDate, keyword, pageable);
return errorLogs.map(this::toDto);
}
public Page<ErrorLogDto> findByServerId(Long serverId, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
Page<ErrorLog> errorLogs = errorLogRepository.findByServerIdOrderByOccurredAtDesc(serverId, pageable);
return errorLogs.map(this::toDto);
}
public ErrorLogDto findById(Long id) {
return errorLogRepository.findById(id)
.map(this::toDto)
.orElseThrow(() -> new RuntimeException("ErrorLog not found: " + id));
}
/**
* 트리 구조 데이터 조회
*/
public List<FileTreeDto.ServerNode> getFileTree() {
// 파일별 에러 통계 조회
List<Object[]> stats = errorLogRepository.getFileErrorStats();
// 서버별로 그룹핑
Map<Long, List<Object[]>> serverGroups = new LinkedHashMap<>();
for (Object[] stat : stats) {
Long serverId = ((Number) stat[0]).longValue();
serverGroups.computeIfAbsent(serverId, k -> new ArrayList<>()).add(stat);
}
List<FileTreeDto.ServerNode> result = new ArrayList<>();
for (Map.Entry<Long, List<Object[]>> entry : serverGroups.entrySet()) {
Long serverId = entry.getKey();
List<Object[]> serverStats = entry.getValue();
String serverName = (String) serverStats.get(0)[1];
// 경로별로 그룹핑
Map<String, List<FileTreeDto.FileNode>> pathGroups = new LinkedHashMap<>();
int serverTotalErrors = 0;
for (Object[] stat : serverStats) {
String filePath = (String) stat[2];
int errorCount = ((Number) stat[3]).intValue();
int criticalCount = ((Number) stat[4]).intValue();
int errorLevelCount = ((Number) stat[5]).intValue();
int warnCount = ((Number) stat[6]).intValue();
// 경로와 파일명 분리
int lastSlash = filePath.lastIndexOf('/');
String path = lastSlash > 0 ? filePath.substring(0, lastSlash) : "/";
String fileName = lastSlash > 0 ? filePath.substring(lastSlash + 1) : filePath;
FileTreeDto.FileNode fileNode = FileTreeDto.FileNode.builder()
.filePath(filePath)
.fileName(fileName)
.errorCount(errorCount)
.criticalCount(criticalCount)
.errorLevelCount(errorLevelCount)
.warnCount(warnCount)
.build();
pathGroups.computeIfAbsent(path, k -> new ArrayList<>()).add(fileNode);
serverTotalErrors += errorCount;
}
// PathNode 생성
List<FileTreeDto.PathNode> pathNodes = new ArrayList<>();
for (Map.Entry<String, List<FileTreeDto.FileNode>> pathEntry : pathGroups.entrySet()) {
List<FileTreeDto.FileNode> files = pathEntry.getValue();
int pathTotalErrors = files.stream().mapToInt(FileTreeDto.FileNode::getErrorCount).sum();
pathNodes.add(FileTreeDto.PathNode.builder()
.path(pathEntry.getKey())
.totalErrorCount(pathTotalErrors)
.files(files)
.build());
}
result.add(FileTreeDto.ServerNode.builder()
.serverId(serverId)
.serverName(serverName)
.totalErrorCount(serverTotalErrors)
.paths(pathNodes)
.build());
}
return result;
}
/**
* 서버별 파일 목록 조회
*/
public List<String> getFilesByServer(Long serverId) {
if (serverId == null) {
return errorLogRepository.findDistinctFilePaths();
}
return errorLogRepository.findDistinctFilePathsByServerId(serverId);
}
/**
* 선택한 ID들의 에러 삭제
*/
@Transactional
public int deleteByIds(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return 0;
}
return errorLogRepository.deleteByIdIn(ids);
}
/**
* 파일 삭제 (에러로그 + 스캔기록)
*/
@Transactional
public Map<String, Object> deleteFileAndErrors(Long serverId, String filePath) {
int deletedErrors = errorLogRepository.deleteByServerIdAndFilePath(serverId, filePath);
int deletedFiles = scannedFileRepository.deleteByServerIdAndFilePath(serverId, filePath);
return Map.of(
"success", true,
"deletedErrors", deletedErrors,
"deletedScannedFiles", deletedFiles
);
}
/**
* 서버+파일 기준 에러 삭제
*/
@Transactional
public int deleteByServerAndFile(Long serverId, String filePath) {
return errorLogRepository.deleteByServerIdAndFilePath(serverId, filePath);
}
private ErrorLogDto toDto(ErrorLog errorLog) {
return ErrorLogDto.builder()
.id(errorLog.getId())
.serverId(errorLog.getServer().getId())
.serverName(errorLog.getServer().getName())
.patternId(errorLog.getPattern().getId())
.patternName(errorLog.getPattern().getName())
.filePath(errorLog.getFilePath())
.lineNumber(errorLog.getLineNumber())
.summary(errorLog.getSummary())
.context(errorLog.getContext())
.severity(errorLog.getSeverity())
.occurredAt(errorLog.getOccurredAt())
.createdAt(errorLog.getCreatedAt())
.build();
}
}