- 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.
260 lines
10 KiB
PHP
260 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Logging\DefaultChannelLogger;
|
|
use App\Framework\Logging\HasChannel;
|
|
use App\Framework\Logging\LogChannel;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\SupportsChannels;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
|
|
describe('DefaultChannelLogger', function () {
|
|
beforeEach(function () {
|
|
// Create a test logger that records calls
|
|
$this->logCalls = [];
|
|
$this->testLogger = new class($this->logCalls) implements Logger, SupportsChannels {
|
|
public function __construct(private array &$logCalls)
|
|
{
|
|
}
|
|
|
|
public function debug(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::DEBUG, $message, $context);
|
|
}
|
|
|
|
public function info(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::INFO, $message, $context);
|
|
}
|
|
|
|
public function notice(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::NOTICE, $message, $context);
|
|
}
|
|
|
|
public function warning(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::WARNING, $message, $context);
|
|
}
|
|
|
|
public function error(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::ERROR, $message, $context);
|
|
}
|
|
|
|
public function critical(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::CRITICAL, $message, $context);
|
|
}
|
|
|
|
public function alert(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::ALERT, $message, $context);
|
|
}
|
|
|
|
public function emergency(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::EMERGENCY, $message, $context);
|
|
}
|
|
|
|
public function log(LogLevel $level, string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, $level, $message, $context);
|
|
}
|
|
|
|
public function logToChannel(
|
|
LogChannel $channel,
|
|
LogLevel $level,
|
|
string $message,
|
|
?LogContext $context = null
|
|
): void {
|
|
$this->logCalls[] = [
|
|
'channel' => $channel,
|
|
'level' => $level,
|
|
'message' => $message,
|
|
'context' => $context
|
|
];
|
|
}
|
|
|
|
public function channel(LogChannel|string $channel): Logger&HasChannel
|
|
{
|
|
$logChannel = $channel instanceof LogChannel ? $channel : LogChannel::from($channel);
|
|
return new DefaultChannelLogger($this, $logChannel);
|
|
}
|
|
};
|
|
|
|
$this->channelLogger = new DefaultChannelLogger($this->testLogger, LogChannel::SECURITY);
|
|
});
|
|
|
|
describe('interface implementation', function () {
|
|
it('implements Logger and HasChannel interfaces', function () {
|
|
expect($this->channelLogger)->toBeInstanceOf(Logger::class);
|
|
expect($this->channelLogger)->toBeInstanceOf(HasChannel::class);
|
|
});
|
|
|
|
it('has correct channel property', function () {
|
|
expect($this->channelLogger->channel)->toBe(LogChannel::SECURITY);
|
|
});
|
|
});
|
|
|
|
describe('log level delegation', function () {
|
|
it('delegates debug to parent logger', function () {
|
|
$this->channelLogger->debug('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['channel'])->toBe(LogChannel::SECURITY);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::DEBUG);
|
|
expect($this->logCalls[0]['message'])->toBe('Test message');
|
|
});
|
|
|
|
it('delegates info to parent logger', function () {
|
|
$this->channelLogger->info('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::INFO);
|
|
});
|
|
|
|
it('delegates warning to parent logger', function () {
|
|
$this->channelLogger->warning('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::WARNING);
|
|
});
|
|
|
|
it('delegates error to parent logger', function () {
|
|
$this->channelLogger->error('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::ERROR);
|
|
});
|
|
|
|
it('delegates critical to parent logger', function () {
|
|
$this->channelLogger->critical('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::CRITICAL);
|
|
});
|
|
|
|
it('delegates alert to parent logger', function () {
|
|
$this->channelLogger->alert('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::ALERT);
|
|
});
|
|
|
|
it('delegates emergency to parent logger', function () {
|
|
$this->channelLogger->emergency('Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::EMERGENCY);
|
|
});
|
|
|
|
it('delegates generic log to parent logger', function () {
|
|
$this->channelLogger->log(LogLevel::WARNING, 'Test message', LogContext::withData(['key' => 'value']));
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['level'])->toBe(LogLevel::WARNING);
|
|
expect($this->logCalls[0]['message'])->toBe('Test message');
|
|
});
|
|
});
|
|
|
|
describe('context handling', function () {
|
|
it('supports log context objects', function () {
|
|
$logContext = LogContext::withData(['user_id' => 123]);
|
|
|
|
$this->channelLogger->info('Test message', $logContext);
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['context'])->toBeInstanceOf(LogContext::class);
|
|
});
|
|
|
|
it('supports empty context', function () {
|
|
$this->channelLogger->info('Test message');
|
|
|
|
expect(count($this->logCalls))->toBe(1);
|
|
expect($this->logCalls[0]['message'])->toBe('Test message');
|
|
});
|
|
});
|
|
|
|
describe('channel independence', function () {
|
|
it('different channels work independently', function () {
|
|
$logCalls = [];
|
|
$testLogger = new class($logCalls) implements Logger, SupportsChannels {
|
|
public function __construct(private array &$logCalls)
|
|
{
|
|
}
|
|
|
|
public function debug(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::DEBUG, $message, $context);
|
|
}
|
|
|
|
public function info(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::INFO, $message, $context);
|
|
}
|
|
|
|
public function notice(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::NOTICE, $message, $context);
|
|
}
|
|
|
|
public function warning(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, LogLevel::WARNING, $message, $context);
|
|
}
|
|
|
|
public function error(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::ERROR, $message, $context);
|
|
}
|
|
|
|
public function critical(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::CRITICAL, $message, $context);
|
|
}
|
|
|
|
public function alert(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::ALERT, $message, $context);
|
|
}
|
|
|
|
public function emergency(string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::ERROR, LogLevel::EMERGENCY, $message, $context);
|
|
}
|
|
|
|
public function log(LogLevel $level, string $message, ?LogContext $context = null): void
|
|
{
|
|
$this->logToChannel(LogChannel::FRAMEWORK, $level, $message, $context);
|
|
}
|
|
|
|
public function logToChannel(
|
|
LogChannel $channel,
|
|
LogLevel $level,
|
|
string $message,
|
|
?LogContext $context = null
|
|
): void {
|
|
$this->logCalls[] = ['channel' => $channel];
|
|
}
|
|
|
|
public function channel(LogChannel|string $channel): Logger&HasChannel
|
|
{
|
|
$logChannel = $channel instanceof LogChannel ? $channel : LogChannel::from($channel);
|
|
return new DefaultChannelLogger($this, $logChannel);
|
|
}
|
|
};
|
|
|
|
$cacheLogger = new DefaultChannelLogger($testLogger, LogChannel::CACHE);
|
|
$dbLogger = new DefaultChannelLogger($testLogger, LogChannel::DATABASE);
|
|
$securityLogger = new DefaultChannelLogger($testLogger, LogChannel::SECURITY);
|
|
|
|
expect($cacheLogger->channel)->toBe(LogChannel::CACHE);
|
|
expect($dbLogger->channel)->toBe(LogChannel::DATABASE);
|
|
expect($securityLogger->channel)->toBe(LogChannel::SECURITY);
|
|
});
|
|
});
|
|
});
|