Files
michaelschiemer/src/Framework/Logging/Formatter/JsonFormatter.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

150 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Logging\Formatter;
use App\Framework\Config\Environment;
use App\Framework\Filesystem\Serializers\JsonSerializer;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\Security\SensitiveDataRedactor;
/**
* JSON Formatter für strukturierte Logs
*
* Nutzt JsonSerializer für einheitliche JSON-Ausgabe.
* Produziert konsistente Struktur für Log-Aggregatoren (Elasticsearch, Datadog, etc.)
*
* Standard-Felder für Log-Aggregatoren:
* - @timestamp: Elasticsearch-konformes Zeitstempelfeld
* - severity: RFC 5424 Severity Level (0-7)
* - environment: Deployment-Umgebung (production, staging, development)
* - host: Server-Hostname
* - service: Service-/Anwendungsname
*/
final readonly class JsonFormatter implements LogFormatter
{
private JsonSerializer $serializer;
private string $environment;
private string $host;
private string $serviceName;
private ?SensitiveDataRedactor $redactor;
public function __construct(
private bool $prettyPrint = false,
private bool $includeExtras = true,
private bool $flattenContext = true,
?Environment $env = null,
?string $serviceName = null,
private bool $redactSensitiveData = false,
?SensitiveDataRedactor $redactor = null
) {
$this->serializer = $prettyPrint
? JsonSerializer::pretty()
: JsonSerializer::compact();
// Environment Detection (production, staging, development)
$this->environment = $env?->getString('APP_ENV', 'production') ?? 'production';
// Host Detection
$this->host = gethostname() ?: 'unknown';
// Service Name (default: app name from env)
$this->serviceName = $serviceName ?? $env?->getString('APP_NAME', 'app') ?? 'app';
// Redactor Setup: Auto-select based on environment if not provided
if ($redactSensitiveData && $redactor === null) {
$this->redactor = match ($this->environment) {
'production' => SensitiveDataRedactor::production(),
'testing' => SensitiveDataRedactor::testing(),
default => SensitiveDataRedactor::development(),
};
} else {
$this->redactor = $redactor;
}
}
public function __invoke(LogRecord $record): string
{
$data = $this->formatRecord($record);
return $this->serializer->serialize($data);
}
/**
* Formatiert LogRecord zu einheitlichem Array für JSON-Serialisierung
*
* @return array<string, mixed>
*/
private function formatRecord(LogRecord $record): array
{
$timestamp = $record->timestamp->format('c'); // ISO 8601
$data = [
// Standard Log Fields
'timestamp' => $timestamp,
'@timestamp' => $timestamp, // Elasticsearch convention
'level' => $record->level->getName(),
'level_value' => $record->level->value,
'severity' => $record->level->toRFC5424(), // RFC 5424 (0-7)
'channel' => $record->channel,
'message' => $this->redactMessage($record->message),
// Infrastructure Fields (for log aggregators)
'environment' => $this->environment,
'host' => $this->host,
'service' => $this->serviceName,
];
// Correlation ID (top-level für bessere Filterbarkeit)
if ($record->context->hasCorrelationId()) {
$data['correlation.id'] = $record->context->getCorrelationId()->toString();
}
// Context hinzufügen mit optionalem Flatten
$context = $record->context->toArray();
if ($this->flattenContext && isset($context['structured'])) {
// Flatten: Nur strukturierte Daten für bessere Aggregator-Kompatibilität
$data['context'] = $this->redactData($context['structured']);
} else {
// Raw: Gesamtes LogContext-Array
$data['context'] = $this->redactData($context);
}
// Extras hinzufügen (wenn aktiviert und vorhanden)
if ($this->includeExtras && !empty($record->extra)) {
$data['extra'] = $this->redactData($record->extra);
}
return $data;
}
/**
* Redacted Message falls Redactor aktiviert
*/
private function redactMessage(string $message): string
{
if ($this->redactor === null) {
return $message;
}
return $this->redactor->redactString($message);
}
/**
* Redacted Array-Daten falls Redactor aktiviert
*
* @param array<string, mixed> $data
* @return array<string, mixed>
*/
private function redactData(array $data): array
{
if ($this->redactor === null) {
return $data;
}
return $this->redactor->redact($data);
}
}