From 5f9ea37a3d59f854b0ee8446e51e81a2718b85f8 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Thu, 15 Apr 2021 14:19:22 +0200 Subject: [PATCH] Include compiled template code in exception logs (#4122) * Include compiled template code in exception logs Close #3943 * Highlight error line in exceptions' template context * Replace `break` with `return` in `getTemplateContextLines()` --- .../files/acp/templates/exceptionLogView.tpl | 2 +- wcfsetup/install/files/lib/core.functions.php | 91 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/wcfsetup/install/files/acp/templates/exceptionLogView.tpl b/wcfsetup/install/files/acp/templates/exceptionLogView.tpl index 7a26735b76..2cf2d8818e 100644 --- a/wcfsetup/install/files/acp/templates/exceptionLogView.tpl +++ b/wcfsetup/install/files/acp/templates/exceptionLogView.tpl @@ -133,7 +133,7 @@ {foreach from=$chain[information] item=extraInformation}
{$extraInformation[0]}
-
{$extraInformation[1]}
+
{$extraInformation[1]}
{/foreach} {/if} diff --git a/wcfsetup/install/files/lib/core.functions.php b/wcfsetup/install/files/lib/core.functions.php index 4cc97ca124..b414761add 100644 --- a/wcfsetup/install/files/lib/core.functions.php +++ b/wcfsetup/install/files/lib/core.functions.php @@ -125,6 +125,62 @@ namespace wcf\functions\exception { use wcf\system\exception\SystemException; use wcf\util\FileUtil; use wcf\util\StringUtil; + + /** + * If the stacktrace contains a compiled template, the context of the relevant template line + * is returned, otherwise an empty array is returned. + */ + function getTemplateContextLines(\Throwable $e): array { + try { + $contextLineCount = 5; + foreach ($e->getTrace() as $traceEntry) { + if (isset($traceEntry['file']) && \preg_match('~/templates/compiled/.+\.php$~', + $traceEntry['file'])) { + $startLine = $traceEntry['line'] - $contextLineCount; + $relativeErrorLine = $contextLineCount; + if ($startLine < 0) { + $startLine = 0; + $relativeErrorLine = $traceEntry['line'] - 1; + } + + $file = \fopen($traceEntry['file'], 'r'); + if (!$file) { + return []; + } + + for ($line = 0; $line < $startLine; $line++) { + if (\substr(\fgets($file, 1024), -1) !== "\n") { + // We don't want to handle a file where lines exceed 1024 Bytes. + return []; + } + } + + $maxLineCount = 2 * $contextLineCount + 1; + $lines = []; + while (!\feof($file) && \count($lines) < $maxLineCount) { + $line = \fgets($file, 1024); + if (\substr($line, -1) !== "\n" && !\feof($file)) { + // We don't want to handle a file where lines exceed 1024 Bytes. + return []; + } + + if (count($lines) === $relativeErrorLine - 1) { + $line = "====> {$line}"; + } + + $lines[] = $line; + } + + return $lines; + } + } + } + catch (\Throwable $e) { + // Ignore errors while extracting the template context to be saved in the exception log. + } + + return []; + } /** * Logs the given Throwable. @@ -140,6 +196,24 @@ namespace wcf\functions\exception { return str_replace("\n", ' ', $item); }; + $getExtraInformation = function (\Throwable $e) { + $extraInformation = []; + + if ($e instanceof IExtraInformationException) { + $extraInformation = $e->getExtraInformation(); + } + + $templateContextLines = getTemplateContextLines($e); + if (!empty($templateContextLines)) { + $extraInformation[] = [ + 'Template Context', + \implode("", $templateContextLines), + ]; + } + + return !empty($extraInformation) ? base64_encode(serialize($extraInformation)) : "-"; + }; + // don't forget to update ExceptionLogUtil / ExceptionLogViewPage, when changing the log file format $message = gmdate('r', TIME_NOW)."\n". 'Message: '.$stripNewlines($e->getMessage())."\n". @@ -156,7 +230,7 @@ namespace wcf\functions\exception { 'Error Message: '.$stripNewlines($prev->getMessage())."\n". 'Error Code: '.$stripNewlines($prev->getCode())."\n". 'File: '.$stripNewlines($prev->getFile()).' ('.$prev->getLine().')'."\n". - 'Extra Information: '.($prev instanceof IExtraInformationException ? base64_encode(serialize($prev->getExtraInformation())) : '-')."\n". + 'Extra Information: ' . $getExtraInformation($prev) . "\n". 'Stack Trace: '.json_encode(array_map(function ($item) { $item['args'] = array_map(function ($item) { switch (gettype($item)) { @@ -343,6 +417,11 @@ EXPLANATION; min-height: 1.5em; } + pre.exceptionFieldValue { + font-size: 14px; + white-space: pre-wrap; + } + .exceptionSystemInformation, .exceptionErrorDetails, .exceptionStacktrace { @@ -548,6 +627,16 @@ EXPLANATION; +
  • +

    Template Context:

    +
    +
  • +
  • Stack Trace:

    -- 2.20.1