- 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.
222 lines
7.2 KiB
PHP
222 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\ValueObjects\LogEntry;
|
|
|
|
describe('LogEntry', function () {
|
|
it('creates from parsed log line', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Test error message {"user_id":123}',
|
|
raw: '[2024-01-15 10:30:45] local.ERROR: Test error message {"user_id":123}',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->level)->toBe(LogLevel::ERROR);
|
|
expect($entry->message)->toBe('Test error message');
|
|
expect($entry->context)->toBe('{"user_id":123}');
|
|
expect($entry->parsed)->toBeTrue();
|
|
expect($entry->sourcePath)->toBe($path);
|
|
});
|
|
|
|
it('creates from raw unparsed line', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromRawLine('Some random log line', $path);
|
|
|
|
expect($entry->level)->toBe(LogLevel::INFO);
|
|
expect($entry->message)->toBe('Some random log line');
|
|
expect($entry->context)->toBe('');
|
|
expect($entry->parsed)->toBeFalse();
|
|
});
|
|
|
|
it('extracts message without context', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'INFO',
|
|
messageWithContext: 'Simple message without context',
|
|
raw: 'Simple message without context',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->message)->toBe('Simple message without context');
|
|
expect($entry->context)->toBe('');
|
|
});
|
|
|
|
it('matches search term in message', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Database connection failed',
|
|
raw: 'Database connection failed',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->matchesSearch('database'))->toBeTrue();
|
|
expect($entry->matchesSearch('Database'))->toBeTrue(); // Case insensitive
|
|
expect($entry->matchesSearch('connection'))->toBeTrue();
|
|
expect($entry->matchesSearch('redis'))->toBeFalse();
|
|
});
|
|
|
|
it('matches search term in context', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Error occurred {"database":"mysql"}',
|
|
raw: 'Error occurred {"database":"mysql"}',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->matchesSearch('mysql'))->toBeTrue();
|
|
});
|
|
|
|
it('checks log level match', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Test message',
|
|
raw: 'Test message',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->matchesLevel(LogLevel::ERROR))->toBeTrue();
|
|
expect($entry->matchesLevel(LogLevel::INFO))->toBeFalse();
|
|
});
|
|
|
|
it('checks minimum log level', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Test message',
|
|
raw: 'Test message',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->isAtLeastLevel(LogLevel::ERROR))->toBeTrue();
|
|
expect($entry->isAtLeastLevel(LogLevel::WARNING))->toBeTrue();
|
|
expect($entry->isAtLeastLevel(LogLevel::CRITICAL))->toBeFalse();
|
|
});
|
|
|
|
it('formats timestamp', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'INFO',
|
|
messageWithContext: 'Test',
|
|
raw: 'Test',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->getFormattedTimestamp())->toBe('2024-01-15 10:30:45');
|
|
expect($entry->getFormattedTimestamp('Y-m-d'))->toBe('2024-01-15');
|
|
});
|
|
|
|
it('parses context as array', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Error {"user_id":123,"action":"login"}',
|
|
raw: 'Error',
|
|
sourcePath: $path
|
|
);
|
|
|
|
$contextArray = $entry->getContextArray();
|
|
|
|
expect($contextArray)->toBeArray();
|
|
expect($contextArray['user_id'])->toBe(123);
|
|
expect($contextArray['action'])->toBe('login');
|
|
});
|
|
|
|
it('returns empty array for no context', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'INFO',
|
|
messageWithContext: 'Simple message',
|
|
raw: 'Simple message',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->getContextArray())->toBe([]);
|
|
expect($entry->hasContext())->toBeFalse();
|
|
});
|
|
|
|
it('converts to array', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'ERROR',
|
|
messageWithContext: 'Test error',
|
|
raw: '[2024-01-15 10:30:45] ERROR: Test error',
|
|
sourcePath: $path
|
|
);
|
|
|
|
$array = $entry->toArray();
|
|
|
|
expect($array)->toHaveKey('timestamp');
|
|
expect($array)->toHaveKey('level');
|
|
expect($array)->toHaveKey('message');
|
|
expect($array)->toHaveKey('context');
|
|
expect($array)->toHaveKey('raw');
|
|
expect($array)->toHaveKey('parsed');
|
|
expect($array)->toHaveKey('source_path');
|
|
|
|
expect($array['level'])->toBe('ERROR');
|
|
expect($array['message'])->toBe('Test error');
|
|
});
|
|
|
|
it('parses all log levels correctly', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$levels = ['DEBUG', 'INFO', 'NOTICE', 'WARNING', 'ERROR', 'CRITICAL', 'ALERT', 'EMERGENCY'];
|
|
|
|
foreach ($levels as $levelName) {
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: $levelName,
|
|
messageWithContext: 'Test',
|
|
raw: 'Test',
|
|
sourcePath: $path
|
|
);
|
|
|
|
expect($entry->level->getName())->toBe($levelName);
|
|
}
|
|
});
|
|
|
|
it('handles unknown log level gracefully', function () {
|
|
$path = FilePath::create('/tmp/test.log');
|
|
|
|
$entry = LogEntry::fromParsedLine(
|
|
timestamp: '2024-01-15 10:30:45',
|
|
level: 'UNKNOWN',
|
|
messageWithContext: 'Test',
|
|
raw: 'Test',
|
|
sourcePath: $path
|
|
);
|
|
|
|
// Should fallback to INFO
|
|
expect($entry->level)->toBe(LogLevel::INFO);
|
|
});
|
|
});
|