Files
michaelschiemer/tests/Unit/Framework/Logging/Formatter/LogFormatterTest.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

202 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Framework\Logging\Formatter;
use App\Framework\Logging\Formatter\DevelopmentFormatter;
use App\Framework\Logging\Formatter\JsonFormatter;
use App\Framework\Logging\Formatter\LineFormatter;
use App\Framework\Logging\Formatter\StructuredFormatter;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
use DateTimeImmutable;
use DateTimeZone;
use Exception;
beforeEach(function () {
$this->timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin'));
$this->basicContext = LogContext::withData(['user_id' => 123, 'action' => 'login']);
});
describe('LineFormatter', function () {
it('formats basic log record', function () {
$formatter = new LineFormatter();
$record = new LogRecord('Test message', $this->basicContext, LogLevel::INFO, $this->timestamp);
$output = $formatter($record);
expect($output)->toContain('2024-01-15 10:30:45');
expect($output)->toContain('INFO');
expect($output)->toContain('Test message');
expect($output)->toContain('user_id');
});
it('supports custom format template', function () {
$formatter = new LineFormatter('{level}: {message}');
$record = new LogRecord('Custom format', $this->basicContext, LogLevel::WARNING, $this->timestamp);
$output = $formatter($record);
expect($output)->toBe('WARNING: Custom format');
});
it('includes channel in output', function () {
$formatter = new LineFormatter();
$record = new LogRecord('Channel test', $this->basicContext, LogLevel::ERROR, $this->timestamp, 'security');
$output = $formatter($record);
expect($output)->toContain('security.ERROR');
});
});
describe('JsonFormatter', function () {
it('formats record as valid JSON', function () {
$formatter = new JsonFormatter();
$record = new LogRecord('JSON test', $this->basicContext, LogLevel::DEBUG, $this->timestamp);
$output = $formatter($record);
$decoded = json_decode($output, true);
expect($decoded)->toBeArray();
expect($decoded['message'])->toBe('JSON test');
expect($decoded['level'])->toBe('DEBUG');
expect($decoded['context']['user_id'])->toBe(123);
});
it('formats with pretty print when enabled', function () {
$formatter = new JsonFormatter(prettyPrint: true);
$record = new LogRecord('Pretty JSON', $this->basicContext, LogLevel::INFO, $this->timestamp);
$output = $formatter($record);
expect($output)->toContain("\n");
expect($output)->toContain(" ");
});
it('excludes extras when disabled', function () {
$formatter = new JsonFormatter(includeExtras: false);
$record = new LogRecord('No extras', $this->basicContext, LogLevel::INFO, $this->timestamp);
$record->addExtra('test_extra', 'value');
$output = $formatter($record);
$decoded = json_decode($output, true);
expect($decoded)->not->toHaveKey('extra');
});
});
describe('DevelopmentFormatter', function () {
it('formats with human-readable output', function () {
$formatter = new DevelopmentFormatter(colorOutput: false);
$record = new LogRecord('Dev message', $this->basicContext, LogLevel::ERROR, $this->timestamp);
$output = $formatter($record);
expect($output)->toContain('10:30:45');
expect($output)->toContain('ERROR');
expect($output)->toContain('Context:');
expect($output)->toContain('user_id: 123');
});
it('formats exception details', function () {
$formatter = new DevelopmentFormatter(colorOutput: false);
$exception = new Exception('Test exception');
$context = LogContext::withData(['exception' => $exception]);
$record = new LogRecord('Exception occurred', $context, LogLevel::ERROR, $this->timestamp);
// Simulate exception processor enrichment
$record->addExtras([
'exception_class' => Exception::class,
'exception_file' => __FILE__,
'exception_line' => __LINE__,
'exception_hash' => 'abc12345',
'exception_severity' => 'medium',
'stack_trace_short' => 'File.php:123 → Class::method()',
]);
$output = $formatter($record);
expect($output)->toContain('Exception:');
expect($output)->toContain('Class: Exception');
expect($output)->toContain('Hash: abc12345');
expect($output)->toContain('Trace: File.php:123');
});
});
describe('StructuredFormatter', function () {
it('formats as logfmt by default', function () {
$formatter = new StructuredFormatter();
$record = new LogRecord('Structured test', $this->basicContext, LogLevel::INFO, $this->timestamp);
$output = $formatter($record);
expect($output)->toBeString();
expect($output)->toContain('level=INFO');
expect($output)->toContain('msg="Structured test"');
expect($output)->toContain('user_id=123');
});
it('formats as key-value when specified', function () {
$formatter = new StructuredFormatter(format: 'kv');
$record = new LogRecord('KV test', $this->basicContext, LogLevel::WARNING, $this->timestamp);
$output = $formatter($record);
expect($output)->toContain('level: WARNING');
expect($output)->toContain('msg: KV test');
expect($output)->toContain(', ');
});
it('returns array when format is array', function () {
$formatter = new StructuredFormatter(format: 'array');
$record = new LogRecord('Array test', $this->basicContext, LogLevel::INFO, $this->timestamp);
$output = $formatter($record);
expect($output)->toBeArray();
expect($output['level'])->toBe('INFO');
expect($output['msg'])->toBe('Array test');
});
it('sanitizes keys properly', function () {
$context = LogContext::withData(['_internal_key' => 'value', 'key-with-dash' => 'value2']);
$formatter = new StructuredFormatter(format: 'array');
$record = new LogRecord('Sanitize test', $context, LogLevel::INFO, $this->timestamp);
$output = $formatter($record);
expect($output)->toHaveKey('internal_key'); // _ prefix removed
expect($output)->toHaveKey('key_with_dash'); // dash converted to underscore
});
});
describe('Formatter Integration', function () {
it('all formatters handle same record consistently', function () {
$context = LogContext::withData(['test' => 'value'])->addTags('integration');
$record = new LogRecord('Multi-formatter test', $context, LogLevel::INFO, $this->timestamp);
$lineFormatter = new LineFormatter();
$jsonFormatter = new JsonFormatter();
$devFormatter = new DevelopmentFormatter(colorOutput: false);
$structuredFormatter = new StructuredFormatter();
$lineOutput = $lineFormatter($record);
$jsonOutput = $jsonFormatter($record);
$devOutput = $devFormatter($record);
$structuredOutput = $structuredFormatter($record);
// All should contain the message
expect($lineOutput)->toContain('Multi-formatter test');
expect($jsonOutput)->toContain('Multi-formatter test');
expect($devOutput)->toContain('Multi-formatter test');
expect($structuredOutput)->toContain('Multi-formatter test');
// JSON should be valid
expect(json_decode($jsonOutput))->not->toBeNull();
});
});