Commit d82e93c4 authored by platyhouse's avatar platyhouse

# ptyRun 로그 출력 및 로테이션 기능 추가

## ptyRun 스크립트 개선

### 로그 파일 저장 기능 추가
- ptyRun: 스크립트 실행 시 stdout/stderr를 로그 파일에 저장하는 기능 구현
- ptyRun: proc_open을 사용하여 실시간으로 출력을 화면과 로그 파일에 동시 기록
- ptyRun: 로그 파일명에 스크립트명과 타임스탬프 포함 (예: script_20231223_143022.log)

### 로그 로테이션 옵션 추가
- ptyRun: `--log-dir=경로` 옵션으로 로그 저장 폴더 지정 (기본값: 스크립트폴더/ptyRun_logs)
- ptyRun: `--log-rotate-date=일수` 옵션으로 일수 기반 로그 보관 기능 구현
- ptyRun: `--log-rotate-count=개수` 옵션으로 개수 기반 로그 보관 기능 구현
- ptyRun: rotateLogsByDate(), rotateLogsByCount() 함수로 오래된 로그 자동 삭제

### 프로세스 실행 방식 변경
- ptyRun: pcntl_fork 대신 proc_open 사용으로 stdout/stderr 캡처 가능하도록 개선
- ptyRun: 논블로킹 모드로 실시간 출력 읽기 구현
- ptyRun: 락 파일에 log_file 정보 추가

## ptyCliLog 클래스 확장

