timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin')); $this->context = LogContext::withData(['test' => 'data']); }); it('handles records in both CLI and web mode', function () { // ConsoleHandler now works in both CLI and web mode $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: false); $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Should handle the record regardless of SAPI mode $result = $handler->isHandling($record); expect($result)->toBe(true); }); it('respects minimum level configuration', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, minLevel: LogLevel::WARNING, debugOnly: false); $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 ); $debugResult = $handler->isHandling($debugRecord); expect($debugResult)->toBe(false); $infoResult = $handler->isHandling($infoRecord); expect($infoResult)->toBe(false); $warningResult = $handler->isHandling($warningRecord); expect($warningResult)->toBe(true); $errorResult = $handler->isHandling($errorRecord); expect($errorResult)->toBe(true); }); 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'); $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: true); $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $result1 = $handler->isHandling($record); expect($result1)->toBe(false); // Test with APP_DEBUG = true putenv('APP_DEBUG=true'); $result2 = $handler->isHandling($record); expect($result2)->toBe(true); // Test with debugOnly = false (should always handle) putenv('APP_DEBUG=false'); $handler = new ConsoleHandler($formatter, debugOnly: false); $result3 = $handler->isHandling($record); expect($result3)->toBe(true); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); it('can change minimum level after creation', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, minLevel: LogLevel::DEBUG, debugOnly: false); $infoRecord = new LogRecord( message: 'Info', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $result1 = $handler->isHandling($infoRecord); expect($result1)->toBe(true); $handler->setMinLevel(LogLevel::WARNING); $result2 = $handler->isHandling($infoRecord); expect($result2)->toBe(false); }); it('uses formatter for output', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: false); $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Verify handler processes records expect($handler->isHandling($record))->toBe(true); }); it('handles output correctly using stdout and stderr in CLI mode', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, stderrLevel: LogLevel::WARNING, debugOnly: false); // 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 $result1 = $handler->isHandling($infoRecord); expect($result1)->toBe(true); $result2 = $handler->isHandling($errorRecord); expect($result2)->toBe(true); // 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 $result3 = $handler->isHandling($infoRecord); expect($result3)->toBe(true); $result4 = $handler->isHandling($errorRecord); expect($result4)->toBe(true); }); it('formats records with extra data correctly', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: false); $record = new LogRecord( message: 'Test with extras', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Add various types of extra data $record = $record->withExtra('request_id', 'req-123') ->withExtra('structured_tags', ['important', 'audit']) ->withExtra('trace_context', [ 'trace_id' => 'trace-abc-def-123', 'active_span' => ['spanId' => 'span-456-789'], ]) ->withExtra('user_context', [ 'user_id' => 'user-999', 'is_authenticated' => true, ]) ->withExtra('request_context', [ 'request_method' => 'POST', 'request_uri' => '/api/users/create', ]); // The handler should process this record $result = $handler->isHandling($record); expect($result)->toBe(true); // 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'); }); it('handles records with channel information', function () { $formatter = new LineFormatter(format: '{channel}{level_name}: {message}'); $handler = new ConsoleHandler($formatter, debugOnly: false); $record = new LogRecord( message: 'Database connection established', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp, channel: 'database' ); $result = $handler->isHandling($record); expect($result)->toBe(true); // 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'); }); it('applies correct colors for stdout log levels in CLI mode', function () { $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: false); // Only test stdout levels (DEBUG, INFO, NOTICE) // WARNING and above go to stderr and cannot be captured with ob_start() $levels = [ LogLevel::DEBUG, LogLevel::INFO, LogLevel::NOTICE, ]; 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"); } }); it('uses stderr for all logs in web mode', function () { // This test verifies that in web mode, all logs go to stderr // We can't easily mock PHP_SAPI, but we can verify the logic exists $formatter = new LineFormatter(); $handler = new ConsoleHandler($formatter, debugOnly: false); $record = new LogRecord( message: 'Web request log', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Handler should process records expect($handler->isHandling($record))->toBe(true); // Note: Actual stderr/stdout routing based on PHP_SAPI is tested at runtime // This test ensures the handler works in both modes });