Logging
This commit is contained in:
parent
f1d46dacb6
commit
75ce822b84
6 changed files with 399 additions and 1 deletions
36
tests/Logging/CustomLogger.php
Normal file
36
tests/Logging/CustomLogger.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Logging;
|
||||
|
||||
use Psr\Log\AbstractLogger;
|
||||
|
||||
class CustomLogger extends AbstractLogger
|
||||
{
|
||||
public array $logs = [];
|
||||
|
||||
public function log($level, string|\Stringable $message, array $context = []): void
|
||||
{
|
||||
$this->logs[] = [
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'context' => $context,
|
||||
];
|
||||
}
|
||||
|
||||
public function hasLog(string $level, string $contains): bool
|
||||
{
|
||||
foreach ($this->logs as $log) {
|
||||
if ($log['level'] === $level && str_contains($log['message'], $contains)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->logs = [];
|
||||
}
|
||||
}
|
||||
281
tests/Logging/LogTest.php
Normal file
281
tests/Logging/LogTest.php
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Logging;
|
||||
|
||||
use Icefox\DTO\Log;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\NullLogger;
|
||||
use Tests\Classes\PrimitiveData;
|
||||
use Tests\TestCase;
|
||||
|
||||
describe('logger resolution', function () {
|
||||
|
||||
afterEach(function () {
|
||||
config()->set('dto.log.logger', NullLogger::class);
|
||||
});
|
||||
|
||||
it('uses NullLogger as fallback when logger config is null', function () {
|
||||
config()->set('dto.log.logger', null);
|
||||
$log = new Log();
|
||||
expect($log->logger)->toBeInstanceOf(NullLogger::class);
|
||||
});
|
||||
|
||||
it('uses NullLogger as fallback when logger config is invalid', function () {
|
||||
config()->set('dto.log.logger', 'NonExistentLoggerClass');
|
||||
$log = new Log();
|
||||
expect($log->logger)->toBeInstanceOf(NullLogger::class);
|
||||
});
|
||||
|
||||
it('instantiates logger from class name via Laravel container', function () {
|
||||
config()->set('dto.log.logger', CustomLogger::class);
|
||||
$log = new Log();
|
||||
expect($log->logger)->toBeInstanceOf(CustomLogger::class);
|
||||
});
|
||||
|
||||
it('uses logger object directly when provided', function () {
|
||||
$customLogger = new CustomLogger();
|
||||
config()->set('dto.log.logger', $customLogger);
|
||||
$log = new Log();
|
||||
expect($log->logger)->toBe($customLogger);
|
||||
});
|
||||
|
||||
it('invokes callable to get logger instance', function () {
|
||||
config()->set('dto.log.logger', function () {
|
||||
return new CustomLogger();
|
||||
});
|
||||
|
||||
$log = new Log();
|
||||
|
||||
expect($log->logger)->toBeInstanceOf(CustomLogger::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('log level configuration', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
$this->customLogger = new CustomLogger();
|
||||
config()->set('dto.log.logger', $this->customLogger);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
config()->set('dto.log.logger', NullLogger::class);
|
||||
config()->set('dto.log.rules', LogLevel::DEBUG);
|
||||
config()->set('dto.log.input', LogLevel::DEBUG);
|
||||
config()->set('dto.log.raw_input', LogLevel::DEBUG);
|
||||
config()->set('dto.log.validation_errors', LogLevel::INFO);
|
||||
});
|
||||
|
||||
it('logs rules at configured level', function () {
|
||||
config()->set('dto.log.rules', LogLevel::INFO);
|
||||
|
||||
$log = new Log();
|
||||
$log->rules(['field' => ['required']]);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::INFO, 'field'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs input at configured level', function () {
|
||||
config()->set('dto.log.input', LogLevel::INFO);
|
||||
|
||||
$log = new Log();
|
||||
$log->input(['field' => 'value']);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::INFO, 'value'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs raw input at configured level', function () {
|
||||
config()->set('dto.log.raw_input', LogLevel::ERROR);
|
||||
|
||||
$log = new Log();
|
||||
$log->inputRaw(['field' => 'raw_value']);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::ERROR, 'raw_value'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs validation errors at configured level', function () {
|
||||
config()->set('dto.log.validation_errors', LogLevel::ERROR);
|
||||
|
||||
$log = new Log();
|
||||
$log->validationErrors(['field' => ['The field is required.']]);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::ERROR, 'required'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('allows different log levels for each log type', function () {
|
||||
config()->set('dto.log.rules', LogLevel::DEBUG);
|
||||
config()->set('dto.log.input', LogLevel::INFO);
|
||||
config()->set('dto.log.raw_input', LogLevel::INFO);
|
||||
|
||||
$log = new Log();
|
||||
|
||||
$log->rules(['rules_field' => ['required']]);
|
||||
$log->input(['input_field' => 'value']);
|
||||
$log->inputRaw(['raw_field' => 'raw_value']);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'rules_field'))->toBeTrue();
|
||||
expect($this->customLogger->hasLog(LogLevel::INFO, 'input_field'))->toBeTrue();
|
||||
expect($this->customLogger->hasLog(LogLevel::INFO, 'raw_field'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('defaults to DEBUG level when not configured', function () {
|
||||
config()->set('dto.log.rules', null);
|
||||
config()->set('dto.log.input', null);
|
||||
config()->set('dto.log.raw_input', null);
|
||||
|
||||
$customLogger = new CustomLogger();
|
||||
config()->set('dto.log.logger', $customLogger);
|
||||
|
||||
$log = new Log();
|
||||
|
||||
$log->rules(['field' => ['required']]);
|
||||
$log->input(['field' => 'value']);
|
||||
$log->inputRaw(['field' => 'raw_value']);
|
||||
|
||||
expect(count($customLogger->logs))->toBe(3);
|
||||
expect($customLogger->logs[0]['level'])->toBe(LogLevel::DEBUG);
|
||||
expect($customLogger->logs[1]['level'])->toBe(LogLevel::DEBUG);
|
||||
expect($customLogger->logs[2]['level'])->toBe(LogLevel::DEBUG);
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration with DataObject', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
$this->customLogger = new CustomLogger();
|
||||
config()->set('dto.log.logger', $this->customLogger);
|
||||
config()->set('dto.log.rules', LogLevel::DEBUG);
|
||||
config()->set('dto.log.input', LogLevel::DEBUG);
|
||||
config()->set('dto.log.raw_input', LogLevel::DEBUG);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
config()->set('dto.log.logger', NullLogger::class);
|
||||
});
|
||||
|
||||
it('logs raw input during fromArray execution', function () {
|
||||
PrimitiveData::fromArray([
|
||||
'string' => 'test',
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'raw_input'))->toBeFalse();
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'string'))->toBeTrue();
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, '42'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs rules during fromArray execution', function () {
|
||||
PrimitiveData::fromArray([
|
||||
'string' => 'test',
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'required'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs processed input during fromArray execution', function () {
|
||||
PrimitiveData::fromArray([
|
||||
'string' => 'test',
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'test'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('captures all three log types during successful fromArray', function () {
|
||||
PrimitiveData::fromArray([
|
||||
'string' => 'integration_test',
|
||||
'int' => 123,
|
||||
'float' => 9.99,
|
||||
'bool' => false,
|
||||
]);
|
||||
|
||||
$rawInputLogged = false;
|
||||
$rulesLogged = false;
|
||||
$inputLogged = false;
|
||||
|
||||
foreach ($this->customLogger->logs as $log) {
|
||||
if (str_contains($log['message'], 'string')) {
|
||||
$rawInputLogged = true;
|
||||
}
|
||||
if (str_contains($log['message'], 'required')) {
|
||||
$rulesLogged = true;
|
||||
}
|
||||
if (str_contains($log['message'], 'integration_test')) {
|
||||
$inputLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
expect($rawInputLogged)->toBeTrue('Raw input should be logged');
|
||||
expect($rulesLogged)->toBeTrue('Rules should be logged');
|
||||
expect($inputLogged)->toBeTrue('Processed input should be logged');
|
||||
});
|
||||
|
||||
it('logs even when validation fails', function () {
|
||||
try {
|
||||
PrimitiveData::fromArray([
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, 'required'))->toBeTrue();
|
||||
expect($this->customLogger->hasLog(LogLevel::DEBUG, '42'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs validation errors when validation fails', function () {
|
||||
config()->set('dto.log.validation_errors', LogLevel::ERROR);
|
||||
|
||||
try {
|
||||
PrimitiveData::fromArray([
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
expect($this->customLogger->hasLog(LogLevel::ERROR, 'string'))->toBeTrue();
|
||||
expect($this->customLogger->hasLog(LogLevel::ERROR, 'required'))->toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logging with NullLogger', function () {
|
||||
|
||||
it('does not throw when logging with NullLogger', function () {
|
||||
config()->set('dto.log.logger', NullLogger::class);
|
||||
|
||||
$log = new Log();
|
||||
|
||||
expect(function () use ($log) {
|
||||
$log->rules(['field' => ['required']]);
|
||||
$log->input(['field' => 'value']);
|
||||
$log->inputRaw(['field' => 'raw_value']);
|
||||
})->not->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
it('does not affect DataObject behavior when using NullLogger', function () {
|
||||
config()->set('dto.log.logger', NullLogger::class);
|
||||
|
||||
$object = PrimitiveData::fromArray([
|
||||
'string' => 'test',
|
||||
'int' => 42,
|
||||
'float' => 3.14,
|
||||
'bool' => true,
|
||||
]);
|
||||
|
||||
expect($object)->toBeInstanceOf(PrimitiveData::class);
|
||||
expect($object->string)->toBe('test');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue