Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class TrimInactiveProfileLogs extends Command
|
||||
{
|
||||
protected $signature = 'profiles:trim-logs {--days=30 : Number of days of logs to keep}';
|
||||
|
||||
protected $description = 'Trim inactive profile log files to retain only recent entries';
|
||||
|
||||
/**
|
||||
* Every log written by the scheduler through appendOutputTo().
|
||||
*
|
||||
* appendOutputTo() never rotates - it appends for the life of the server.
|
||||
* Laravel's own daily driver rotates laravel-*.log, but these files sit
|
||||
* outside it, so without this command they grow without bound. On
|
||||
* production wirechat-cleanup.log had reached 4.4 MB from a task that runs
|
||||
* every five minutes, and bounce-processing.log 1.8 MB.
|
||||
*
|
||||
* Keep in step with the appendOutputTo() calls in Console\Kernel.
|
||||
*/
|
||||
/**
|
||||
* Lines kept in a log that carries no timestamps, so it can still be
|
||||
* capped. Roughly a fortnight of a five-minute task at two lines a run.
|
||||
*/
|
||||
private const MAX_UNDATED_LINES = 10000;
|
||||
|
||||
protected $logFiles = [
|
||||
'inactive-profiles.log',
|
||||
'mark-inactive-profiles.log',
|
||||
'mark-incomplete-profiles.log',
|
||||
'permanent-deletions.log',
|
||||
'wirechat-cleanup.log',
|
||||
'bounce-processing.log',
|
||||
'scout-reindex.log',
|
||||
'call-expiry.log',
|
||||
'cert-monitor.log',
|
||||
'ip-cleanup.log',
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$daysToKeep = (int) $this->option('days');
|
||||
$cutoffDate = Carbon::now()->subDays($daysToKeep);
|
||||
|
||||
$this->info("Trimming log files older than {$daysToKeep} days ({$cutoffDate->format('Y-m-d H:i:s')})...");
|
||||
|
||||
$totalSizeBefore = 0;
|
||||
$totalSizeAfter = 0;
|
||||
$filesProcessed = 0;
|
||||
|
||||
foreach ($this->logFiles as $logFileName) {
|
||||
$logPath = storage_path('logs/' . $logFileName);
|
||||
|
||||
if (!File::exists($logPath)) {
|
||||
// Normal: a task that is disabled or has never run writes no
|
||||
// log. Not worth a warning on every monthly run.
|
||||
continue;
|
||||
}
|
||||
|
||||
$sizeBefore = File::size($logPath);
|
||||
$totalSizeBefore += $sizeBefore;
|
||||
|
||||
$trimmed = $this->trimLogFile($logPath, $cutoffDate);
|
||||
|
||||
$sizeAfter = File::size($logPath);
|
||||
$totalSizeAfter += $sizeAfter;
|
||||
$filesProcessed++;
|
||||
|
||||
if ($trimmed) {
|
||||
$savedBytes = $sizeBefore - $sizeAfter;
|
||||
$savedKB = round($savedBytes / 1024, 2);
|
||||
$this->info("✓ {$logFileName}: Trimmed from " . $this->formatFileSize($sizeBefore) . " to " . $this->formatFileSize($sizeAfter) . " (saved {$savedKB} KB)");
|
||||
} else {
|
||||
$this->info("✓ {$logFileName}: No entries older than {$daysToKeep} days (size: " . $this->formatFileSize($sizeBefore) . ")");
|
||||
}
|
||||
}
|
||||
|
||||
if ($filesProcessed > 0) {
|
||||
$totalSaved = $totalSizeBefore - $totalSizeAfter;
|
||||
$this->info("\nTotal: Processed {$filesProcessed} files, saved " . $this->formatFileSize($totalSaved));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim log file to keep only entries newer than cutoff date.
|
||||
*
|
||||
* @param string $logPath
|
||||
* @param Carbon $cutoffDate
|
||||
* @return bool Whether any entries were removed
|
||||
*/
|
||||
protected function trimLogFile($logPath, $cutoffDate)
|
||||
{
|
||||
$content = File::get($logPath);
|
||||
$lines = explode("\n", $content);
|
||||
$keptLines = [];
|
||||
$removedCount = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Skip empty lines
|
||||
if (trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract timestamp from log line format: [YYYY-MM-DD HH:MM:SS] message
|
||||
if (preg_match('/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/', $line, $matches)) {
|
||||
$logDate = Carbon::parse($matches[1]);
|
||||
|
||||
if ($logDate->greaterThanOrEqualTo($cutoffDate)) {
|
||||
$keptLines[] = $line;
|
||||
} else {
|
||||
$removedCount++;
|
||||
}
|
||||
} else {
|
||||
// Keep lines without timestamps: they are usually a
|
||||
// continuation of the timestamped line above (a stack trace,
|
||||
// wrapped output) and dropping them would corrupt the entry.
|
||||
$keptLines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Date-based trimming only works on files whose lines carry a
|
||||
// timestamp. appendOutputTo() writes a command's raw stdout, which
|
||||
// mostly does not ("Deleting expired disappearing messages..."), so
|
||||
// those files would keep every line forever. Fall back to a tail: keep
|
||||
// the most recent lines and drop the rest.
|
||||
if ($removedCount === 0 && count($keptLines) > self::MAX_UNDATED_LINES) {
|
||||
$dated = false;
|
||||
foreach ($keptLines as $line) {
|
||||
if (preg_match('/^\[\d{4}-\d{2}-\d{2} /', $line)) {
|
||||
$dated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dated) {
|
||||
$keptLines = array_slice($keptLines, -self::MAX_UNDATED_LINES);
|
||||
File::put($logPath, implode("\n", $keptLines) . "\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($removedCount > 0) {
|
||||
// Rewrite the log file with only kept lines
|
||||
File::put($logPath, implode("\n", $keptLines) . "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size in human-readable format.
|
||||
*
|
||||
* @param int $bytes
|
||||
* @return string
|
||||
*/
|
||||
protected function formatFileSize($bytes)
|
||||
{
|
||||
if ($bytes >= 1048576) {
|
||||
return round($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
return round($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
return $bytes . ' bytes';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user