- 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
294 lines
9.2 KiB
PHP
294 lines
9.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Logging;
|
|
|
|
use App\Framework\Logging\DefaultLogger;
|
|
use App\Framework\Logging\LogHandler;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use App\Framework\Logging\ProcessorManager;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
use App\Framework\Logging\ValueObjects\RequestContext;
|
|
use App\Framework\Logging\ValueObjects\UserContext;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
use App\Framework\Tracing\TraceContext;
|
|
|
|
describe('DefaultLogger', function () {
|
|
beforeEach(function () {
|
|
// Clear any existing trace context
|
|
TraceContext::clear();
|
|
});
|
|
|
|
afterEach(function () {
|
|
TraceContext::clear();
|
|
});
|
|
|
|
it('can log with array context (legacy)', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$context = ['user_id' => 123, 'action' => 'login'];
|
|
$logger->info('User logged in', $context);
|
|
|
|
expect($handler->records)->toHaveCount(1);
|
|
$record = $handler->records[0];
|
|
|
|
expect($record->getMessage())->toBe('User logged in');
|
|
expect($record->getContext())->toBe($context);
|
|
expect($record->getLevel())->toBe(LogLevel::INFO);
|
|
});
|
|
|
|
it('can log with LogContext', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$logContext = LogContext::withData(['user_id' => 123, 'action' => 'login'])
|
|
->addTags('authentication', 'user-action')
|
|
->addMetadata('source', 'test');
|
|
|
|
$logger->info('User logged in', $logContext);
|
|
|
|
expect($handler->records)->toHaveCount(1);
|
|
$record = $handler->records[0];
|
|
|
|
expect($record->getMessage())->toBe('User logged in');
|
|
expect($record->getLevel())->toBe(LogLevel::INFO);
|
|
|
|
// Context should be converted to array
|
|
$context = $record->getContext();
|
|
expect($context)->toHaveKey('user_id', 123);
|
|
expect($context)->toHaveKey('action', 'login');
|
|
expect($context)->toHaveKey('_tags', ['authentication', 'user-action']);
|
|
expect($context)->toHaveKey('source', 'test');
|
|
|
|
// Structured data should be in extras
|
|
expect($record->getExtra('structured_tags'))->toBe(['authentication', 'user-action']);
|
|
});
|
|
|
|
it('converts LogContext with trace to array context', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$trace = TraceContext::start(new SecureRandomGenerator());
|
|
$span = $trace->startSpan('test-span');
|
|
|
|
$logContext = LogContext::withData(['key' => 'value'])
|
|
->withTrace($trace);
|
|
|
|
$logger->info('Test message', $logContext);
|
|
|
|
$record = $handler->records[0];
|
|
$context = $record->getContext();
|
|
|
|
expect($context)->toHaveKey('_trace_id', $trace->getTraceId());
|
|
expect($context)->toHaveKey('_span_id', $span->spanId);
|
|
|
|
// Trace should also be in extras
|
|
$traceExtra = $record->getExtra('trace_context');
|
|
expect($traceExtra)->toHaveKey('trace_id', $trace->getTraceId());
|
|
expect($traceExtra)->toHaveKey('active_span');
|
|
});
|
|
|
|
it('converts LogContext with user to array context', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$userContext = UserContext::authenticated('123', 'john');
|
|
$logContext = LogContext::withData(['key' => 'value'])
|
|
->withUser($userContext);
|
|
|
|
$logger->info('Test message', $logContext);
|
|
|
|
$record = $handler->records[0];
|
|
$context = $record->getContext();
|
|
|
|
expect($context)->toHaveKey('_user_id', '123');
|
|
|
|
// User should also be in extras
|
|
$userExtra = $record->getExtra('user_context');
|
|
expect($userExtra)->toBeArray();
|
|
expect($userExtra)->toHaveKey('user_id', '123');
|
|
});
|
|
|
|
it('converts LogContext with request to array context', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$requestContext = RequestContext::empty();
|
|
$logContext = LogContext::withData(['key' => 'value'])
|
|
->withRequest($requestContext);
|
|
|
|
$logger->info('Test message', $logContext);
|
|
|
|
$record = $handler->records[0];
|
|
|
|
// Request should be in extras
|
|
$requestExtra = $record->getExtra('request_context');
|
|
expect($requestExtra)->toBeArray();
|
|
});
|
|
|
|
it('logs at different levels with LogContext', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$context = LogContext::withData(['test' => 'value']);
|
|
|
|
$logger->debug('Debug message', $context);
|
|
$logger->info('Info message', $context);
|
|
$logger->notice('Notice message', $context);
|
|
$logger->warning('Warning message', $context);
|
|
$logger->error('Error message', $context);
|
|
$logger->critical('Critical message', $context);
|
|
$logger->alert('Alert message', $context);
|
|
$logger->emergency('Emergency message', $context);
|
|
|
|
expect($handler->records)->toHaveCount(8);
|
|
|
|
$levels = array_map(fn ($record) => $record->getLevel(), $handler->records);
|
|
expect($levels)->toBe([
|
|
LogLevel::DEBUG,
|
|
LogLevel::INFO,
|
|
LogLevel::NOTICE,
|
|
LogLevel::WARNING,
|
|
LogLevel::ERROR,
|
|
LogLevel::CRITICAL,
|
|
LogLevel::ALERT,
|
|
LogLevel::EMERGENCY,
|
|
]);
|
|
});
|
|
|
|
it('respects minimum log level', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::WARNING,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$context = LogContext::withData(['test' => 'value']);
|
|
|
|
$logger->debug('Debug message', $context);
|
|
$logger->info('Info message', $context);
|
|
$logger->warning('Warning message', $context);
|
|
$logger->error('Error message', $context);
|
|
|
|
expect($handler->records)->toHaveCount(2);
|
|
expect($handler->records[0]->getLevel())->toBe(LogLevel::WARNING);
|
|
expect($handler->records[1]->getLevel())->toBe(LogLevel::ERROR);
|
|
});
|
|
|
|
it('calls processors on enriched record', function () {
|
|
$processor = new TestLogProcessor();
|
|
$processorManager = new ProcessorManager($processor);
|
|
$handler = new TestLogHandler();
|
|
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler],
|
|
processorManager: $processorManager
|
|
);
|
|
|
|
$context = LogContext::withData(['test' => 'value']);
|
|
$logger->info('Test message', $context);
|
|
|
|
expect($processor->processedRecords)->toHaveCount(1);
|
|
expect($handler->records)->toHaveCount(1);
|
|
|
|
// Processor should have seen the enriched record
|
|
$processedRecord = $processor->processedRecords[0];
|
|
expect($processedRecord->getExtra('structured_tags'))->toBeNull(); // No tags in this test
|
|
});
|
|
|
|
it('handles empty LogContext', function () {
|
|
$handler = new TestLogHandler();
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::DEBUG,
|
|
handlers: [$handler]
|
|
);
|
|
|
|
$logger->info('Test message', LogContext::empty());
|
|
|
|
expect($handler->records)->toHaveCount(1);
|
|
$record = $handler->records[0];
|
|
|
|
expect($record->getContext())->toBe([]);
|
|
expect($record->getExtras())->toBe([]);
|
|
});
|
|
|
|
it('can get configuration', function () {
|
|
$handler = new TestLogHandler();
|
|
$processor = new TestLogProcessor();
|
|
$processorManager = new ProcessorManager($processor);
|
|
|
|
$logger = new DefaultLogger(
|
|
minLevel: LogLevel::INFO,
|
|
handlers: [$handler],
|
|
processorManager: $processorManager
|
|
);
|
|
|
|
$config = $logger->getConfiguration();
|
|
|
|
expect($config)->toHaveKey('minLevel', 200);
|
|
expect($config)->toHaveKey('handlers');
|
|
expect($config)->toHaveKey('processors');
|
|
expect($config['handlers'])->toBe([TestLogHandler::class]);
|
|
});
|
|
});
|
|
|
|
// Test helpers
|
|
class TestLogHandler implements LogHandler
|
|
{
|
|
public array $records = [];
|
|
|
|
public function isHandling(LogRecord $record): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function handle(LogRecord $record): void
|
|
{
|
|
$this->records[] = $record;
|
|
}
|
|
}
|
|
|
|
class TestLogProcessor implements \App\Framework\Logging\LogProcessor
|
|
{
|
|
public array $processedRecords = [];
|
|
|
|
public function processRecord(LogRecord $record): LogRecord
|
|
{
|
|
$this->processedRecords[] = $record;
|
|
|
|
return $record->addExtra('processed_by', 'TestLogProcessor');
|
|
}
|
|
|
|
public function getPriority(): int
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return 'test-processor';
|
|
}
|
|
}
|