From 5504ca154a111591be08660a64bf967c6f74e909 Mon Sep 17 00:00:00 2001 From: jjstyle Date: Tue, 10 Feb 2026 11:56:01 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9B=8C=EC=BB=A4=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EA=B7=B8=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 6 + app/Controllers/Manage/WorkerLog.php | 221 ++++++++++++++++++++ app/Views/pages/manage/worker_log.php | 284 ++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) create mode 100644 app/Controllers/Manage/WorkerLog.php create mode 100644 app/Views/pages/manage/worker_log.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 903ee9f..e46662a 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -661,6 +661,12 @@ $routes->group('manage', ['namespace' => 'App\Controllers\Manage'], function ($r /** API - 로그인로그관리 */ $routes->get('loginlog/getLogList', 'LoginLog::getLogList'); $routes->get('loginlog/excel', 'LoginLog::excel'); + + /** Worker 로그 관리 */ + $routes->get('workerlog', 'WorkerLog::index'); + $routes->get('workerlog/stream', 'WorkerLog::stream'); + $routes->get('workerlog/download', 'WorkerLog::download'); + $routes->post('workerlog/delete', 'WorkerLog::delete'); }); /** diff --git a/app/Controllers/Manage/WorkerLog.php b/app/Controllers/Manage/WorkerLog.php new file mode 100644 index 0000000..31b504c --- /dev/null +++ b/app/Controllers/Manage/WorkerLog.php @@ -0,0 +1,221 @@ + 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']); + }); + + $data['logs'] = $allLogs; + $data['date'] = $date; + $data['logType'] = $logType; + $data['logDirs'] = $logDirs; + + // 사용 가능한 날짜 목록 (최근 30일) + $data['availableDates'] = $this->getAvailableLogDates($logDirs); + + return view('pages/manage/worker_log', $data); + } + + /** + * 로그 파일 파싱 + */ + private function parseLogFile($filePath) + { + $logs = []; + $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + foreach ($lines as $line) { + // 로그 포맷: [2025-12-22 10:30:45] [INFO] [NaverWorker::run:95] 메시지 + if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) { + $logs[] = [ + 'timestamp' => $matches[1], + 'level' => $matches[2], + 'location' => $matches[3] ?? '', + 'message' => $matches[4] + ]; + } else { + // 파싱 실패한 경우 원본 그대로 + $logs[] = [ + 'timestamp' => date('Y-m-d H:i:s'), + 'level' => 'UNKNOWN', + 'location' => '', + 'message' => $line + ]; + } + } + + return $logs; + } + + /** + * 로그 파일이 존재하는 날짜 목록 가져오기 + */ + 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) { + if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) { + $newLogs[] = [ + 'timestamp' => $matches[1], + 'level' => $matches[2], + 'location' => $matches[3] ?? '', + 'message' => $matches[4] + ]; + } + } + } + } + + 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' => '로그 파일을 찾을 수 없습니다.']); + } +} diff --git a/app/Views/pages/manage/worker_log.php b/app/Views/pages/manage/worker_log.php new file mode 100644 index 0000000..a3aa2ca --- /dev/null +++ b/app/Views/pages/manage/worker_log.php @@ -0,0 +1,284 @@ +extend('layouts/base') ?> + +section('content') ?> +
+
+
+
+ +
+
+ Worker 로그 통합 관리 +
+ API Receiver 및 Worker 서비스의 로그를 한 곳에서 확인할 수 있습니다. +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
전체 로그
+
Total Logs
+
+
+
+ +
+
+
+
+
+
+
+
+
+
INFO
+
Information
+
+
+
+ $l['level'] === 'INFO')) ?> +
+
+
+
+
+
+
+
+
+
ERROR
+
Errors
+
+
+
+ $l['level'] === 'ERROR')) ?> +
+
+
+
+
+
+
+
+
+
DEBUG
+
Debug Logs
+
+
+
+ $l['level'] === 'DEBUG')) ?> +
+
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
시간레벨소스위치메시지
로그가 없습니다.
+ + + + + + 'API Receiver', + 'naver_worker' => 'Worker', + 'naver_worker_failed' => 'Worker (Failed)' + ]; + echo $sourceLabel[$log['source']] ?? $log['source']; + ?> + + + + + +
+
+
+
+
+
+ + + + + +endSection() ?>