- 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.
140 lines
4.7 KiB
PHP
140 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Framework\Logging;
|
|
|
|
use App\Framework\Logging\Aggregation\AggregationConfig;
|
|
use App\Framework\Logging\Handlers\AggregatingLogHandler;
|
|
use App\Framework\Logging\LogHandler;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class AggregatingLogHandlerTest extends TestCase
|
|
{
|
|
public function test_aggregates_identical_messages(): void
|
|
{
|
|
$inner = $this->createMock(LogHandler::class);
|
|
|
|
$config = new AggregationConfig(
|
|
flushIntervalSeconds: 60,
|
|
minOccurrencesForAggregation: 2
|
|
);
|
|
|
|
$handler = new AggregatingLogHandler($inner, $config);
|
|
|
|
// Keine Ausgabe bis flush
|
|
$inner->expects($this->never())->method('handle');
|
|
|
|
// Sende 5 identische Messages
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$handler->handle($this->createLogRecord('Identical error message', LogLevel::INFO));
|
|
}
|
|
|
|
$this->assertEquals(5, $handler->getTotalProcessed());
|
|
$this->assertEquals(4, $handler->getTotalAggregated()); // Erste nicht aggregiert
|
|
}
|
|
|
|
public function test_normalizes_messages_with_variables(): void
|
|
{
|
|
$inner = $this->createMock(LogHandler::class);
|
|
|
|
$config = AggregationConfig::production();
|
|
$handler = new AggregatingLogHandler($inner, $config);
|
|
|
|
// Diese Messages sollten als identisch erkannt werden
|
|
$handler->handle($this->createLogRecord('User 123 logged in', LogLevel::INFO));
|
|
$handler->handle($this->createLogRecord('User 456 logged in', LogLevel::INFO));
|
|
$handler->handle($this->createLogRecord('User 789 logged in', LogLevel::INFO));
|
|
|
|
// Sollten aggregiert werden
|
|
$this->assertEquals(1, $handler->getAggregatedCount());
|
|
}
|
|
|
|
public function test_does_not_aggregate_critical_levels(): void
|
|
{
|
|
$inner = $this->createMock(LogHandler::class);
|
|
|
|
$config = AggregationConfig::production(); // Aggregiert nur INFO, NOTICE, WARNING
|
|
$handler = new AggregatingLogHandler($inner, $config);
|
|
|
|
// ERROR sollte direkt durchgereicht werden
|
|
$inner->expects($this->once())->method('handle');
|
|
|
|
$handler->handle($this->createLogRecord('Critical error', LogLevel::ERROR));
|
|
}
|
|
|
|
public function test_flushes_on_interval(): void
|
|
{
|
|
$inner = $this->createMock(LogHandler::class);
|
|
|
|
$config = new AggregationConfig(
|
|
flushIntervalSeconds: 0, // Sofortiger Flush für Test
|
|
minOccurrencesForAggregation: 2
|
|
);
|
|
|
|
$handler = new AggregatingLogHandler($inner, $config);
|
|
|
|
$handler->handle($this->createLogRecord('Test', LogLevel::INFO));
|
|
$handler->handle($this->createLogRecord('Test', LogLevel::INFO));
|
|
|
|
// Trigger flush durch neuen Handle
|
|
$inner->expects($this->atLeastOnce())->method('handle');
|
|
$handler->handle($this->createLogRecord('Another', LogLevel::INFO));
|
|
}
|
|
|
|
public function test_outputs_aggregation_summary(): void
|
|
{
|
|
$actualRecord = null;
|
|
$inner = $this->createMock(LogHandler::class);
|
|
$inner->method('handle')->willReturnCallback(function($record) use (&$actualRecord) {
|
|
$actualRecord = $record;
|
|
});
|
|
|
|
$config = new AggregationConfig(
|
|
minOccurrencesForAggregation: 2
|
|
);
|
|
|
|
$handler = new AggregatingLogHandler($inner, $config);
|
|
|
|
// Sende mehrere identische Messages
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$handler->handle($this->createLogRecord('Database query failed', LogLevel::WARNING));
|
|
}
|
|
|
|
$handler->flush();
|
|
|
|
$this->assertNotNull($actualRecord);
|
|
$this->assertStringContainsString('occurred', $actualRecord->message);
|
|
$this->assertArrayHasKey('aggregated', $actualRecord->context->structured);
|
|
$this->assertEquals(5, $actualRecord->context->structured['occurrence_count']);
|
|
}
|
|
|
|
public function test_health_check(): void
|
|
{
|
|
$inner = $this->createMock(LogHandler::class);
|
|
$handler = new AggregatingLogHandler($inner);
|
|
|
|
$health = $handler->check();
|
|
|
|
$this->assertEquals(\App\Framework\Health\HealthStatus::HEALTHY, $health->status);
|
|
$this->assertArrayHasKey('total_processed', $health->details);
|
|
$this->assertArrayHasKey('aggregation_rate', $health->details);
|
|
}
|
|
|
|
private function createLogRecord(
|
|
string $message = 'test',
|
|
LogLevel $level = LogLevel::INFO
|
|
): LogRecord {
|
|
return new LogRecord(
|
|
level: $level,
|
|
message: $message,
|
|
channel: 'test',
|
|
context: LogContext::empty(),
|
|
timestamp: new \DateTimeImmutable()
|
|
);
|
|
}
|
|
}
|