timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin')); $this->context = LogContext::withData(['test' => 'data']); }); it('only handles records in CLI mode', function () { // ConsoleHandler should only work in CLI mode expect(PHP_SAPI)->toBe('cli'); $handler = new ConsoleHandler(); $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // In CLI mode, should handle the record expect($handler->isHandling($record))->toBeTrue(); }); it('respects minimum level configuration', function () { $handler = new ConsoleHandler(minLevel: LogLevel::WARNING); $debugRecord = new LogRecord( message: 'Debug', context: $this->context, level: LogLevel::DEBUG, timestamp: $this->timestamp ); $infoRecord = new LogRecord( message: 'Info', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $warningRecord = new LogRecord( message: 'Warning', context: $this->context, level: LogLevel::WARNING, timestamp: $this->timestamp ); $errorRecord = new LogRecord( message: 'Error', context: $this->context, level: LogLevel::ERROR, timestamp: $this->timestamp ); expect($handler->isHandling($debugRecord))->toBeFalse(); expect($handler->isHandling($infoRecord))->toBeFalse(); expect($handler->isHandling($warningRecord))->toBeTrue(); expect($handler->isHandling($errorRecord))->toBeTrue(); }); it('respects debug only mode when APP_DEBUG is not set', function () { // Save original value $originalDebug = getenv('APP_DEBUG'); // Test with APP_DEBUG = false putenv('APP_DEBUG=false'); $handler = new ConsoleHandler(debugOnly: true); $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($handler->isHandling($record))->toBeFalse(); // Test with APP_DEBUG = true putenv('APP_DEBUG=true'); expect($handler->isHandling($record))->toBeTrue(); // Test with debugOnly = false (should always handle) putenv('APP_DEBUG=false'); $handler = new ConsoleHandler(debugOnly: false); expect($handler->isHandling($record))->toBeTrue(); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); it('can change minimum level after creation', function () { $handler = new ConsoleHandler(minLevel: LogLevel::DEBUG); $infoRecord = new LogRecord( message: 'Info', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($handler->isHandling($infoRecord))->toBeTrue(); $handler->setMinLevel(LogLevel::WARNING); expect($handler->isHandling($infoRecord))->toBeFalse(); }); it('can change output format', function () { $handler = new ConsoleHandler(); $originalFormat = '{color}[{level_name}]{reset} {timestamp} {request_id}{message}{structured}'; $newFormat = '{level_name}: {message}'; $handler->setOutputFormat($newFormat); // Note: We can't easily test the actual output without mocking file_put_contents or echo, // but we can verify the method returns the handler for fluent interface expect($handler->setOutputFormat($newFormat))->toBe($handler); }); it('handles output correctly using stdout and stderr', function () { // Save original APP_DEBUG value $originalDebug = getenv('APP_DEBUG'); putenv('APP_DEBUG=true'); $handler = new ConsoleHandler(stderrLevel: LogLevel::WARNING); // Test that lower levels would go to stdout (DEBUG, INFO, NOTICE) $infoRecord = new LogRecord( message: 'Info message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Test that higher levels would go to stderr (WARNING and above) $errorRecord = new LogRecord( message: 'Error message', context: $this->context, level: LogLevel::ERROR, timestamp: $this->timestamp ); // We can verify the handler processes these records expect($handler->isHandling($infoRecord))->toBeTrue(); expect($handler->isHandling($errorRecord))->toBeTrue(); // Capture output ob_start(); $handler->handle($infoRecord); $stdoutOutput = ob_get_clean(); // For stderr, we would need to redirect stderr to test it properly // This is complex in PHPUnit/Pest, so we just verify it handles the record ob_start(); $stderrBefore = ob_get_clean(); $handler->handle($errorRecord); // The handler should have processed both records expect($handler->isHandling($infoRecord))->toBeTrue(); expect($handler->isHandling($errorRecord))->toBeTrue(); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); it('formats records with extra data correctly', function () { // Save original APP_DEBUG value $originalDebug = getenv('APP_DEBUG'); putenv('APP_DEBUG=true'); $handler = new ConsoleHandler(); $record = new LogRecord( message: 'Test with extras', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Add various types of extra data $record->addExtra('request_id', 'req-123'); $record->addExtra('structured_tags', ['important', 'audit']); $record->addExtra('trace_context', [ 'trace_id' => 'trace-abc-def-123', 'active_span' => ['spanId' => 'span-456-789'], ]); $record->addExtra('user_context', [ 'user_id' => 'user-999', 'is_authenticated' => true, ]); $record->addExtra('request_context', [ 'request_method' => 'POST', 'request_uri' => '/api/users/create', ]); // The handler should process this record expect($handler->isHandling($record))->toBeTrue(); // Capture the output ob_start(); $handler->handle($record); $output = ob_get_clean(); // The output should contain the message expect($output)->toContain('Test with extras'); // It should contain the request_id expect($output)->toContain('req-123'); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); it('handles records with channel information', function () { // Save original APP_DEBUG value $originalDebug = getenv('APP_DEBUG'); putenv('APP_DEBUG=true'); $handler = new ConsoleHandler( outputFormat: '{channel}{level_name}: {message}' ); $record = new LogRecord( message: 'Database connection established', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp, channel: 'database' ); expect($handler->isHandling($record))->toBeTrue(); // Capture output ob_start(); $handler->handle($record); $output = ob_get_clean(); // The output should contain the channel expect($output)->toContain('[database]'); expect($output)->toContain('Database connection established'); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); it('applies correct colors for different log levels', function () { // Save original APP_DEBUG value $originalDebug = getenv('APP_DEBUG'); putenv('APP_DEBUG=true'); $handler = new ConsoleHandler(); $levels = [ LogLevel::DEBUG, LogLevel::INFO, LogLevel::NOTICE, LogLevel::WARNING, LogLevel::ERROR, LogLevel::CRITICAL, LogLevel::ALERT, LogLevel::EMERGENCY, ]; foreach ($levels as $level) { $record = new LogRecord( message: "{$level->getName()} message", context: $this->context, level: $level, timestamp: $this->timestamp ); ob_start(); $handler->handle($record); $output = ob_get_clean(); // Each level should have its color code in the output $expectedColor = $level->getConsoleColor()->value; expect($output)->toContain($expectedColor); expect($output)->toContain("{$level->getName()} message"); } // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } });