logFilePath = ini_get('error_log'); } public function getParsedLogFile() { $parsedLogs = []; $logFilePath = $this->logFilePath; // error_log can be disabled (empty ini value), set to "syslog", or // point at a file that no longer exists — there is nothing to parse, // and file_get_contents would warn on those values. if (empty($logFilePath) || !is_string($logFilePath) || 'syslog' === $logFilePath || !is_file($logFilePath)) { return $parsedLogs; } // A long-lived error log can grow to gigabytes; parsing it whole // exhausts memory on the request that reads it. Read only the // trailing window — recent entries are what the admin UI shows. $tail_bytes = 1024 * 1024; $size = filesize($logFilePath); $content = false; if (false !== $size && $size > $tail_bytes) { $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 if (false !== $handle) { fseek($handle, $size - $tail_bytes); $content = stream_get_contents($handle); fclose($handle); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- operates on a native stream handle // Drop the leading partial line the window cut into. if (false !== $content && false !== strpos($content, "\n")) { $content = substr($content, strpos($content, "\n") + 1); } } } else { $content = @file_get_contents($logFilePath); } if (false === $content || '' === $content) { return $parsedLogs; } $logFileHandle = explode("\n", $content); foreach ($logFileHandle as $id => $currentLine) { // Normal error log line starts with the date & time in [] if ('[' === ($currentLine[0] ?? '')) { // if (10000 === \count($parsedLogs)) { // return $parsedLogs; // } // Get the datetime when the error occurred and convert it to // the site's timezone. try { $dateArr = []; preg_match('~^\[(.*?)\]~', $currentLine, $dateArr); $errorDateTime = ''; if (!empty($dateArr[1])) { $currentLine = str_replace($dateArr[0], '', $currentLine); $currentLine = trim($currentLine); $errorDateTime = new DateTime($dateArr[1]); $timezone = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); $errorDateTime->setTimezone($timezone); $errorDateTime = $errorDateTime->format('Y-m-d H:i:s'); } } catch (\Exception $e) { $errorDateTime = ''; } // Get the type of the error if (false !== strpos($currentLine, 'PHP Warning')) { $currentLine = str_replace('PHP Warning:', '', $currentLine); $currentLine = trim($currentLine); $errorType = 'WARNING'; } else if (false !== strpos($currentLine, 'PHP Notice')) { $currentLine = str_replace('PHP Notice:', '', $currentLine); $currentLine = trim($currentLine); $errorType = 'NOTICE'; } else if (false !== strpos($currentLine, 'PHP Fatal error')) { $currentLine = str_replace('PHP Fatal error:', '', $currentLine); $currentLine = trim($currentLine); $errorType = 'FATAL'; } else if (false !== strpos($currentLine, 'PHP Parse error')) { $currentLine = str_replace('PHP Parse error:', '', $currentLine); $currentLine = trim($currentLine); $errorType = 'SYNTAX'; } else if (false !== strpos($currentLine, 'PHP Exception')) { $currentLine = str_replace('PHP Exception:', '', $currentLine); $currentLine = trim($currentLine); $errorType = 'EXCEPTION'; } else { $errorType = 'UNKNOWN'; } if (false !== strpos($currentLine, ' on line ')) { $errorLine = explode(' on line ', $currentLine); $errorLine = trim($errorLine[1]); $currentLine = str_replace(' on line ' . $errorLine, '', $currentLine); } else { $errorLine = substr($currentLine, strrpos($currentLine, ':') + 1); $currentLine = str_replace(':' . $errorLine, '', $currentLine); } $errorFile = explode(' in /', $currentLine, 2); if (isset($errorFile[1])) { $errorFile = '/' . trim($errorFile[1]); $currentLine = str_replace(' in ' . $errorFile, '', $currentLine); } else { $errorFile = ''; } // The message of the error $errorMessage = trim($currentLine); $parsedLogs[] = [ 'id' => $id+1, 'dateTime' => $errorDateTime, 'type' => $errorType, 'file' => $errorFile, 'line' => (int)$errorLine, 'message' => $errorMessage, 'stackTrace' => [] ]; } // Stack trace beginning line else if ('Stack trace:' === $currentLine) { $parsedLogsLastKey = array_key_last($parsedLogs); if (null === $parsedLogsLastKey) { continue; } $stackTraceLineNumber = 0; for ($trace_index = $id + 1, $trace_count = count($logFileHandle); $trace_index < $trace_count; $trace_index++) { $trace_line = trim($logFileHandle[$trace_index]); if ('' === $trace_line) { continue; } if ('#' === $trace_line[0]) { $prefix = '#' . $stackTraceLineNumber; if (0 === strpos($trace_line, $prefix)) { $trace_line = substr($trace_line, strlen($prefix)); } $parsedLogs[$parsedLogsLastKey]['stackTrace'][] = trim($trace_line); $stackTraceLineNumber++; continue; } if (0 === strpos($trace_line, 'thrown in ')) { $parsedLogs[$parsedLogsLastKey]['stackTrace'][] = $trace_line; } break; } } } return $parsedLogs; } }