feat: add dedicated deploy logging to storage/logs/deploy.log

- Add 'deploy' channel in logging.php
- Instrument DeployTask with phpLog() for all state transitions
- Instrument DeploySiteAction with channel('deploy') for triggers, errors, clears
- Logs written to storage/logs/deploy.log (separate from laravel.log)
This commit is contained in:
F4ilji
2026-07-02 00:57:57 +05:00
parent 8715179ffa
commit 09bba25993
3 changed files with 197 additions and 97 deletions
@@ -2,72 +2,63 @@
namespace App\Containers\Dashboard\Actions\Posts;
use App\Containers\Dashboard\Tasks\DeployTask;
use Illuminate\Support\Facades\Log;
class DeploySiteAction
{
private string $deployScript = '/var/www/_deploy/deploy.sh';
private string $logFile = '/var/www/_deploy/deploy.log';
public function __construct(
private readonly DeployTask $deployTask,
) {}
/**
* Запускает deploy.sh напрямую
*
* @return array ['success' => bool, 'message' => string]
*/
public function run(): array
{
try {
if (!file_exists($this->deployScript)) {
if (!$this->deployTask->scriptExists()) {
Log::channel('deploy')->warning('[DeployAction] Script not found');
return [
'success' => false,
'message' => 'Скрипт деплоя не найден на сервере',
];
}
// Проверяем, не запущен ли уже деплой
if ($this->isDeployRunning()) {
if ($this->deployTask->isDeployRunning()) {
Log::channel('deploy')->warning('[DeployAction] Deploy already running', [
'user_id' => auth()->id(),
]);
return [
'success' => false,
'message' => 'Деплой уже запущен! Подождите завершения текущего процесса.',
];
}
// Очищаем старый лог
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
// Запускаем deploy.sh в фоне (docker socket проброшен в контейнер)
$command = sprintf(
'bash %s > %s 2>&1 &',
escapeshellarg($this->deployScript),
escapeshellarg($this->logFile)
);
shell_exec($command);
// Даём процессу время на запуск
usleep(500000); // 0.5 сек
if (!$this->isDeployRunning()) {
// Процесс не запустился — читаем лог
$error = file_exists($this->logFile) ? file_get_contents($this->logFile) : 'Нет лога';
Log::error('Deploy script failed to start', ['log' => $error]);
$started = $this->deployTask->startDeploy();
if (!$started) {
Log::channel('deploy')->error('[DeployAction] Failed to start deploy script', [
'user_id' => auth()->id(),
]);
return [
'success' => false,
'message' => 'Скрипт деплоя не запустился. Проверьте лог.',
];
}
Log::info('Deploy triggered', ['user_id' => auth()->id()]);
Log::channel('deploy')->info('[DeployAction] Deploy triggered successfully', [
'user_id' => auth()->id(),
'user_email' => auth()->user()?->email,
]);
return [
'success' => true,
'message' => 'Деплой запущен! Процесс обновления сайта начнётся в течение нескольких секунд.',
];
} catch (\Exception $e) {
Log::error('Deploy trigger failed', ['exception' => $e->getMessage()]);
Log::channel('deploy')->error('[DeployAction] Exception', [
'user_id' => auth()->id(),
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'success' => false,
@@ -76,78 +67,31 @@ class DeploySiteAction
}
}
/**
* Проверяет статус деплоя
*/
public function getStatus(): array
{
if (!file_exists($this->logFile)) {
return ['status' => 'idle', 'message' => 'Деплой не запущен'];
}
$log = file_get_contents($this->logFile);
// Проверяем, запущен ли процесс
if ($this->isDeployRunning()) {
return [
'status' => 'running',
'message' => 'Деплой выполняется...',
'log' => $log,
];
}
// Процесс завершил — проверяем результат
$lastLines = array_slice(explode("\n", trim($log)), -5);
$lastOutput = implode("\n", $lastLines);
$isSuccess = stripos($lastOutput, 'успешн') !== false
|| stripos($lastOutput, 'success') !== false
|| stripos($lastOutput, '✅') !== false;
$isFailed = stripos($lastOutput, 'ошибк') !== false
|| stripos($lastOutput, 'error') !== false
|| stripos($lastOutput, '❌') !== false;
if ($isSuccess) {
return [
'status' => 'completed',
'message' => 'Деплой завершён успешно!',
'log' => $log,
];
}
if ($isFailed) {
return [
'status' => 'failed',
'message' => 'Деплой завершён с ошибкой',
'log' => $log,
];
}
return $this->deployTask->getDeployStatus();
}
public function getLog(int $lines = 50): array
{
return [
'status' => 'unknown',
'message' => 'Статус неизвестен',
'log' => $log,
'log' => $this->deployTask->getLogTail($lines),
'full_log' => $this->deployTask->getLog(),
];
}
public function getHistory(): array
{
return [
'history' => $this->deployTask->getHistory(),
];
}
/**
* Очищает статус деплоя
*/
public function clearStatus(): void
{
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
/**
* Проверяет, запущен ли процесс деплоя
*/
private function isDeployRunning(): bool
{
$output = shell_exec('pgrep -f "deploy\\.sh"');
return !empty(trim($output ?? ''));
Log::channel('deploy')->info('[DeployAction] Log cleared', [
'user_id' => auth()->id(),
]);
$this->deployTask->clearLog();
}
}
@@ -0,0 +1,149 @@
<?php
namespace App\Containers\Dashboard\Tasks;
use Illuminate\Support\Facades\Log;
class DeployTask
{
private string $deployScript = '/var/www/_deploy/deploy.sh';
private string $logFile = '/var/www/_deploy/deploy.log';
private string $historyDir = '/var/www/_deploy/history';
public function scriptExists(): bool
{
$exists = file_exists($this->deployScript);
$this->phpLog('check_script', $exists ? 'found' : 'not_found');
return $exists;
}
public function isDeployRunning(): bool
{
$output = shell_exec('pgrep -f "deploy\\.sh"');
$running = !empty(trim($output ?? ''));
return $running;
}
public function startDeploy(): bool
{
if ($this->isDeployRunning()) {
$this->phpLog('start', 'already_running');
return false;
}
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
$this->phpLog('start', 'launching deploy.sh');
$command = sprintf(
'bash %s > /dev/null 2>&1 &',
escapeshellarg($this->deployScript)
);
shell_exec($command);
usleep(500000);
$started = $this->isDeployRunning();
$this->phpLog('start', $started ? 'process_started' : 'process_failed_to_start');
return $started;
}
public function getDeployStatus(): array
{
if (!$this->isDeployRunning()) {
if (!file_exists($this->logFile)) {
$this->phpLog('status', 'idle');
return ['status' => 'idle', 'message' => 'Деплой не запущен'];
}
$log = $this->getLog();
$lastLines = array_slice(explode("\n", trim($log)), -5);
$lastOutput = implode("\n", $lastLines);
$isSuccess = stripos($lastOutput, '✅') !== false
|| stripos($lastOutput, 'completed successfully') !== false;
$isFailed = stripos($lastOutput, '❌') !== false
|| stripos($lastOutput, 'ERROR') !== false;
$status = $isSuccess ? 'completed' : ($isFailed ? 'failed' : 'unknown');
$this->phpLog('status', $status);
return [
'status' => $status,
'message' => $isSuccess ? 'Деплой завершён успешно!' : 'Деплой завершён',
'log' => $log,
];
}
$this->phpLog('status', 'running');
return [
'status' => 'running',
'message' => 'Деплой выполняется...',
'log' => $this->getLog(),
];
}
public function getLog(): string
{
if (!file_exists($this->logFile)) {
return '';
}
return file_get_contents($this->logFile);
}
public function getLogTail(int $lines = 50): string
{
$log = $this->getLog();
if (empty($log)) {
return '';
}
$allLines = explode("\n", trim($log));
return implode("\n", array_slice($allLines, -$lines));
}
public function clearLog(): void
{
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
$this->phpLog('clear', 'deploy log cleared');
}
public function getHistory(): array
{
if (!is_dir($this->historyDir)) {
return [];
}
$files = glob($this->historyDir . '/*.json');
usort($files, fn($a, $b) => strcmp($b, $a));
$history = [];
foreach (array_slice($files, 0, 50) as $file) {
$content = file_get_contents($file);
$data = json_decode($content, true);
if ($data) {
$history[] = $data;
}
}
return $history;
}
private function phpLog(string $event, string $message): void
{
Log::channel('deploy')->info('[Deploy] {event}: {message}', [
'event' => $event,
'message' => $message,
]);
}
}