- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
317 lines
9.0 KiB
PHP
317 lines
9.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Framework\Logging\Handlers;
|
|
|
|
use App\Framework\Logging\Handlers\ConsoleHandler;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
|
|
beforeEach(function () {
|
|
$this->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');
|
|
}
|
|
});
|