### 공개 속성 추가
- ptyLibrary_PHP/cli/ptyCliLog.php: url, elastic 속성 추가로 외부 연동 정보 저장 가능
parent b2f61522
...@@ -20,6 +20,8 @@ class ptyCliLog ...@@ -20,6 +20,8 @@ class ptyCliLog
private $color; private $color;
private $debug; private $debug;
private $colors; private $colors;
public $url;
public $elastic;
public function __construct($prefix = "APP", $color = ptyCliColor::LIGHT_WHITE, $debug = true) public function __construct($prefix = "APP", $color = ptyCliColor::LIGHT_WHITE, $debug = true)
{ {
......
...@@ -8,10 +8,14 @@ ...@@ -8,10 +8,14 @@
* 옵션: * 옵션:
* --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false) * --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false)
* --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한) * --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)
* --log-dir=경로 로그 저장 폴더 (기본값: 스크립트폴더/ptyRun_logs)
* --log-rotate-date=일수 로그 보관 일수 (기본값: 0 - 무제한)
* --log-rotate-count=개수 로그 보관 개수 (기본값: 0 - 무제한)
* --help 도움말 출력 * --help 도움말 출력
* *
* 예제: * 예제:
* ./ptyRun "php /path/to/script.php" --max-run-time=20 * ./ptyRun "php /path/to/script.php" --max-run-time=20
* ./ptyRun "php script.php" --log-rotate-date=7
*/ */
namespace platyFramework; namespace platyFramework;
...@@ -31,12 +35,17 @@ if (empty($positionalArgs) || isset($options['help'])) { ...@@ -31,12 +35,17 @@ if (empty($positionalArgs) || isset($options['help'])) {
echo "옵션:\n"; echo "옵션:\n";
echo " --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false)\n"; echo " --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false)\n";
echo " --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)\n"; echo " --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)\n";
echo " --log-dir=경로 로그 저장 폴더 (기본값: 스크립트폴더/ptyRun_logs)\n";
echo " --log-rotate-date=일수 로그 보관 일수 (기본값: 0 - 무제한)\n";
echo " --log-rotate-count=개수 로그 보관 개수 (기본값: 0 - 무제한)\n";
echo " --help 도움말 출력\n"; echo " --help 도움말 출력\n";
echo "\n"; echo "\n";
echo "예제:\n"; echo "예제:\n";
echo " {$argv[0]} \"php /path/to/script.php\" --max-run-time=20\n"; echo " {$argv[0]} \"php /path/to/script.php\" --max-run-time=20\n";
echo " {$argv[0]} \"php script.php\" --run-duplicate=true\n"; echo " {$argv[0]} \"php script.php\" --run-duplicate=true\n";
echo " {$argv[0]} \"./myScript.sh\"\n"; echo " {$argv[0]} \"./myScript.sh\"\n";
echo " {$argv[0]} \"php script.php\" --log-rotate-date=7\n";
echo " {$argv[0]} \"php script.php\" --log-rotate-count=10 --log-dir=/var/log/myapp\n";
echo "\n"; echo "\n";
exit(isset($options['help']) ? 0 : 1); exit(isset($options['help']) ? 0 : 1);
} }
...@@ -44,6 +53,63 @@ if (empty($positionalArgs) || isset($options['help'])) { ...@@ -44,6 +53,63 @@ if (empty($positionalArgs) || isset($options['help'])) {
$scriptCommand = $positionalArgs[0]; $scriptCommand = $positionalArgs[0];
$runDuplicate = isset($options['run-duplicate']) && $options['run-duplicate'] === 'true'; $runDuplicate = isset($options['run-duplicate']) && $options['run-duplicate'] === 'true';
$maxRunTime = isset($options['max-run-time']) ? (int)$options['max-run-time'] : 0; $maxRunTime = isset($options['max-run-time']) ? (int)$options['max-run-time'] : 0;
$logDir = $options['log-dir'] ?? __DIR__ . '/ptyRun_logs';
$logRotateDate = isset($options['log-rotate-date']) ? (int)$options['log-rotate-date'] : 0;
$logRotateCount = isset($options['log-rotate-count']) ? (int)$options['log-rotate-count'] : 0;
// 로그 디렉토리 생성
if (!is_dir($logDir)) {
if (!mkdir($logDir, 0755, true)) {
echo "Error: 로그 디렉토리 생성 실패: {$logDir}\n";
exit(1);
}
}
// 로그 파일명 생성 (스크립트명에서 안전한 파일명 추출)
function getLogFileName($command, $logDir) {
// 명령어에서 스크립트 파일명 추출
preg_match('/([a-zA-Z0-9_\-\.]+\.(?:php|sh|py|rb|js|pl))/i', $command, $matches);
$scriptName = $matches[1] ?? 'script';
$scriptName = preg_replace('/\.[^.]+$/', '', $scriptName); // 확장자 제거
$timestamp = date('Ymd_His');
return $logDir . '/' . $scriptName . '_' . $timestamp . '.log';
}
// 로그 로테이션 함수
function rotateLogsByDate($logDir, $days) {
if ($days <= 0) return;
$cutoffTime = time() - ($days * 24 * 60 * 60);
$files = glob($logDir . '/*.log');
foreach ($files as $file) {
if (filemtime($file) < $cutoffTime) {
@unlink($file);
}
}
}
function rotateLogsByCount($logDir, $count) {
if ($count <= 0) return;
$files = glob($logDir . '/*.log');
if (count($files) <= $count) return;
// 파일을 수정 시간 기준 정렬 (오래된 것이 먼저)
usort($files, function($a, $b) {
return filemtime($a) - filemtime($b);
});
// 초과분 삭제
$deleteCount = count($files) - $count;
for ($i = 0; $i < $deleteCount; $i++) {
@unlink($files[$i]);
}
}
// 로그 로테이션 실행
rotateLogsByDate($logDir, $logRotateDate);
rotateLogsByCount($logDir, $logRotateCount);
// 로거 인스턴스 생성 // 로거 인스턴스 생성
$log = ptyCliLog("RUN", ptyCliLog::COLOR_CYAN, true); $log = ptyCliLog("RUN", ptyCliLog::COLOR_CYAN, true);
...@@ -94,34 +160,114 @@ try { ...@@ -94,34 +160,114 @@ try {
} }
} }
// 로그 파일 준비
$logFile = getLogFileName($scriptCommand, $logDir);
$logHandle = fopen($logFile, 'w');
if (!$logHandle) {
throw new \Exception("로그 파일 생성 실패: {$logFile}");
}
// 로그 헤더 작성
$logHeader = sprintf(
"[%s] ptyRun 시작\n명령어: %s\n%s\n",
date('Y-m-d H:i:s'),
$scriptCommand,
str_repeat('-', 60)
);
fwrite($logHandle, $logHeader);
// 스크립트 실행 // 스크립트 실행
$log->info("스크립트 실행: {$scriptCommand}"); $log->info("스크립트 실행: {$scriptCommand}");
$log->info("로그 파일: {$logFile}");
$pid = pcntl_fork(); // proc_open으로 실행하여 stdout/stderr 캡처
$descriptorspec = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open($scriptCommand, $descriptorspec, $pipes, null, null);
if (!is_resource($process)) {
fclose($logHandle);
throw new \Exception("프로세스 실행 실패");
}
// 부모 프로세스의 stdin 닫기
fclose($pipes[0]);
// 논블로킹 모드 설정
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
// 프로세스 정보 가져오기
$status = proc_get_status($process);
$pid = $status['pid'];
if ($pid == -1) {
throw new \Exception("프로세스 fork 실패");
} elseif ($pid == 0) {
// 자식 프로세스
exec($scriptCommand, $output, $returnCode);
exit($returnCode);
} else {
// 부모 프로세스
if (!$runDuplicate) { if (!$runDuplicate) {
// 락 파일 생성 // 락 파일 생성
$lockData = [ $lockData = [
'pid' => $pid, 'pid' => $pid,
'command' => $scriptCommand, 'command' => $scriptCommand,
'start_time' => time(), 'start_time' => time(),
'log_file' => $logFile,
]; ];
file_put_contents($lockFile, json_encode($lockData)); file_put_contents($lockFile, json_encode($lockData));
file_put_contents($pidFile, $pid); file_put_contents($pidFile, $pid);
} }
// 자식 프로세스 대기 // stdout/stderr 실시간 읽기 및 출력
$status = 0; while (true) {
pcntl_waitpid($pid, $status); $status = proc_get_status($process);
$exitCode = pcntl_wexitstatus($status);
// stdout 읽기
while ($line = fgets($pipes[1])) {
echo $line;
fwrite($logHandle, $line);
fflush($logHandle);
}
// stderr 읽기
while ($line = fgets($pipes[2])) {
fwrite(STDERR, $line);
fwrite($logHandle, "[STDERR] " . $line);
fflush($logHandle);
}
// 프로세스가 종료되었으면 남은 출력 읽고 종료
if (!$status['running']) {
// 남은 stdout 읽기
while ($line = fgets($pipes[1])) {
echo $line;
fwrite($logHandle, $line);
}
// 남은 stderr 읽기
while ($line = fgets($pipes[2])) {
fwrite(STDERR, $line);
fwrite($logHandle, "[STDERR] " . $line);
}
break;
}
// CPU 사용률 낮추기 위해 잠시 대기
usleep(10000); // 10ms
}
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);
// 로그 푸터 작성
$logFooter = sprintf(
"\n%s\n[%s] ptyRun 종료 (Exit Code: %d)\n",
str_repeat('-', 60),
date('Y-m-d H:i:s'),
$exitCode
);
fwrite($logHandle, $logFooter);
fclose($logHandle);
// 락 파일 제거 // 락 파일 제거
if (!$runDuplicate) { if (!$runDuplicate) {
...@@ -136,7 +282,6 @@ try { ...@@ -136,7 +282,6 @@ try {
} }
exit($exitCode); exit($exitCode);
}
} catch (\Exception $e) { } catch (\Exception $e) {
$log->error("Error: " . $e->getMessage()); $log->error("Error: " . $e->getMessage());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment