← Code-Übersicht

JsonLineLogger.php

Pfad: src/Infrastructure/Rechnung/JsonLineLogger.php

Ext: php

Größe: 2908 Bytes

Geändert: 2026-07-16 17:37:59+02

<?php
declare(strict_types=1);

namespace Demo\Infrastructure\Rechnung;

use Demo\Domain\Rechnung\InvoiceLoggerPort;
use RuntimeException;

final class JsonLineLogger implements InvoiceLoggerPort
{
    public function __construct(private readonly string $logPath)
    {
        if ($this->logPath === '') {
            throw new RuntimeException('Logpfad fehlt');
        }
    }

    /** @param array<string,mixed> $context */
    public function logStep(string $runId, string $step, string $status, array $context = []): void
    {
        $record = [
            'timestamp' => gmdate('c'),
            'runId' => $runId,
            'step' => $step,
            'status' => $status,
            'context' => $this->redact($context),
        ];
        $line = json_encode($record, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n";
        $handle = @fopen($this->logPath, 'ab');
        if ($handle === false) {
            throw new RuntimeException('Auditlog kann nicht geoeffnet werden');
        }
        try {
            if (!flock($handle, LOCK_EX)) {
                throw new RuntimeException('Auditlog kann nicht gesperrt werden');
            }
            $remaining = $line;
            while ($remaining !== '') {
                $written = @fwrite($handle, $remaining);
                if ($written === false || $written === 0) {
                    throw new RuntimeException('Auditlog kann nicht geschrieben werden');
                }
                $remaining = substr($remaining, $written);
            }
            if (!fflush($handle)) {
                throw new RuntimeException('Auditlog kann nicht persistiert werden');
            }
            if (function_exists('fsync') && !fsync($handle)) {
                throw new RuntimeException('Auditlog kann nicht synchronisiert werden');
            }
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }

    /**
     * @param array<string,mixed> $values
     * @return array<string,mixed>
     */
    private function redact(array $values): array
    {
        $redacted = [];
        foreach ($values as $key => $value) {
            if (preg_match('/password|secret|token|authorization|api.?key|cookie/i', $key) === 1) {
                $redacted[$key] = '[REDACTED]';
                continue;
            }
            if (is_array($value)) {
                $redacted[$key] = $this->redact($value);
                continue;
            }
            $redacted[$key] = is_string($value) ? $this->redactText($value) : $value;
        }
        return $redacted;
    }

    private function redactText(string $value): string
    {
        $value = preg_replace('/Bearer\s+[A-Za-z0-9._~+\/-]+/i', 'Bearer [REDACTED]', $value) ?? $value;
        return preg_replace('#(https?://)[^/@\s]+:[^/@\s]+@#i', '$1[REDACTED]@', $value) ?? $value;
    }
}

Frühere Versionen

Version Zeitpunkt Operation
42 2026-07-16 19:55:01.889455+02 UPDATE