diff --git a/familing/src/main/java/com/pinu/familing/global/log/controller/LogController.java b/familing/src/main/java/com/pinu/familing/global/log/controller/LogController.java index affa00b..66cba85 100644 --- a/familing/src/main/java/com/pinu/familing/global/log/controller/LogController.java +++ b/familing/src/main/java/com/pinu/familing/global/log/controller/LogController.java @@ -1,71 +1,92 @@ package com.pinu.familing.global.log.controller; -import com.pinu.familing.global.log.dto.LogResponse; import com.pinu.familing.global.log.service.LogService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/log") -@Slf4j public class LogController { private final LogService logService; private final List emitters = new CopyOnWriteArrayList<>(); - private static final Long TIMEOUT = Long.MAX_VALUE; - -// // 기본 로그 조회 API - 향후 필터링 기능 추가를 위해 유지 -// @GetMapping -// public LogResponse getLogs( -// @RequestParam(defaultValue = "0") int page, -// @RequestParam(defaultValue = "100") int size -// ) { -// return logService.getLogsByPage(page, size); -// } + private static final Long TIMEOUT = 60L * 1000 * 60; // 60 minutes - @GetMapping("/stream") + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamLogs() { SseEmitter emitter = new SseEmitter(TIMEOUT); emitters.add(emitter); - emitter.onCompletion(() -> emitters.remove(emitter)); - emitter.onTimeout(() -> emitters.remove(emitter)); + // 클라이언트 연결이 끊어졌을 때 처리 + emitter.onCompletion(() -> { + log.debug("SSE 연결 완료"); + emitters.remove(emitter); + }); + + emitter.onTimeout(() -> { + log.debug("SSE 연결 타임아웃"); + emitter.complete(); + emitters.remove(emitter); + }); + + // 에러 발생 시 처리 + emitter.onError(ex -> { + log.error("SSE 에러 발생", ex); + emitter.complete(); + emitters.remove(emitter); + }); - // Send initial data try { + // 초기 데이터 전송 List initialLogs = logService.getNewLogs(); - emitter.send(SseEmitter.event().data(initialLogs)); + if (!initialLogs.isEmpty()) { + emitter.send(SseEmitter.event() + .name("logs") + .data(initialLogs, MediaType.APPLICATION_JSON)); + } } catch (IOException e) { - log.error("Failed to send initial logs", e); - emitter.complete(); + log.error("초기 로그 전송 실패", e); + emitters.remove(emitter); + emitter.completeWithError(e); } return emitter; } - @Scheduled(fixedDelay = 1000) + // 주기적으로 새로운 로그 확인 및 전송 + //@Scheduled(fixedDelay = 1000) public void checkNewLogs() { - if (emitters.isEmpty()) return; + if (emitters.isEmpty()) { + return; + } List newLogs = logService.getNewLogs(); if (!newLogs.isEmpty()) { + List deadEmitters = new ArrayList<>(); + emitters.forEach(emitter -> { try { - emitter.send(SseEmitter.event().data(newLogs)); + emitter.send(SseEmitter.event() + .name("logs") + .data(newLogs, MediaType.APPLICATION_JSON)); } catch (IOException e) { - emitters.remove(emitter); + deadEmitters.add(emitter); + log.error("로그 전송 실패", e); } }); + + emitters.removeAll(deadEmitters); } } } \ No newline at end of file diff --git a/familing/src/main/java/com/pinu/familing/global/log/service/LogService.java b/familing/src/main/java/com/pinu/familing/global/log/service/LogService.java index f1f54da..483347d 100644 --- a/familing/src/main/java/com/pinu/familing/global/log/service/LogService.java +++ b/familing/src/main/java/com/pinu/familing/global/log/service/LogService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; @@ -16,10 +17,23 @@ public class LogService { private String logFilePath; public List getNewLogs() { + // 파일 체크를 먼저 수행 + File logFile = new File(logFilePath); + log.debug("로그 파일 경로: {}", logFilePath); // 설정된 경로 확인용 - try (RandomAccessFile file = new RandomAccessFile(logFilePath, "r")) { - long fileLength = file.length(); + if (!logFile.exists()) { + log.error("로그 파일을 찾을 수 없습니다. 경로: {}", logFilePath); + return new ArrayList<>(); + } + + if (!logFile.canRead()) { + log.error("로그 파일에 대한 읽기 권한이 없습니다. 경로: {}", logFilePath); + return new ArrayList<>(); + } + + log.debug("파일 크기: {} bytes", logFile.length()); // 파일 크기 확인용 + try (RandomAccessFile file = new RandomAccessFile(logFile, "r")) { List logs = new ArrayList<>(); String line;