Files
michaelschiemer/src/Framework/Logging/Formatter/StructuredFormatter.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

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'
};
}
}