← Code-Übersicht

InvoiceLogAlertChecker.php

Pfad: src/Operations/InvoiceLogAlertChecker.php

Ext: php

Größe: 2887 Bytes

Geändert: 2026-07-16 18:07:46+02

<?php

declare(strict_types=1);

namespace Demo\Operations;

use JsonException;
use RuntimeException;

final class InvoiceLogAlertChecker
{
    /**
     * @return array{events: int, alerts: list<array{runId: string, code: string, phase: string}>}
     */
    public function check(string $path): array
    {
        $handle = @fopen($path, 'rb');
        if ($handle === false) {
            throw new RuntimeException('Audit-Log ist nicht lesbar.');
        }

        $events = 0;
        $alerts = [];
        try {
            while (($line = fgets($handle)) !== false) {
                if (trim($line) === '') {
                    continue;
                }
                ++$events;
                try {
                    $event = json_decode($line, true, 32, JSON_THROW_ON_ERROR);
                } catch (JsonException) {
                    $alerts[] = $this->alert('unknown', 'UNKNOWN_LOG_FORMAT', 'audit');
                    continue;
                }
                if (!is_array($event)) {
                    $alerts[] = $this->alert('unknown', 'UNKNOWN_LOG_FORMAT', 'audit');
                    continue;
                }

                $runId = $this->scalar($event, ['runId', 'run_id', 'correlationId']) ?? 'unknown';
                $code = $this->scalar($event, ['errorCode', 'error_code', 'code']) ?? '';
                $phase = $this->scalar($event, ['phase']) ?? 'unknown';
                if ($this->requiresAlert($code)) {
                    $alerts[] = $this->alert($runId, $code, $phase);
                }
            }
            if (!feof($handle)) {
                throw new RuntimeException('Audit-Log konnte nicht vollstaendig gelesen werden.');
            }
        } finally {
            fclose($handle);
        }

        return ['events' => $events, 'alerts' => $alerts];
    }

    private function requiresAlert(string $code): bool
    {
        $code = strtoupper($code);
        return str_contains($code, 'UNKNOWN') || str_contains($code, 'AUTH');
    }

    /**
     * @param array<array-key, mixed> $values
     * @param list<string> $keys
     */
    private function scalar(array $values, array $keys): ?string
    {
        foreach ($keys as $key) {
            $value = $values[$key] ?? null;
            if (is_string($value) || is_int($value)) {
                return (string) $value;
            }
        }
        foreach ($values as $value) {
            if (is_array($value)) {
                $found = $this->scalar($value, $keys);
                if ($found !== null) {
                    return $found;
                }
            }
        }
        return null;
    }

    /** @return array{runId: string, code: string, phase: string} */
    private function alert(string $runId, string $code, string $phase): array
    {
        return ['runId' => $runId, 'code' => $code, 'phase' => $phase];
    }
}