update 22

This commit is contained in:
2026-01-07 01:14:51 +09:00
parent 57c3eea429
commit 66e8e21302
220 changed files with 2911 additions and 700 deletions

View File

@@ -7,17 +7,17 @@ import org.springframework.transaction.annotation.Transactional;
import research.loghunter.entity.*;
import research.loghunter.repository.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@@ -450,4 +450,339 @@ public class ScanService {
int errorsFound,
String error
) {}
// === 분석 결과 초기화 ===
/**
* 서버별 분석 결과 초기화
*/
@Transactional
public ResetResult resetScanData(Long serverId) {
long deletedErrors = errorLogRepository.countByServerId(serverId);
errorLogRepository.deleteByServerId(serverId);
List<ScannedFile> scannedFiles = scannedFileRepository.findByServerId(serverId);
int deletedFiles = scannedFiles.size();
scannedFileRepository.deleteAll(scannedFiles);
List<ScanHistory> histories = scanHistoryRepository.findByServerIdOrderByStartedAtDesc(serverId);
int deletedHistories = histories.size();
scanHistoryRepository.deleteAll(histories);
log.info("Reset scan data for server {}: {} errors, {} files, {} histories",
serverId, deletedErrors, deletedFiles, deletedHistories);
return new ResetResult((int) deletedErrors, deletedFiles, deletedHistories);
}
/**
* 전체 분석 결과 초기화
*/
@Transactional
public ResetResult resetAllScanData() {
long deletedErrors = errorLogRepository.count();
errorLogRepository.deleteAll();
long deletedFiles = scannedFileRepository.count();
scannedFileRepository.deleteAll();
long deletedHistories = scanHistoryRepository.count();
scanHistoryRepository.deleteAll();
log.info("Reset all scan data: {} errors, {} files, {} histories",
deletedErrors, deletedFiles, deletedHistories);
return new ResetResult((int) deletedErrors, (int) deletedFiles, (int) deletedHistories);
}
// === 에러 통계 ===
/**
* 파일별 에러 통계
*/
public List<FileErrorStats> getErrorStatsByFile(Long serverId) {
List<Object[]> results = errorLogRepository.getErrorStatsByFile(serverId);
return results.stream()
.map(row -> new FileErrorStats(
(String) row[0], // filePath
(String) row[1], // serverName
((Number) row[2]).intValue(), // totalCount
((Number) row[3]).intValue(), // criticalCount
((Number) row[4]).intValue(), // errorCount
((Number) row[5]).intValue(), // warnCount
(LocalDateTime) row[6] // lastOccurredAt
))
.toList();
}
/**
* 서버별 에러 통계
*/
public List<ServerErrorStats> getErrorStatsByServer() {
List<Object[]> results = errorLogRepository.getErrorStatsByServer();
return results.stream()
.map(row -> new ServerErrorStats(
((Number) row[0]).longValue(), // serverId
(String) row[1], // serverName
((Number) row[2]).intValue(), // totalCount
((Number) row[3]).intValue(), // criticalCount
((Number) row[4]).intValue(), // errorCount
((Number) row[5]).intValue(), // warnCount
(LocalDateTime) row[6] // lastOccurredAt
))
.toList();
}
/**
* 패턴별 에러 통계
*/
public List<PatternErrorStats> getErrorStatsByPattern(Long serverId) {
List<Object[]> results = errorLogRepository.getErrorStatsByPattern(serverId);
return results.stream()
.map(row -> new PatternErrorStats(
((Number) row[0]).longValue(), // patternId
(String) row[1], // patternName
(String) row[2], // severity
((Number) row[3]).intValue(), // count
(LocalDateTime) row[4] // lastOccurredAt
))
.toList();
}
/**
* 대시보드용: 서버별 최근 N일 일별 통계
*/
public List<ServerDailyStats> getDailyStatsByServer(int days) {
LocalDateTime endDate = LocalDate.now().plusDays(1).atStartOfDay(); // 내일 00:00
LocalDateTime startDate = LocalDate.now().minusDays(days - 1).atStartOfDay(); // N일 전 00:00
// epoch milliseconds로 변환
long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
// 서버별로 그룹화
Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
for (Object[] row : results) {
Long serverId = ((Number) row[0]).longValue();
String serverName = (String) row[1];
String dateStr = (String) row[2]; // SQLite date() 함수는 String 반환
int total = ((Number) row[3]).intValue();
int critical = ((Number) row[4]).intValue();
int error = ((Number) row[5]).intValue();
int warn = ((Number) row[6]).intValue();
serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
.dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
}
// 모든 날짜 채우기 (데이터 없는 날짜는 0으로)
List<String> allDates = new ArrayList<>();
for (int i = days - 1; i >= 0; i--) {
allDates.add(LocalDate.now().minusDays(i).toString());
}
for (ServerDailyStats server : serverMap.values()) {
Map<String, DailyStat> dateMap = server.dailyStats().stream()
.collect(Collectors.toMap(DailyStat::date, s -> s));
List<DailyStat> filledStats = allDates.stream()
.map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
.toList();
server.dailyStats().clear();
server.dailyStats().addAll(filledStats);
}
return new ArrayList<>(serverMap.values());
}
/**
* 월별현황용: 서버별 해당 월 일별 통계
*/
public List<ServerDailyStats> getMonthlyStatsByServer(int year, int month) {
LocalDate firstDay = LocalDate.of(year, month, 1);
LocalDate lastDay = firstDay.withDayOfMonth(firstDay.lengthOfMonth());
LocalDateTime startDate = firstDay.atStartOfDay();
LocalDateTime endDate = lastDay.plusDays(1).atStartOfDay();
// epoch milliseconds로 변환
long startMs = startDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
long endMs = endDate.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli();
log.info("Monthly stats query: year={}, month={}, startMs={}, endMs={}", year, month, startMs, endMs);
List<Object[]> results = errorLogRepository.getDailyStatsByServer(startMs, endMs);
log.info("Monthly stats results count: {}", results.size());
// 서버별로 그룹화
Map<Long, ServerDailyStats> serverMap = new LinkedHashMap<>();
for (Object[] row : results) {
Long serverId = ((Number) row[0]).longValue();
String serverName = (String) row[1];
String dateStr = (String) row[2]; // SQLite date() 함수는 String 반환
int total = ((Number) row[3]).intValue();
int critical = ((Number) row[4]).intValue();
int error = ((Number) row[5]).intValue();
int warn = ((Number) row[6]).intValue();
log.debug("Row: serverId={}, serverName={}, date={}, total={}", serverId, serverName, dateStr, total);
serverMap.computeIfAbsent(serverId, k -> new ServerDailyStats(serverId, serverName, new ArrayList<>()))
.dailyStats().add(new DailyStat(dateStr, total, critical, error, warn));
}
// 모든 날짜 채우기
List<String> allDates = new ArrayList<>();
for (int day = 1; day <= firstDay.lengthOfMonth(); day++) {
allDates.add(LocalDate.of(year, month, day).toString());
}
for (ServerDailyStats server : serverMap.values()) {
Map<String, DailyStat> dateMap = server.dailyStats().stream()
.collect(Collectors.toMap(DailyStat::date, s -> s));
List<DailyStat> filledStats = allDates.stream()
.map(date -> dateMap.getOrDefault(date, new DailyStat(date, 0, 0, 0, 0)))
.toList();
server.dailyStats().clear();
server.dailyStats().addAll(filledStats);
}
return new ArrayList<>(serverMap.values());
}
/**
* 일별현황용: 서버별 해당 날짜 5분 단위 통계
*/
public List<ServerTimeStats> getTimeStatsByServer(LocalDate date, int intervalMinutes) {
LocalDateTime startDate = date.atStartOfDay();
LocalDateTime endDate = date.plusDays(1).atStartOfDay();
List<Object[]> results = errorLogRepository.getErrorsByDateRange(startDate, endDate);
// 서버별로 그룹화
Map<Long, ServerTimeStats> serverMap = new LinkedHashMap<>();
// 시간 슬롯 초기화 (5분 단위 = 288개)
int slots = 24 * 60 / intervalMinutes;
for (Object[] row : results) {
Long serverId = ((Number) row[0]).longValue();
String serverName = (String) row[1];
LocalDateTime occurredAt = (LocalDateTime) row[2];
String severity = (String) row[3];
ServerTimeStats stats = serverMap.computeIfAbsent(serverId,
k -> new ServerTimeStats(serverId, serverName, initTimeSlots(slots, intervalMinutes)));
// 해당 시간 슬롯 찾기
int minuteOfDay = occurredAt.getHour() * 60 + occurredAt.getMinute();
int slotIndex = minuteOfDay / intervalMinutes;
if (slotIndex < stats.timeStats().size()) {
TimeStat slot = stats.timeStats().get(slotIndex);
slot.incrementTotal();
switch (severity) {
case "CRITICAL" -> slot.incrementCritical();
case "ERROR" -> slot.incrementError();
case "WARN" -> slot.incrementWarn();
}
}
}
return new ArrayList<>(serverMap.values());
}
private List<TimeStat> initTimeSlots(int slots, int intervalMinutes) {
List<TimeStat> timeStats = new ArrayList<>();
for (int i = 0; i < slots; i++) {
int minutes = i * intervalMinutes;
String time = String.format("%02d:%02d", minutes / 60, minutes % 60);
timeStats.add(new TimeStat(time));
}
return timeStats;
}
// DTOs for stats
public record ResetResult(int deletedErrors, int deletedFiles, int deletedHistories) {}
public record FileErrorStats(
String filePath,
String serverName,
int totalCount,
int criticalCount,
int errorCount,
int warnCount,
LocalDateTime lastOccurredAt
) {}
public record ServerErrorStats(
Long serverId,
String serverName,
int totalCount,
int criticalCount,
int errorCount,
int warnCount,
LocalDateTime lastOccurredAt
) {}
public record PatternErrorStats(
Long patternId,
String patternName,
String severity,
int count,
LocalDateTime lastOccurredAt
) {}
// 일별 통계 DTO
public record ServerDailyStats(
Long serverId,
String serverName,
List<DailyStat> dailyStats
) {}
public record DailyStat(
String date,
int total,
int critical,
int error,
int warn
) {}
// 시간별 통계 DTO
public record ServerTimeStats(
Long serverId,
String serverName,
List<TimeStat> timeStats
) {}
public static class TimeStat {
private final String time;
private int total;
private int critical;
private int error;
private int warn;
public TimeStat(String time) {
this.time = time;
}
public String getTime() { return time; }
public int getTotal() { return total; }
public int getCritical() { return critical; }
public int getError() { return error; }
public int getWarn() { return warn; }
public void incrementTotal() { total++; }
public void incrementCritical() { critical++; }
public void incrementError() { error++; }
public void incrementWarn() { warn++; }
}
}