Include compiled template code in exception logs (#4122)
authorMatthias Schmidt <gravatronics@live.com>
Thu, 15 Apr 2021 12:19:22 +0000 (14:19 +0200)
committerGitHub <noreply@github.com>
Thu, 15 Apr 2021 12:19:22 +0000 (14:19 +0200)
* Include compiled template code in exception logs

Close #3943

* Highlight error line in exceptions' template context

* Replace `break` with `return` in `getTemplateContextLines()`

wcfsetup/install/files/acp/templates/exceptionLogView.tpl
wcfsetup/install/files/lib/core.functions.php

index 7a26735b7615d0d0cde4bb7a0daee97e1a125c09..2cf2d8818e3a1f60c2e9ce293763f872cfad559e 100644 (file)
                                                {foreach from=$chain[information] item=extraInformation}
                                                        <dl>
                                                                <dt>{$extraInformation[0]}</dt>
-                                                               <dd>{$extraInformation[1]}</dd>
+                                                               <dd style="white-space: pre-wrap;">{$extraInformation[1]}</dd>
                                                        </dl>
                                                {/foreach}
                                        {/if}
index 4cc97ca124838523d52190b9ca635d3929128b0f..b414761add3a7cc8e699a18d7f8176314dc23e2d 100644 (file)
@@ -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;
                                                                        <?php
                                                                }
                                                        }
+
+                                                       $templateContextLines = getTemplateContextLines($e);
+                                                       if (!empty($templateContextLines)) {
+                                                               ?>
+                                                               <li>
+                                                                       <p class="exceptionFieldTitle">Template Context<span class="exceptionColon">:</span></p>
+                                                                       <pre class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(implode("", $templateContextLines));?></pre>
+                                                               </li>
+                                                               <?php
+                                                       }
                                                        ?>
                                                        <li>
                                                                <p class="exceptionFieldTitle">Stack Trace<span class="exceptionColon">:</span></p>