Files
michaelschiemer/tests/Unit/Framework/Logging/DefaultLoggerTest.php
Michael Schiemer 5050c7d73a docs: consolidate documentation into organized structure
- 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
2025-10-05 11:05:04 +02:00

404 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Framework\Logging;
use App\Framework\Logging\DefaultLogger;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
beforeEach(function () {
// Reset any static state
});
it('can create a logger with default settings', function () {
$logger = new DefaultLogger();
expect($logger)->toBeInstanceOf(DefaultLogger::class);
expect($logger->getConfiguration()['minLevel'])->toBe(LogLevel::DEBUG->value);
});
it('can create a logger with custom minimum level', function () {
$logger = new DefaultLogger(minLevel: LogLevel::WARNING);
expect($logger->getConfiguration()['minLevel'])->toBe(LogLevel::WARNING->value);
});
it('respects minimum log level and filters lower priority messages', function () {
$mockHandler = new class () implements LogHandler {
public array $handledRecords = [];
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->handledRecords[] = $record;
}
};
$logger = new DefaultLogger(
minLevel: LogLevel::WARNING,
handlers: [$mockHandler]
);
// These should be ignored (lower than WARNING)
$logger->debug('Debug message');
$logger->info('Info message');
$logger->notice('Notice message');
// These should be handled (WARNING or higher)
$logger->warning('Warning message');
$logger->error('Error message');
$logger->critical('Critical message');
expect($mockHandler->handledRecords)->toHaveCount(3);
expect($mockHandler->handledRecords[0]->getMessage())->toBe('Warning message');
expect($mockHandler->handledRecords[1]->getMessage())->toBe('Error message');
expect($mockHandler->handledRecords[2]->getMessage())->toBe('Critical message');
});
it('can log messages with all severity levels', function () {
$recordedLevels = [];
$mockHandler = new class ($recordedLevels) implements LogHandler {
public function __construct(private array &$recordedLevels)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->recordedLevels[] = $record->getLevel()->value;
}
};
$logger = new DefaultLogger(handlers: [$mockHandler]);
$logger->debug('Debug');
$logger->info('Info');
$logger->notice('Notice');
$logger->warning('Warning');
$logger->error('Error');
$logger->critical('Critical');
$logger->alert('Alert');
$logger->emergency('Emergency');
expect($recordedLevels)->toBe([
LogLevel::DEBUG->value,
LogLevel::INFO->value,
LogLevel::NOTICE->value,
LogLevel::WARNING->value,
LogLevel::ERROR->value,
LogLevel::CRITICAL->value,
LogLevel::ALERT->value,
LogLevel::EMERGENCY->value,
]);
});
it('can log with LogContext', function () {
$capturedContext = null;
$mockHandler = new class ($capturedContext) implements LogHandler {
public function __construct(private ?array &$capturedContext)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->capturedContext = $record->getContext();
}
};
$logger = new DefaultLogger(handlers: [$mockHandler]);
$context = LogContext::withData(['user_id' => 123, 'action' => 'login']);
$logger->info('User action', $context);
expect($capturedContext)->toMatchArray(['user_id' => 123, 'action' => 'login']);
});
it('can log with null context', function () {
$capturedContext = null;
$mockHandler = new class ($capturedContext) implements LogHandler {
public function __construct(private ?array &$capturedContext)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->capturedContext = $record->getContext();
}
};
$logger = new DefaultLogger(handlers: [$mockHandler]);
// Log without context
$logger->info('Simple message');
expect($capturedContext)->toBe([]);
});
it('can be created without ProcessorManager', function () {
$processedRecord = null;
$mockHandler = new class ($processedRecord) implements LogHandler {
public function __construct(private ?LogRecord &$processedRecord)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->processedRecord = $record;
}
};
$logger = new DefaultLogger(
handlers: [$mockHandler]
);
$logger->info('Test message');
expect($processedRecord)->not->toBeNull();
expect($processedRecord->getMessage())->toBe('Test message');
});
it('logs with LogContext including tags', function () {
$capturedContext = null;
$mockHandler = new class ($capturedContext) implements LogHandler {
public function __construct(private ?array &$capturedContext)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->capturedContext = $record->getContext();
}
};
$logger = new DefaultLogger(
handlers: [$mockHandler]
);
$context = LogContext::withData(['request_id' => 'req-123'])
->addTags('api', 'v2');
$logger->info('API request', $context);
expect($capturedContext)->toMatchArray([
'request_id' => 'req-123',
'_tags' => ['api', 'v2'],
]);
});
it('provides channel loggers for different log channels', function () {
$logger = new DefaultLogger();
expect($logger->security)->not->toBeNull();
expect($logger->cache)->not->toBeNull();
expect($logger->database)->not->toBeNull();
expect($logger->framework)->not->toBeNull();
expect($logger->error)->not->toBeNull();
});
it('can log to specific channels using channel loggers', function () {
$capturedChannel = null;
$capturedMessage = null;
$mockHandler = new class ($capturedChannel, $capturedMessage) implements LogHandler {
public function __construct(
private ?string &$capturedChannel,
private ?string &$capturedMessage
) {
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->capturedChannel = $record->getChannel();
$this->capturedMessage = $record->getMessage();
}
};
$logger = new DefaultLogger(handlers: [$mockHandler]);
// Log to security channel with LogContext
$context = LogContext::withData(['ip' => '192.168.1.1'])->addTags('security');
$logger->security->warning('Unauthorized access attempt', $context);
expect($capturedChannel)->toBe(LogChannel::SECURITY->value);
expect($capturedMessage)->toBe('Unauthorized access attempt');
// Log to database channel without context
$logger->database->error('Connection failed');
expect($capturedChannel)->toBe(LogChannel::DATABASE->value);
expect($capturedMessage)->toBe('Connection failed');
});
it('calls multiple handlers when configured', function () {
$handler1Called = false;
$handler2Called = false;
$handler3Called = false;
$handler1 = new class ($handler1Called) implements LogHandler {
public function __construct(private bool &$called)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->called = true;
}
};
$handler2 = new class ($handler2Called) implements LogHandler {
public function __construct(private bool &$called)
{
}
public function isHandling(LogRecord $record): bool
{
return $record->getLevel()->value >= LogLevel::WARNING->value;
}
public function handle(LogRecord $record): void
{
$this->called = true;
}
};
$handler3 = new class ($handler3Called) implements LogHandler {
public function __construct(private bool &$called)
{
}
public function isHandling(LogRecord $record): bool
{
return false; // Never handles
}
public function handle(LogRecord $record): void
{
$this->called = true;
}
};
$logger = new DefaultLogger(handlers: [$handler1, $handler2, $handler3]);
$logger->error('Test error');
expect($handler1Called)->toBeTrue();
expect($handler2Called)->toBeTrue();
expect($handler3Called)->toBeFalse();
});
it('creates log records with correct timestamp', function () {
$capturedRecord = null;
$mockHandler = new class ($capturedRecord) implements LogHandler {
public function __construct(private ?LogRecord &$capturedRecord)
{
}
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
$this->capturedRecord = $record;
}
};
$logger = new DefaultLogger(handlers: [$mockHandler]);
$beforeLog = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin'));
$logger->info('Test timestamp');
$afterLog = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Berlin'));
expect($capturedRecord)->not->toBeNull();
expect($capturedRecord->getTimestamp())->toBeInstanceOf(\DateTimeImmutable::class);
$timestamp = $capturedRecord->getTimestamp();
expect($timestamp >= $beforeLog)->toBeTrue();
expect($timestamp <= $afterLog)->toBeTrue();
expect($timestamp->getTimezone()->getName())->toBe('Europe/Berlin');
});
it('returns correct configuration information', function () {
$handler1 = new class () implements LogHandler {
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
}
};
$handler2 = new class () implements LogHandler {
public function isHandling(LogRecord $record): bool
{
return true;
}
public function handle(LogRecord $record): void
{
}
};
$logger = new DefaultLogger(
minLevel: LogLevel::INFO,
handlers: [$handler1, $handler2]
);
$config = $logger->getConfiguration();
expect($config['minLevel'])->toBe(LogLevel::INFO->value);
expect($config['handlers'])->toHaveCount(2);
expect($config['processors'])->toBeArray();
});