Files
michaelschiemer/src/Framework/Process/Console/LogCommands.php
Michael Schiemer 95147ff23e refactor(deployment): Remove WireGuard VPN dependency and restore public service access
Remove WireGuard integration from production deployment to simplify infrastructure:
- Remove docker-compose-direct-access.yml (VPN-bound services)
- Remove VPN-only middlewares from Grafana, Prometheus, Portainer
- Remove WireGuard middleware definitions from Traefik
- Remove WireGuard IPs (10.8.0.0/24) from Traefik forwarded headers

All monitoring services now publicly accessible via subdomains:
- grafana.michaelschiemer.de (with Grafana native auth)
- prometheus.michaelschiemer.de (with Basic Auth)
- portainer.michaelschiemer.de (with Portainer native auth)

All services use Let's Encrypt SSL certificates via Traefik.
2025-11-05 12:48:25 +01:00

292 lines
11 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Framework\Process\Console;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ExitCode;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Process\Services\LogAnalysisService;
/**
* Log Console Commands.
*/
final readonly class LogCommands
{
public function __construct(
private LogAnalysisService $logAnalysis
) {
}
#[ConsoleCommand('log:tail', 'Display last N lines of a log file')]
public function tail(ConsoleInput $input): int
{
$filePath = $input->getArgument('file');
if ($filePath === null) {
echo "❌ Please provide a log file path.\n";
echo "Usage: php console.php log:tail <file> [--lines=100]\n";
return ExitCode::FAILURE;
}
try {
$file = FilePath::create($filePath);
} catch (\InvalidArgumentException $e) {
echo "❌ Invalid file path: {$filePath}\n";
echo "Error: {$e->getMessage()}\n";
return ExitCode::FAILURE;
}
if (! $file->exists() || ! $file->isFile()) {
echo "❌ File does not exist or is not a file: {$filePath}\n";
return ExitCode::FAILURE;
}
$lines = (int) ($input->getOption('lines') ?? 100);
echo "Showing last {$lines} lines of: {$file->toString()}\n\n";
echo "--- LOG OUTPUT ---\n\n";
$output = $this->logAnalysis->tail($file, $lines);
echo $output;
if (empty($output)) {
echo "(No content)\n";
}
return ExitCode::SUCCESS;
}
#[ConsoleCommand('log:errors', 'Find errors in a log file')]
public function errors(ConsoleInput $input): int
{
$filePath = $input->getArgument('file');
if ($filePath === null) {
echo "❌ Please provide a log file path.\n";
echo "Usage: php console.php log:errors <file> [--lines=1000]\n";
return ExitCode::FAILURE;
}
try {
$file = FilePath::create($filePath);
} catch (\InvalidArgumentException $e) {
echo "❌ Invalid file path: {$filePath}\n";
echo "Error: {$e->getMessage()}\n";
return ExitCode::FAILURE;
}
if (! $file->exists() || ! $file->isFile()) {
echo "❌ File does not exist or is not a file: {$filePath}\n";
return ExitCode::FAILURE;
}
$lines = (int) ($input->getOption('lines') ?? 1000);
echo "Searching for errors in: {$file->toString()} (last {$lines} lines)...\n\n";
$result = $this->logAnalysis->findErrors($file, $lines);
if (empty($result->entries)) {
echo "✅ No errors found!\n";
return ExitCode::SUCCESS;
}
echo "┌─ ERRORS FOUND ──────────────────────────────────────────┐\n";
echo "│ Total Errors: {$result->getErrorCount()}\n";
echo "│ Total Lines: {$result->totalLines}\n";
echo "└─────────────────────────────────────────────────────────┘\n\n";
echo "Error Entries:\n";
foreach ($result->entries as $entry) {
$timestamp = $entry->timestamp?->format('Y-m-d H:i:s') ?? 'N/A';
echo " [{$timestamp}] {$entry->level}: {$entry->message}\n";
}
return ExitCode::SUCCESS;
}
#[ConsoleCommand('log:warnings', 'Find warnings in a log file')]
public function warnings(ConsoleInput $input): int
{
$filePath = $input->getArgument('file');
if ($filePath === null) {
echo "❌ Please provide a log file path.\n";
echo "Usage: php console.php log:warnings <file> [--lines=1000]\n";
return ExitCode::FAILURE;
}
try {
$file = FilePath::create($filePath);
} catch (\InvalidArgumentException $e) {
echo "❌ Invalid file path: {$filePath}\n";
echo "Error: {$e->getMessage()}\n";
return ExitCode::FAILURE;
}
if (! $file->exists() || ! $file->isFile()) {
echo "❌ File does not exist or is not a file: {$filePath}\n";
return ExitCode::FAILURE;
}
$lines = (int) ($input->getOption('lines') ?? 1000);
echo "Searching for warnings in: {$file->toString()} (last {$lines} lines)...\n\n";
$result = $this->logAnalysis->findWarnings($file, $lines);
if (empty($result->entries)) {
echo "✅ No warnings found!\n";
return ExitCode::SUCCESS;
}
echo "┌─ WARNINGS FOUND ────────────────────────────────────────┐\n";
echo "│ Total Warnings: {$result->getWarningCount()}\n";
echo "│ Total Lines: {$result->totalLines}\n";
echo "└─────────────────────────────────────────────────────────┘\n\n";
echo "Warning Entries:\n";
foreach ($result->entries as $entry) {
$timestamp = $entry->timestamp?->format('Y-m-d H:i:s') ?? 'N/A';
echo " [{$timestamp}] {$entry->level}: {$entry->message}\n";
}
return ExitCode::SUCCESS;
}
#[ConsoleCommand('log:search', 'Search for a pattern in a log file')]
public function search(ConsoleInput $input): int
{
$filePath = $input->getArgument('file');
$pattern = $input->getArgument('pattern');
if ($filePath === null || $pattern === null) {
echo "❌ Please provide a log file path and search pattern.\n";
echo "Usage: php console.php log:search <file> <pattern> [--lines=1000]\n";
return ExitCode::FAILURE;
}
try {
$file = FilePath::create($filePath);
} catch (\InvalidArgumentException $e) {
echo "❌ Invalid file path: {$filePath}\n";
echo "Error: {$e->getMessage()}\n";
return ExitCode::FAILURE;
}
if (! $file->exists() || ! $file->isFile()) {
echo "❌ File does not exist or is not a file: {$filePath}\n";
return ExitCode::FAILURE;
}
$lines = (int) ($input->getOption('lines') ?? 1000);
echo "Searching for '{$pattern}' in: {$file->toString()} (last {$lines} lines)...\n\n";
$result = $this->logAnalysis->search($file, $pattern, $lines);
if (empty($result->entries)) {
echo " No matches found for pattern: {$pattern}\n";
return ExitCode::SUCCESS;
}
echo "┌─ SEARCH RESULTS ────────────────────────────────────────┐\n";
echo "│ Pattern: {$pattern}\n";
echo "│ Matches: " . count($result->entries) . "\n";
echo "│ Total Lines: {$result->totalLines}\n";
echo "└─────────────────────────────────────────────────────────┘\n\n";
echo "Matching Entries:\n";
foreach ($result->entries as $entry) {
$timestamp = $entry->timestamp?->format('Y-m-d H:i:s') ?? 'N/A';
echo " [{$timestamp}] {$entry->level}: {$entry->message}\n";
}
return ExitCode::SUCCESS;
}
#[ConsoleCommand('log:stats', 'Show statistics for a log file')]
public function stats(ConsoleInput $input): int
{
$filePath = $input->getArgument('file');
if ($filePath === null) {
echo "❌ Please provide a log file path.\n";
echo "Usage: php console.php log:stats <file> [--lines=1000]\n";
return ExitCode::FAILURE;
}
try {
$file = FilePath::create($filePath);
} catch (\InvalidArgumentException $e) {
echo "❌ Invalid file path: {$filePath}\n";
echo "Error: {$e->getMessage()}\n";
return ExitCode::FAILURE;
}
if (! $file->exists() || ! $file->isFile()) {
echo "❌ File does not exist or is not a file: {$filePath}\n";
return ExitCode::FAILURE;
}
$lines = (int) ($input->getOption('lines') ?? 1000);
echo "Analyzing log file: {$file->toString()} (last {$lines} lines)...\n\n";
$stats = $this->logAnalysis->getStatistics($file, $lines);
echo "╔════════════════════════════════════════════════════════════╗\n";
echo "║ LOG STATISTICS ║\n";
echo "╚════════════════════════════════════════════════════════════╝\n\n";
echo "┌─ OVERVIEW ──────────────────────────────────────────────┐\n";
echo "│ Total Lines: {$stats['total_lines']}\n";
echo "│ Errors: {$stats['error_count']}\n";
echo "│ Warnings: {$stats['warning_count']}\n";
echo "└─────────────────────────────────────────────────────────┘\n\n";
if (! empty($stats['level_distribution'])) {
echo "┌─ LEVEL DISTRIBUTION ───────────────────────────────────┐\n";
foreach ($stats['level_distribution'] as $level => $count) {
echo "{$level}: {$count}\n";
}
echo "└─────────────────────────────────────────────────────────┘\n\n";
}
if (! empty($stats['top_errors'])) {
echo "┌─ TOP ERRORS ──────────────────────────────────────────┐\n";
$rank = 1;
foreach ($stats['top_errors'] as $message => $count) {
echo "{$rank}. ({$count}x) {$message}\n";
$rank++;
}
echo "└─────────────────────────────────────────────────────────┘\n";
}
return ExitCode::SUCCESS;
}
}