data['title'] = 'Worker 로그 통합 관리'; // 로그 디렉토리 목록 $logDirs = [ 'api_receiver' => ROOTPATH . 'worker/logs', 'naver_worker' => WRITEPATH . 'logs/worker' ]; // 날짜 필터 (기본값: 오늘) $date = $this->request->getGet('date') ?? date('Y-m-d'); $logType = $this->request->getGet('type') ?? 'all'; $logs = []; // API Receiver 로그 읽기 if ($logType === 'all' || $logType === 'api_receiver') { $apiLogFile = $logDirs['api_receiver'] . '/' . $date . '.log'; if (file_exists($apiLogFile)) { $logs['api_receiver'] = $this->parseLogFile($apiLogFile); } } // Naver Worker 로그 읽기 if ($logType === 'all' || $logType === 'naver_worker') { $workerLogFile = $logDirs['naver_worker'] . '/' . $date . '.log'; if (file_exists($workerLogFile)) { $logs['naver_worker'] = $this->parseLogFile($workerLogFile); } // Failed 로그도 읽기 $failedLogFile = $logDirs['naver_worker'] . '/' . $date . '_failed.log'; if (file_exists($failedLogFile)) { $logs['naver_worker_failed'] = $this->parseLogFile($failedLogFile); } } // 모든 로그를 시간순으로 통합 정렬 $allLogs = []; foreach ($logs as $type => $entries) { foreach ($entries as $entry) { $entry['source'] = $type; $allLogs[] = $entry; } } // 시간 역순 정렬 (최신순) usort($allLogs, function($a, $b) { return strtotime($b['timestamp']) - strtotime($a['timestamp']); }); $this->data['logs'] = $allLogs; $this->data['date'] = $date; $this->data['logType'] = $logType; $this->data['logDirs'] = $logDirs; // 사용 가능한 날짜 목록 (최근 30일) $this->data['availableDates'] = $this->getAvailableLogDates($logDirs); return view('pages/manage/worker_log', $this->data); } /** * 로그 파일 파싱 */ private function parseLogFile($filePath) { $logs = []; $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { $logs[] = $this->parseLogLine($line); } return $logs; } /** * 로그 한 줄 파싱 */ private function parseLogLine($line) { // 로그 포맷: [2025-12-22 10:30:45] [INFO] [NaverWorker::run:95] 메시지 if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) { return [ 'timestamp' => $matches[1], 'level' => $matches[2], 'location' => $matches[3] ?? '', 'message' => $matches[4] ]; } // 파싱 실패한 경우 원본 그대로 return [ 'timestamp' => date('Y-m-d H:i:s'), 'level' => 'UNKNOWN', 'location' => '', 'message' => $line ]; } /** * 로그 파일이 존재하는 날짜 목록 가져오기 */ private function getAvailableLogDates($logDirs) { $dates = []; foreach ($logDirs as $dir) { if (!is_dir($dir)) continue; $files = scandir($dir); foreach ($files as $file) { if (preg_match('/(\d{4}-\d{2}-\d{2})(?:_failed)?\.log$/', $file, $matches)) { $dates[$matches[1]] = true; } } } $dates = array_keys($dates); rsort($dates); // 최신순 정렬 return array_slice($dates, 0, 30); // 최근 30일만 } /** * 실시간 로그 스트리밍 (Ajax) */ public function stream() { $type = $this->request->getGet('type') ?? 'naver_worker'; $lastId = (int) ($this->request->getGet('lastId') ?? 0); $logDirs = [ 'api_receiver' => ROOTPATH . 'worker/logs', 'naver_worker' => WRITEPATH . 'logs/worker' ]; $logFile = $logDirs[$type] . '/' . date('Y-m-d') . '.log'; $newLogs = []; if (file_exists($logFile)) { $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); // lastId 이후의 로그만 반환 if ($lastId < count($lines)) { $newLines = array_slice($lines, $lastId); foreach ($newLines as $line) { $newLogs[] = $this->parseLogLine($line); } } } return $this->response->setJSON([ 'success' => true, 'logs' => $newLogs, 'lastId' => $lastId + count($newLogs) ]); } /** * 로그 파일 다운로드 */ public function download() { $date = $this->request->getGet('date') ?? date('Y-m-d'); $type = $this->request->getGet('type') ?? 'naver_worker'; $logDirs = [ 'api_receiver' => ROOTPATH . 'worker/logs', 'naver_worker' => WRITEPATH . 'logs/worker' ]; $logFile = $logDirs[$type] . '/' . $date . '.log'; if (!file_exists($logFile)) { return $this->response->setStatusCode(404)->setBody('로그 파일을 찾을 수 없습니다.'); } return $this->response->download($logFile, null); } /** * 로그 파일 삭제 */ public function delete() { $date = $this->request->getPost('date'); $type = $this->request->getPost('type'); if (!$date || !$type) { return $this->response->setJSON(['success' => false, 'message' => '필수 파라미터 누락']); } $logDirs = [ 'api_receiver' => ROOTPATH . 'worker/logs', 'naver_worker' => WRITEPATH . 'logs/worker' ]; $logFile = $logDirs[$type] . '/' . $date . '.log'; if (file_exists($logFile)) { unlink($logFile); return $this->response->setJSON(['success' => true, 'message' => '로그 파일이 삭제되었습니다.']); } return $this->response->setJSON(['success' => false, 'message' => '로그 파일을 찾을 수 없습니다.']); } }