- 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
109 lines
2.9 KiB
PHP
109 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Logging\Formatter;
|
|
|
|
use App\Framework\Logging\LogRecord;
|
|
|
|
/**
|
|
* Structured Formatter für Log-Aggregation und Analysis
|
|
*/
|
|
final readonly class StructuredFormatter implements LogFormatter
|
|
{
|
|
public function __construct(
|
|
private string $format = 'logfmt', // 'logfmt' or 'kv'
|
|
private bool $includeTimestamp = true
|
|
) {
|
|
}
|
|
|
|
public function __invoke(LogRecord $record): string|array
|
|
{
|
|
$data = $this->extractStructuredData($record);
|
|
|
|
return match($this->format) {
|
|
'logfmt' => $this->formatAsLogfmt($data),
|
|
'kv' => $this->formatAsKeyValue($data),
|
|
'array' => $data, // Return raw array for further processing
|
|
default => $this->formatAsLogfmt($data)
|
|
};
|
|
}
|
|
|
|
private function extractStructuredData(LogRecord $record): array
|
|
{
|
|
$data = [];
|
|
|
|
if ($this->includeTimestamp) {
|
|
$data['ts'] = $record->getTimestamp()->format('c');
|
|
}
|
|
|
|
$data['level'] = $record->getLevel()->getName();
|
|
$data['msg'] = $record->getMessage();
|
|
|
|
if ($record->getChannel()) {
|
|
$data['channel'] = $record->getChannel();
|
|
}
|
|
|
|
// Flatten context
|
|
$context = $record->getContext();
|
|
foreach ($context as $key => $value) {
|
|
$data[$this->sanitizeKey($key)] = $this->sanitizeValue($value);
|
|
}
|
|
|
|
// Add extras
|
|
$extras = $record->getExtras();
|
|
foreach ($extras as $key => $value) {
|
|
$data[$this->sanitizeKey($key)] = $this->sanitizeValue($value);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function formatAsLogfmt(array $data): string
|
|
{
|
|
$pairs = [];
|
|
|
|
foreach ($data as $key => $value) {
|
|
if (is_string($value) && (str_contains($value, ' ') || str_contains($value, '='))) {
|
|
$value = '"' . addslashes($value) . '"';
|
|
}
|
|
|
|
$pairs[] = "{$key}={$value}";
|
|
}
|
|
|
|
return implode(' ', $pairs);
|
|
}
|
|
|
|
private function formatAsKeyValue(array $data): string
|
|
{
|
|
$pairs = [];
|
|
|
|
foreach ($data as $key => $value) {
|
|
$pairs[] = "{$key}: {$value}";
|
|
}
|
|
|
|
return implode(', ', $pairs);
|
|
}
|
|
|
|
private function sanitizeKey(string $key): string
|
|
{
|
|
// Remove internal prefixes and make key aggregation-friendly
|
|
$key = ltrim($key, '_');
|
|
|
|
return preg_replace('/[^a-zA-Z0-9_]/', '_', $key);
|
|
}
|
|
|
|
private function sanitizeValue(mixed $value): string
|
|
{
|
|
return match(true) {
|
|
is_string($value) => $value,
|
|
is_numeric($value) => (string) $value,
|
|
is_bool($value) => $value ? 'true' : 'false',
|
|
is_null($value) => 'null',
|
|
is_array($value) => json_encode($value, JSON_UNESCAPED_SLASHES),
|
|
is_object($value) => get_class($value),
|
|
default => 'unknown'
|
|
};
|
|
}
|
|
}
|