- 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.
340 lines
11 KiB
PHP
340 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Logging\Handlers\SyslogHandler;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
|
|
describe('SyslogHandler', function () {
|
|
beforeEach(function () {
|
|
$this->timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin'));
|
|
});
|
|
|
|
describe('constructor', function () {
|
|
it('accepts custom ident and facility', function () {
|
|
$handler = new SyslogHandler(
|
|
ident: 'test-app',
|
|
facility: LOG_LOCAL0,
|
|
minLevel: LogLevel::WARNING
|
|
);
|
|
|
|
expect($handler)->toBeInstanceOf(SyslogHandler::class);
|
|
});
|
|
|
|
it('uses default values when not specified', function () {
|
|
$handler = new SyslogHandler();
|
|
|
|
expect($handler)->toBeInstanceOf(SyslogHandler::class);
|
|
});
|
|
});
|
|
|
|
describe('isHandling()', function () {
|
|
it('returns true when record level is above minLevel', function () {
|
|
$handler = new SyslogHandler(minLevel: LogLevel::WARNING);
|
|
|
|
$record = new LogRecord(
|
|
message: 'test message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::ERROR,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
expect($handler->isHandling($record))->toBeTrue();
|
|
});
|
|
|
|
it('returns true when record level equals minLevel', function () {
|
|
$handler = new SyslogHandler(minLevel: LogLevel::WARNING);
|
|
|
|
$record = new LogRecord(
|
|
message: 'test message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::WARNING,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
expect($handler->isHandling($record))->toBeTrue();
|
|
});
|
|
|
|
it('returns false when record level is below minLevel', function () {
|
|
$handler = new SyslogHandler(minLevel: LogLevel::ERROR);
|
|
|
|
$record = new LogRecord(
|
|
message: 'test message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::WARNING,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
expect($handler->isHandling($record))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('handle()', function () {
|
|
it('handles log records without errors', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test log message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles records with request_id extra', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = (new LogRecord(
|
|
message: 'Test message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
))->addExtra('request_id', 'req-123');
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles records with channel', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp,
|
|
channel: 'security'
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles records with context data', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test message',
|
|
context: LogContext::withData(['user_id' => 123, 'action' => 'login']),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles all log levels', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test', minLevel: LogLevel::DEBUG);
|
|
|
|
$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: LogContext::empty(),
|
|
level: $level,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
}
|
|
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles records with empty context', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Message without context',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('handles complex context data with special characters', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test message',
|
|
context: LogContext::withData([
|
|
'email' => 'test@example.com',
|
|
'path' => '/api/users/123',
|
|
'special' => "line1\nline2\ttabbed",
|
|
]),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should not throw exception
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('syslog priority mapping', function () {
|
|
it('correctly maps emergency level', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Emergency',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::EMERGENCY,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should use LOG_EMERG priority internally
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('correctly maps alert level', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Alert',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::ALERT,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should use LOG_ALERT priority internally
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('correctly maps critical level', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test');
|
|
|
|
$record = new LogRecord(
|
|
message: 'Critical',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::CRITICAL,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should use LOG_CRIT priority internally
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('syslog connection management', function () {
|
|
it('opens syslog connection on first handle', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test-connection');
|
|
|
|
$record = new LogRecord(
|
|
message: 'First message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should open connection internally
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('reuses open syslog connection for multiple records', function () {
|
|
$handler = new SyslogHandler(ident: 'pest-test-reuse');
|
|
|
|
$record1 = new LogRecord(
|
|
message: 'First message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
$record2 = new LogRecord(
|
|
message: 'Second message',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::WARNING,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
// Should reuse connection
|
|
$handler->handle($record1);
|
|
$handler->handle($record2);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('different facilities', function () {
|
|
it('accepts LOG_USER facility', function () {
|
|
$handler = new SyslogHandler(
|
|
ident: 'pest-test',
|
|
facility: LOG_USER
|
|
);
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('accepts LOG_LOCAL0 facility', function () {
|
|
$handler = new SyslogHandler(
|
|
ident: 'pest-test',
|
|
facility: LOG_LOCAL0
|
|
);
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
|
|
it('accepts LOG_DAEMON facility', function () {
|
|
$handler = new SyslogHandler(
|
|
ident: 'pest-test',
|
|
facility: LOG_DAEMON
|
|
);
|
|
|
|
$record = new LogRecord(
|
|
message: 'Test',
|
|
context: LogContext::empty(),
|
|
level: LogLevel::INFO,
|
|
timestamp: $this->timestamp
|
|
);
|
|
|
|
$handler->handle($record);
|
|
expect(true)->toBeTrue();
|
|
});
|
|
});
|
|
});
|