regexMatch("^\d{4}[-]\d{2}[-]\d{2} .*\(\w+\):\s.*$", $line); } /** * Append one continuation chunk to an entry held as a single text block. * * @param bool $truncated Set to true once the entry reaches its byte budget. */ public static function appendToText(string &$entry, string $line, bool &$truncated): void { if ($truncated) { return; } // Physical log lines already end in their own newline, so a // continuation is appended verbatim. Injecting a separator here // corrupted the later chunks of one over-long physical line, and // every consumer (dedupe signature, evidence budget, plain-text // support excerpt) reads the entry as text, not HTML. $addition = $line; $available = self::ERROR_EXCERPT_MAX_BYTES - strlen($entry); if (strlen($addition) <= $available) { $entry .= $addition; return; } $truncated = true; $markerBytes = strlen(self::TRUNCATION_MARKER); $keepExisting = max(0, self::ERROR_EXCERPT_MAX_BYTES - $markerBytes); $entry = substr($entry, 0, $keepExisting) . self::TRUNCATION_MARKER; } /** * Append one continuation chunk to an entry held as a list of lines. * * @param array $entry * @param int $entryBytes Running byte count of $entry, kept in step. * @param bool $truncated Set to true once the entry reaches its byte budget. */ public static function appendToLines( array &$entry, int &$entryBytes, bool &$truncated, string $line ): void { if ($truncated) { return; } if ($entryBytes >= self::ERROR_EXCERPT_MAX_BYTES) { self::markLinesTruncated($entry, $entryBytes, $truncated); return; } $available = self::ERROR_EXCERPT_MAX_BYTES - $entryBytes; if (strlen($line) <= $available) { $entry[] = $line; $entryBytes += strlen($line); return; } self::markLinesTruncated($entry, $entryBytes, $truncated); } /** @param array $entry */ private static function markLinesTruncated(array &$entry, int &$entryBytes, bool &$truncated): void { if ($truncated) { return; } $targetBytes = max(0, self::ERROR_EXCERPT_MAX_BYTES - strlen(self::TRUNCATION_MARKER)); $excessBytes = max(0, $entryBytes - $targetBytes); while ($excessBytes > 0 && $entry !== array()) { $lastKey = array_key_last($entry); $last = (string)$entry[$lastKey]; $lastBytes = strlen($last); if ($lastBytes <= $excessBytes) { array_pop($entry); $entryBytes -= $lastBytes; $excessBytes -= $lastBytes; continue; } $retainedBytes = $lastBytes - $excessBytes; $entry[$lastKey] = (string)substr($last, 0, $retainedBytes); $entryBytes -= $excessBytes; $excessBytes = 0; } if ($entry === array()) { $entry[] = self::TRUNCATION_MARKER; } else { $lastKey = array_key_last($entry); $entry[$lastKey] .= self::TRUNCATION_MARKER; } $entryBytes += strlen(self::TRUNCATION_MARKER); $truncated = true; } }