$snapshot */ public static function formatSnapshot(array $snapshot): string { $status = isset($snapshot['status']) && is_string($snapshot['status']) ? $snapshot['status'] : 'unreadable'; if ($status === 'missing') { return 'No log file available'; } if ($status !== 'ok') { return 'Log file not readable'; } $rawEntries = isset($snapshot['error_entries']) && is_array($snapshot['error_entries']) ? $snapshot['error_entries'] : array(); $entries = array(); foreach ($rawEntries as $rawEntry) { if (is_array($rawEntry)) { $lines = array_values(array_filter($rawEntry, 'is_string')); if ($lines !== array()) { $entries[] = $lines; } } } $rawRecent = isset($snapshot['recent_lines']) && is_array($snapshot['recent_lines']) ? $snapshot['recent_lines'] : array(); $recent = array_values(array_filter($rawRecent, 'is_string')); if ($entries === array()) { if ($recent === array()) { return 'Log file is empty'; } return trim('No ERROR/WARN entries found. Last ' . count($recent) . " log lines:\n\n" . implode('', $recent)); } $output = 'Last ' . count($entries) . " ERROR/WARN entries:\n\n"; foreach ($entries as $entry) { $output .= implode("\n", $entry) . "\n\n"; } if ($recent !== array()) { $output .= 'Recent context (last ' . count($recent) . " lines):\n\n" . implode('', $recent); } $latest = isset($snapshot['line']) && is_string($snapshot['line']) ? $snapshot['line'] : ''; if ($latest !== '') { $bounded = substr($latest, 0, self::LATEST_ERROR_MAX_BYTES); $output .= "\n\nLatest reportable ERROR evidence:\n\n" . $bounded; if (strlen($latest) > strlen($bounded)) { $output .= "\n[404 Solution] Latest ERROR evidence truncated to " . self::LATEST_ERROR_MAX_BYTES . ' bytes.'; } } return trim($output); } /** * The excerpt, tail-bounded, or a one-line reason. * * @param string $context Which collector is asking, for the warning log. * @param int $maxBytes Tail bound for the excerpt itself; 0 for unbounded. * @return string Never empty. */ public static function resolve(string $context, int $maxBytes = 0): string { if (!function_exists('abj_service_optional')) { return self::REASON_PREFIX . 'the plugin service container is not loaded in this request.'; } $logger = abj_service_optional('logging'); if (!is_object($logger) || !method_exists($logger, 'getSanitizedLogExcerptForSupport')) { return self::REASON_PREFIX . 'the logging service is not available on this install.'; } try { $excerpt = $logger->getSanitizedLogExcerptForSupport(); if (!is_string($excerpt)) { return self::REASON_PREFIX . 'the logging service returned no readable excerpt.'; } if ($excerpt === '') { return self::REASON_PREFIX . 'the log excerpt came back empty.'; } return $maxBytes > 0 && strlen($excerpt) > $maxBytes ? substr($excerpt, -$maxBytes) : $excerpt; } catch (\Throwable $e) { ABJ_404_Solution_FeedbackTransportLog::log( 'warn', $context . ' debug-log excerpt unavailable: ' . $e->getMessage() ); // The underlying message is carried, not just logged: the site that // hit this is the one site whose log we cannot read, so the debug // log is exactly where this explanation would NOT reach anyone. return self::REASON_PREFIX . 'reading it failed (' . substr($e->getMessage(), 0, self::MAX_REASON_DETAIL_LENGTH) . ').'; } } }