Files
michaelschiemer/tests/Integration/Framework/Logging/LoggingInfrastructureTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

508 lines
19 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Logging\Commands\LogHealthCheckCommand;
use App\Framework\Logging\Handlers\RotatingFileHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Console\ConsoleInput;
use App\Framework\Console\ConsoleOutput;
use App\Framework\Console\ExitCode;
/**
* Helper function: Recursively delete directory and all contents
*/
function recursiveDelete(string $dir): void {
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
is_dir($path) ? recursiveDelete($path) : unlink($path);
}
rmdir($dir);
}
/**
* Integration Tests für die gesamte Logging-Infrastruktur
*
* Testet End-to-End Workflows:
* - RotatingFileHandler mit realen Dateien
* - LogHealthCheckCommand Integration
* - Logging-Pipeline mit Rotation und Monitoring
* - Docker-Volume-Interaktion
*/
describe('Logging Infrastructure Integration', function () {
beforeEach(function () {
$this->testDir = sys_get_temp_dir() . '/logging_integration_test_' . uniqid();
mkdir($this->testDir, 0777, true);
mkdir($this->testDir . '/storage/logs', 0777, true);
mkdir($this->testDir . '/storage/logs/app', 0777, true);
mkdir($this->testDir . '/storage/logs/debug', 0777, true);
mkdir($this->testDir . '/storage/logs/security', 0777, true);
});
afterEach(function () {
// Cleanup all test directories and files
recursiveDelete($this->testDir);
});
describe('RotatingFileHandler Integration', function () {
it('handles complete rotation lifecycle with real files', function () {
$logFile = $this->testDir . '/storage/logs/app.log';
// Create handler with very small size limit for testing
$handler = RotatingFileHandler::withSizeRotation(
$logFile,
maxFileSize: Byte::fromBytes(200),
maxFiles: 3,
compress: false // Disable compression for easier testing
);
// Write logs until rotation occurs
for ($i = 0; $i < 10; $i++) {
$record = new LogRecord(
message: "Log entry #{$i} - " . str_repeat('X', 50),
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
// Verify rotation occurred
expect(file_exists($logFile))->toBeTrue();
expect(file_exists($logFile . '.1'))->toBeTrue();
// Verify current log file is within size limit (with some buffer for timestamps)
$currentSize = filesize($logFile);
expect($currentSize)->toBeLessThan(400); // Allow buffer for log formatting
// Verify rotated file exists and contains data
$rotatedContent = file_get_contents($logFile . '.1');
expect($rotatedContent)->not->toBeEmpty();
expect($rotatedContent)->toContain('Log entry');
});
it('integrates daily rotation with file system', function () {
$logFile = $this->testDir . '/storage/logs/daily.log';
// Create old log file from yesterday
file_put_contents($logFile, "Yesterday's log entry\n");
touch($logFile, strtotime('yesterday'));
// Create handler with daily rotation
$handler = RotatingFileHandler::daily($logFile, maxFiles: 7, compress: false);
// Write new log entry
$record = new LogRecord(
message: "Today's log entry",
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
// Verify rotation occurred
expect(file_exists($logFile . '.1'))->toBeTrue();
// Verify old content is in rotated file
$rotatedContent = file_get_contents($logFile . '.1');
expect($rotatedContent)->toContain("Yesterday's log");
// Verify new log only contains today's entry
$currentContent = file_get_contents($logFile);
expect($currentContent)->toContain("Today's log");
expect($currentContent)->not->toContain("Yesterday's log");
});
it('handles production configuration correctly', function () {
$logFile = $this->testDir . '/storage/logs/production.log';
$handler = RotatingFileHandler::production($logFile);
// Verify INFO level logs are handled
$infoRecord = new LogRecord(
message: 'Production info log',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
expect($handler->isHandling($infoRecord))->toBeTrue();
$handler->handle($infoRecord);
expect(file_exists($logFile))->toBeTrue();
$content = file_get_contents($logFile);
expect($content)->toContain('Production info log');
// Verify DEBUG logs are NOT handled by production handler
$debugRecord = new LogRecord(
message: 'Debug log should not appear',
context: LogContext::empty(),
level: LogLevel::DEBUG,
timestamp: new DateTimeImmutable()
);
expect($handler->isHandling($debugRecord))->toBeFalse();
});
it('maintains rotation across multiple log files in subdirectories', function () {
$appLog = $this->testDir . '/storage/logs/app/application.log';
$debugLog = $this->testDir . '/storage/logs/debug/debug.log';
$securityLog = $this->testDir . '/storage/logs/security/security.log';
// Create handlers for different log types
$appHandler = RotatingFileHandler::withSizeRotation($appLog, maxFileSize: Byte::fromBytes(100));
$debugHandler = RotatingFileHandler::withSizeRotation($debugLog, maxFileSize: Byte::fromBytes(100));
$securityHandler = RotatingFileHandler::withSizeRotation($securityLog, maxFileSize: Byte::fromBytes(100));
// Write logs to all handlers
for ($i = 0; $i < 5; $i++) {
$record = new LogRecord(
message: str_repeat('X', 30),
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$appHandler->handle($record);
$debugHandler->handle($record);
$securityHandler->handle($record);
}
// Verify all logs exist
expect(file_exists($appLog))->toBeTrue();
expect(file_exists($debugLog))->toBeTrue();
expect(file_exists($securityLog))->toBeTrue();
// At least one should have triggered rotation
$rotationOccurred = file_exists($appLog . '.1')
|| file_exists($debugLog . '.1')
|| file_exists($securityLog . '.1');
expect($rotationOccurred)->toBeTrue();
});
});
describe('LogHealthCheckCommand Integration', function () {
it('performs complete health check on log infrastructure', function () {
// Save current directory and change to test directory
$originalDir = getcwd();
chdir($this->testDir);
try {
// Create command
$command = new LogHealthCheckCommand();
// Create input without options
$output = new ConsoleOutput();
$input = new ConsoleInput(['test'], $output, null);
// Capture output
ob_start();
$exitCode = $command->execute($input);
$output = ob_get_clean();
// Verify all checks were performed
expect($output)->toContain('Checking log directories');
expect($output)->toContain('Checking write permissions');
expect($output)->toContain('Checking disk space');
expect($output)->toContain('Checking log files');
expect($output)->toContain('Summary');
// Should pass all checks (directories exist, permissions OK, disk space OK, no large files)
expect($exitCode)->toBe(ExitCode::SUCCESS);
expect($output)->toContain('All checks passed');
} finally {
// Restore original directory
chdir($originalDir);
}
});
it('detects missing directories and can auto-fix with --fix-permissions', function () {
// Remove debug directory
rmdir($this->testDir . '/storage/logs/debug');
$originalDir = getcwd();
chdir($this->testDir);
try {
// First run without fix - should fail
$command1 = new LogHealthCheckCommand();
$output = new ConsoleOutput();
$input1 = new ConsoleInput(['test'], $output, null);
ob_start();
$exitCode1 = $command1->execute($input1);
$output1 = ob_get_clean();
expect($exitCode1)->toBe(ExitCode::GENERAL_ERROR);
expect($output1)->toContain('(missing)');
// Second run with --fix-permissions
$command2 = new LogHealthCheckCommand();
$output2 = new ConsoleOutput();
$input2 = new ConsoleInput(['test', '--fix-permissions'], $output2, null);
ob_start();
$exitCode2 = $command2->execute($input2);
$output2 = ob_get_clean();
// Should pass after fix
expect($exitCode2)->toBe(ExitCode::SUCCESS);
expect(is_dir($this->testDir . '/storage/logs/debug'))->toBeTrue();
} finally {
chdir($originalDir);
}
});
it('reports detailed information with --detailed flag', function () {
// Create some log files
file_put_contents($this->testDir . '/storage/logs/test1.log', str_repeat('X', 1000));
file_put_contents($this->testDir . '/storage/logs/test2.log', str_repeat('Y', 2000));
$originalDir = getcwd();
chdir($this->testDir);
try {
$command = new LogHealthCheckCommand();
$output = new ConsoleOutput();
$input = new ConsoleInput(['test', '--detailed'], $output, null);
ob_start();
$command->execute($input);
$output = ob_get_clean();
// Verify detailed output shows file information
expect($output)->toContain('test1.log');
expect($output)->toContain('test2.log');
expect($output)->toContain('Size:');
expect($output)->toContain('Modified:');
} finally {
chdir($originalDir);
}
});
});
describe('Full Logging Pipeline Integration', function () {
it('completes full workflow: write logs -> rotate -> health check', function () {
$logFile = $this->testDir . '/storage/logs/app/workflow.log';
// Step 1: Write logs with rotation
$handler = RotatingFileHandler::withSizeRotation(
$logFile,
maxFileSize: Byte::fromBytes(500),
maxFiles: 3,
compress: false
);
for ($i = 0; $i < 20; $i++) {
$record = new LogRecord(
message: "Workflow log entry #{$i} - " . str_repeat('X', 50),
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
// Verify logs and rotation
expect(file_exists($logFile))->toBeTrue();
expect(file_exists($logFile . '.1'))->toBeTrue();
// Step 2: Run health check
$originalDir = getcwd();
chdir($this->testDir);
try {
$command = new LogHealthCheckCommand();
$output = new ConsoleOutput();
$input = new ConsoleInput(['test', '--detailed'], $output, null);
ob_start();
$exitCode = $command->execute($input);
$healthOutput = ob_get_clean();
// Verify health check passes
expect($exitCode)->toBe(ExitCode::SUCCESS);
expect($healthOutput)->toContain('log file(s)'); // At least 2 files (workflow.log + workflow.log.1)
expect($healthOutput)->toContain('All checks passed');
} finally {
chdir($originalDir);
}
});
it('handles concurrent logging with multiple handlers and health monitoring', function () {
$appLog = $this->testDir . '/storage/logs/app/app.log';
$debugLog = $this->testDir . '/storage/logs/debug/debug.log';
$securityLog = $this->testDir . '/storage/logs/security/security.log';
// Create multiple handlers
$handlers = [
RotatingFileHandler::production($appLog),
RotatingFileHandler::withSizeRotation($debugLog, maxFileSize: Byte::fromKilobytes(1)),
RotatingFileHandler::daily($securityLog),
];
// Write logs concurrently to all handlers
foreach ($handlers as $handler) {
for ($i = 0; $i < 10; $i++) {
$record = new LogRecord(
message: "Concurrent log entry #{$i}",
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
}
// Verify all logs exist
expect(file_exists($appLog))->toBeTrue();
expect(file_exists($debugLog))->toBeTrue();
expect(file_exists($securityLog))->toBeTrue();
// Run health check on entire infrastructure
$originalDir = getcwd();
chdir($this->testDir);
try {
$command = new LogHealthCheckCommand();
$output = new ConsoleOutput();
$input = new ConsoleInput(['test'], $output, null);
ob_start();
$exitCode = $command->execute($input);
$healthOutput = ob_get_clean();
// Should pass all checks
expect($exitCode)->toBe(ExitCode::SUCCESS);
expect($healthOutput)->toContain('log file(s)'); // At least 3 log files
} finally {
chdir($originalDir);
}
});
});
describe('Performance and Scalability Integration', function () {
it('handles high-volume logging efficiently', function () {
$logFile = $this->testDir . '/storage/logs/performance.log';
$handler = RotatingFileHandler::withSizeRotation(
$logFile,
maxFileSize: Byte::fromKilobytes(10),
maxFiles: 5
);
$startTime = microtime(true);
// Write 1000 log entries
for ($i = 0; $i < 1000; $i++) {
$record = new LogRecord(
message: "Performance test entry #{$i}",
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
// Should complete in reasonable time (< 5 seconds for 1000 entries)
expect($executionTime)->toBeLessThan(5.0);
// Verify logs were written and rotated
expect(file_exists($logFile))->toBeTrue();
// Count rotation files (should have multiple due to small size limit)
$rotationCount = 0;
for ($i = 1; $i <= 5; $i++) {
if (file_exists($logFile . '.' . $i)) {
$rotationCount++;
}
}
expect($rotationCount)->toBeGreaterThan(0);
});
it('maintains performance with health checks on large log directories', function () {
// Create many small log files
for ($i = 0; $i < 50; $i++) {
file_put_contents(
$this->testDir . "/storage/logs/file{$i}.log",
str_repeat('X', 100)
);
}
$originalDir = getcwd();
chdir($this->testDir);
try {
$command = new LogHealthCheckCommand();
$output = new ConsoleOutput();
$input = new ConsoleInput(['test', '--detailed'], $output, null);
$startTime = microtime(true);
ob_start();
$exitCode = $command->execute($input);
$healthOutput = ob_get_clean();
$endTime = microtime(true);
$executionTime = $endTime - $startTime;
// Should complete quickly even with 50 files (< 2 seconds)
expect($executionTime)->toBeLessThan(2.0);
expect($exitCode)->toBe(ExitCode::SUCCESS);
expect($healthOutput)->toContain('Found 50 log file(s)');
} finally {
chdir($originalDir);
}
});
});
describe('Docker Volume Integration (Simulated)', function () {
it('works with host-mounted log directories', function () {
// Simulate host mount scenario with correct permissions
$mountedLogDir = $this->testDir . '/storage/logs';
chmod($mountedLogDir, 0777);
$logFile = $mountedLogDir . '/docker-test.log';
$handler = RotatingFileHandler::production($logFile);
// Write logs as if running in Docker container
for ($i = 0; $i < 5; $i++) {
$record = new LogRecord(
message: "Docker container log #{$i}",
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
// Verify logs are accessible from "host" (same directory in this test)
expect(file_exists($logFile))->toBeTrue();
expect(is_readable($logFile))->toBeTrue();
$content = file_get_contents($logFile);
expect($content)->toContain('Docker container log');
});
});
});