| @@ -12,26 +12,64 @@ | ||
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | public function getParsedLogFile() { |
| 15 | 15 | $parsedLogs = []; |
| 16 | - $logFileHandle = explode("\n", file_get_contents($this->logFilePath)); | |
| 16 | + $logFilePath = $this->logFilePath; | |
| 17 | 17 | |
| 18 | + // error_log can be disabled (empty ini value), set to "syslog", or | |
| 19 | + // point at a file that no longer exists — there is nothing to parse, | |
| 20 | + // and file_get_contents would warn on those values. | |
| 21 | + if (empty($logFilePath) || !is_string($logFilePath) || 'syslog' === $logFilePath || !is_file($logFilePath)) { | |
| 22 | + return $parsedLogs; | |
| 23 | + } | |
| 24 | + | |
| 25 | + // A long-lived error log can grow to gigabytes; parsing it whole | |
| 26 | + // exhausts memory on the request that reads it. Read only the | |
| 27 | + // trailing window — recent entries are what the admin UI shows. | |
| 28 | + $tail_bytes = 1024 * 1024; | |
| 29 | + $size = filesize($logFilePath); | |
| 30 | + $content = false; | |
| 31 | + if (false !== $size && $size > $tail_bytes) { | |
| 32 | + $handle = @fopen($logFilePath, 'rb'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- streaming file I/O; WP_Filesystem cannot return a raw stream handle | |
| 33 | + if (false !== $handle) { | |
| 34 | + fseek($handle, $size - $tail_bytes); | |
| 35 | + $content = stream_get_contents($handle); | |
| 36 | + fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle | |
| 37 | + // Drop the leading partial line the window cut into. | |
| 38 | + if (false !== $content && false !== strpos($content, "\n")) { | |
| 39 | + $content = substr($content, strpos($content, "\n") + 1); | |
| 40 | + } | |
| 41 | + } | |
| 42 | + } else { | |
| 43 | + $content = @file_get_contents($logFilePath); | |
| 44 | + } | |
| 45 | + if (false === $content || '' === $content) { | |
| 46 | + return $parsedLogs; | |
| 47 | + } | |
| 48 | + | |
| 49 | + $logFileHandle = explode("\n", $content); | |
| 50 | + | |
| 18 | 51 | foreach ($logFileHandle as $id => $currentLine) { |
| 19 | 52 | // Normal error log line starts with the date & time in [] |
| 20 | - if ('[' === @$currentLine[0]) { | |
| 53 | + if ('[' === ($currentLine[0] ?? '')) { | |
| 21 | 54 | // if (10000 === \count($parsedLogs)) { |
| 22 | 55 | // return $parsedLogs; |
| 23 | 56 | // } |
| 24 | 57 | |
| 25 | - // Get the datetime when the error occurred and convert it to berlin timezone | |
| 58 | + // Get the datetime when the error occurred and convert it to | |
| 59 | + // the site's timezone. | |
| 26 | 60 | try { |
| 27 | 61 | $dateArr = []; |
| 28 | 62 | preg_match('~^\[(.*?)\]~', $currentLine, $dateArr); |
| 29 | - $currentLine = str_replace($dateArr[0], '', $currentLine); | |
| 30 | - $currentLine = trim($currentLine); | |
| 31 | - $errorDateTime = new DateTime($dateArr[1]); | |
| 32 | - $errorDateTime->setTimezone(new DateTimeZone('Europe/Berlin')); | |
| 33 | - $errorDateTime = $errorDateTime->format('Y-m-d H:i:s'); | |
| 63 | + $errorDateTime = ''; | |
| 64 | + if (!empty($dateArr[1])) { | |
| 65 | + $currentLine = str_replace($dateArr[0], '', $currentLine); | |
| 66 | + $currentLine = trim($currentLine); | |
| 67 | + $errorDateTime = new DateTime($dateArr[1]); | |
| 68 | + $timezone = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); | |
| 69 | + $errorDateTime->setTimezone($timezone); | |
| 70 | + $errorDateTime = $errorDateTime->format('Y-m-d H:i:s'); | |
| 71 | + } | |
| 34 | 72 | } catch (\Exception $e) { |
| 35 | 73 | $errorDateTime = ''; |
| 36 | 74 | } |
| 37 | 75 | |
| @@ -68,11 +106,15 @@ | ||
| 68 | 106 | $errorLine = substr($currentLine, strrpos($currentLine, ':') + 1); |
| 69 | 107 | $currentLine = str_replace(':' . $errorLine, '', $currentLine); |
| 70 | 108 | } |
| 71 | 109 | |
| 72 | - $errorFile = explode(' in /', $currentLine); | |
| 73 | - $errorFile = '/' . trim($errorFile[1]); | |
| 74 | - $currentLine = str_replace(' in ' . $errorFile, '', $currentLine); | |
| 110 | + $errorFile = explode(' in /', $currentLine, 2); | |
| 111 | + if (isset($errorFile[1])) { | |
| 112 | + $errorFile = '/' . trim($errorFile[1]); | |
| 113 | + $currentLine = str_replace(' in ' . $errorFile, '', $currentLine); | |
| 114 | + } else { | |
| 115 | + $errorFile = ''; | |
| 116 | + } | |
| 75 | 117 | |
| 76 | 118 | // The message of the error |
| 77 | 119 | $errorMessage = trim($currentLine); |
| 78 | 120 | |
| @@ -86,24 +128,32 @@ | ||
| 86 | 128 | 'stackTrace' => [] |
| 87 | 129 | ]; |
| 88 | 130 | } // Stack trace beginning line |
| 89 | 131 | else if ('Stack trace:' === $currentLine) { |
| 132 | + $parsedLogsLastKey = array_key_last($parsedLogs); | |
| 133 | + if (null === $parsedLogsLastKey) { | |
| 134 | + continue; | |
| 135 | + } | |
| 136 | + | |
| 90 | 137 | $stackTraceLineNumber = 0; |
| 91 | - | |
| 92 | - foreach ($logFileHandle as $line) { | |
| 93 | - $currentLine = str_replace(PHP_EOL, '', $line); | |
| 94 | - | |
| 95 | - // If the current line is a stack trace line | |
| 96 | - if ('#' === $currentLine[0]) { | |
| 97 | - $parsedLogsLastKey = key($parsedLogs); | |
| 98 | - $currentLine = str_replace('#' . $stackTraceLineNumber, '', $currentLine); | |
| 99 | - $parsedLogs[$parsedLogsLastKey]['stackTrace'][] = trim($currentLine); | |
| 100 | - | |
| 138 | + for ($trace_index = $id + 1, $trace_count = count($logFileHandle); $trace_index < $trace_count; $trace_index++) { | |
| 139 | + $trace_line = trim($logFileHandle[$trace_index]); | |
| 140 | + if ('' === $trace_line) { | |
| 141 | + continue; | |
| 142 | + } | |
| 143 | + if ('#' === $trace_line[0]) { | |
| 144 | + $prefix = '#' . $stackTraceLineNumber; | |
| 145 | + if (0 === strpos($trace_line, $prefix)) { | |
| 146 | + $trace_line = substr($trace_line, strlen($prefix)); | |
| 147 | + } | |
| 148 | + $parsedLogs[$parsedLogsLastKey]['stackTrace'][] = trim($trace_line); | |
| 101 | 149 | $stackTraceLineNumber++; |
| 102 | - } // If the current line is the last stack trace ('thrown in...') | |
| 103 | - else { | |
| 104 | - break; | |
| 150 | + continue; | |
| 105 | 151 | } |
| 152 | + if (0 === strpos($trace_line, 'thrown in ')) { | |
| 153 | + $parsedLogs[$parsedLogsLastKey]['stackTrace'][] = $trace_line; | |
| 154 | + } | |
| 155 | + break; | |
| 106 | 156 | } |
| 107 | 157 | } |
| 108 | 158 | } |
| 109 | 159 | |