Files
coolify/app/Console/Commands/ViewScheduledLogs.php

279 lines
9.1 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class ViewScheduledLogs extends Command
{
protected $signature = 'logs:scheduled
{--lines=50 : Number of lines to display}
{--follow : Follow the log file (tail -f)}
{--date= : Specific date (Y-m-d format, defaults to today)}
{--task-name= : Filter by task name (partial match)}
{--task-id= : Filter by task ID}
{--backup-name= : Filter by backup name (partial match)}
{--backup-id= : Filter by backup ID}
{--errors : View error logs only}
{--all : View both normal and error logs}
{--hourly : Filter hourly jobs}
{--daily : Filter daily jobs}
{--weekly : Filter weekly jobs}
{--monthly : Filter monthly jobs}
{--frequency= : Filter by specific cron expression}';
protected $description = 'View scheduled backups and tasks logs with optional filtering';
public function handle()
{
$date = $this->option('date') ?: now()->format('Y-m-d');
$logPaths = $this->getLogPaths($date);
if (empty($logPaths)) {
$this->showAvailableLogFiles($date);
return;
}
$lines = $this->option('lines');
$follow = $this->option('follow');
// Build grep filters
$filters = $this->buildFilters();
$filterDescription = $this->getFilterDescription();
$logTypeDescription = $this->getLogTypeDescription();
if ($follow) {
$this->info("Following {$logTypeDescription} logs for {$date}{$filterDescription} (Press Ctrl+C to stop)...");
$this->line('');
if (count($logPaths) === 1) {
$logPath = $logPaths[0];
if ($filters) {
passthru("tail -f {$logPath} | grep -E '{$filters}'");
} else {
passthru("tail -f {$logPath}");
}
} else {
// Multiple files - use multitail or tail with process substitution
$logPathsStr = implode(' ', $logPaths);
if ($filters) {
passthru("tail -f {$logPathsStr} | grep -E '{$filters}'");
} else {
passthru("tail -f {$logPathsStr}");
}
}
} else {
$this->info("Showing last {$lines} lines of {$logTypeDescription} logs for {$date}{$filterDescription}:");
$this->line('');
if (count($logPaths) === 1) {
$logPath = $logPaths[0];
if ($filters) {
passthru("tail -n {$lines} {$logPath} | grep -E '{$filters}'");
} else {
passthru("tail -n {$lines} {$logPath}");
}
} else {
// Multiple files - concatenate and sort by timestamp
$logPathsStr = implode(' ', $logPaths);
if ($filters) {
passthru("tail -n {$lines} {$logPathsStr} | sort | grep -E '{$filters}'");
} else {
passthru("tail -n {$lines} {$logPathsStr} | sort");
}
}
}
}
private function getLogPaths(string $date): array
{
$paths = [];
if ($this->option('errors')) {
// Error logs only
$errorPath = storage_path("logs/scheduled-errors-{$date}.log");
if (File::exists($errorPath)) {
$paths[] = $errorPath;
}
} elseif ($this->option('all')) {
// Both normal and error logs
$normalPath = storage_path("logs/scheduled-{$date}.log");
$errorPath = storage_path("logs/scheduled-errors-{$date}.log");
if (File::exists($normalPath)) {
$paths[] = $normalPath;
}
if (File::exists($errorPath)) {
$paths[] = $errorPath;
}
} else {
// Normal logs only (default)
$normalPath = storage_path("logs/scheduled-{$date}.log");
if (File::exists($normalPath)) {
$paths[] = $normalPath;
}
}
return $paths;
}
private function showAvailableLogFiles(string $date): void
{
$logType = $this->getLogTypeDescription();
$this->warn("No {$logType} logs found for date {$date}");
// Show available log files
$normalFiles = File::glob(storage_path('logs/scheduled-*.log'));
$errorFiles = File::glob(storage_path('logs/scheduled-errors-*.log'));
if (! empty($normalFiles) || ! empty($errorFiles)) {
$this->info('Available scheduled log files:');
if (! empty($normalFiles)) {
$this->line(' Normal logs:');
foreach ($normalFiles as $file) {
$basename = basename($file);
$this->line(" - {$basename}");
}
}
if (! empty($errorFiles)) {
$this->line(' Error logs:');
foreach ($errorFiles as $file) {
$basename = basename($file);
$this->line(" - {$basename}");
}
}
}
}
private function getLogTypeDescription(): string
{
if ($this->option('errors')) {
return 'error';
} elseif ($this->option('all')) {
return 'all';
} else {
return 'normal';
}
}
private function buildFilters(): ?string
{
$filters = [];
if ($taskName = $this->option('task-name')) {
$filters[] = '"task_name":"[^"]*'.preg_quote($taskName, '/').'[^"]*"';
}
if ($taskId = $this->option('task-id')) {
$filters[] = '"task_id":'.preg_quote($taskId, '/');
}
if ($backupName = $this->option('backup-name')) {
$filters[] = '"backup_name":"[^"]*'.preg_quote($backupName, '/').'[^"]*"';
}
if ($backupId = $this->option('backup-id')) {
$filters[] = '"backup_id":'.preg_quote($backupId, '/');
}
// Frequency filters
if ($this->option('hourly')) {
$filters[] = $this->getFrequencyPattern('hourly');
}
if ($this->option('daily')) {
$filters[] = $this->getFrequencyPattern('daily');
}
if ($this->option('weekly')) {
$filters[] = $this->getFrequencyPattern('weekly');
}
if ($this->option('monthly')) {
$filters[] = $this->getFrequencyPattern('monthly');
}
if ($frequency = $this->option('frequency')) {
$filters[] = '"frequency":"'.preg_quote($frequency, '/').'"';
}
return empty($filters) ? null : implode('|', $filters);
}
private function getFrequencyPattern(string $type): string
{
$patterns = [
'hourly' => [
'0 \* \* \* \*', // 0 * * * *
'@hourly', // @hourly
],
'daily' => [
'0 0 \* \* \*', // 0 0 * * *
'@daily', // @daily
'@midnight', // @midnight
],
'weekly' => [
'0 0 \* \* [0-6]', // 0 0 * * 0-6 (any day of week)
'@weekly', // @weekly
],
'monthly' => [
'0 0 1 \* \*', // 0 0 1 * * (first of month)
'@monthly', // @monthly
],
];
$typePatterns = $patterns[$type] ?? [];
// For grep, we need to match the frequency field in JSON
return '"frequency":"('.implode('|', $typePatterns).')"';
}
private function getFilterDescription(): string
{
$descriptions = [];
if ($taskName = $this->option('task-name')) {
$descriptions[] = "task name: {$taskName}";
}
if ($taskId = $this->option('task-id')) {
$descriptions[] = "task ID: {$taskId}";
}
if ($backupName = $this->option('backup-name')) {
$descriptions[] = "backup name: {$backupName}";
}
if ($backupId = $this->option('backup-id')) {
$descriptions[] = "backup ID: {$backupId}";
}
// Frequency filters
if ($this->option('hourly')) {
$descriptions[] = 'hourly jobs';
}
if ($this->option('daily')) {
$descriptions[] = 'daily jobs';
}
if ($this->option('weekly')) {
$descriptions[] = 'weekly jobs';
}
if ($this->option('monthly')) {
$descriptions[] = 'monthly jobs';
}
if ($frequency = $this->option('frequency')) {
$descriptions[] = "frequency: {$frequency}";
}
return empty($descriptions) ? '' : ' (filtered by '.implode(', ', $descriptions).')';
}
}