From eea6d85d8d6b117761dd747ebf91b3e7a07575fa Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Mon, 4 Nov 2024 15:54:38 +0100 Subject: [PATCH] Add api endpoint for exception details --- .../templates/shared_exceptionLogDetails.tpl | 59 +++++++++++++ .../Core/Api/Exceptions/RenderException.ts | 28 +++++++ .../Core/Api/Exceptions/RenderException.js | 24 ++++++ .../files/lib/bootstrap/com.woltlab.wcf.php | 1 + .../core/exceptions/RenderException.class.php | 84 +++++++++++++++++++ 5 files changed, 196 insertions(+) create mode 100644 com.woltlab.wcf/templates/shared_exceptionLogDetails.tpl create mode 100644 ts/WoltLabSuite/Core/Api/Exceptions/RenderException.ts create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Api/Exceptions/RenderException.js create mode 100644 wcfsetup/install/files/lib/system/endpoint/controller/core/exceptions/RenderException.class.php diff --git a/com.woltlab.wcf/templates/shared_exceptionLogDetails.tpl b/com.woltlab.wcf/templates/shared_exceptionLogDetails.tpl new file mode 100644 index 0000000000..0f8df80d2d --- /dev/null +++ b/com.woltlab.wcf/templates/shared_exceptionLogDetails.tpl @@ -0,0 +1,59 @@ +
+
+
+ + +
+
+
+
{lang}wcf.acp.exceptionLog.exception.date{/lang}
+
{$exception[date]|plainTime}
+
+
+
{lang}wcf.acp.exceptionLog.exception.requestURI{/lang}
+
{$exception[requestURI]}
+
+
+
{lang}wcf.acp.exceptionLog.exception.referrer{/lang}
+
{$exception[referrer]}
+
+
+
{lang}wcf.acp.exceptionLog.exception.userAgent{/lang}
+
{$exception[userAgent]}
+
+
+
{lang}wcf.acp.exceptionLog.exception.memory{/lang}
+
{$exception[peakMemory]|filesizeBinary} / {if $exception[maxMemory] == -1}∞{else}{$exception[maxMemory]|filesizeBinary}{/if}
+
+{foreach from=$exception[chain] item=chain} +
+
{lang}wcf.acp.exceptionLog.exception.message{/lang}
+
{$chain[message]}
+
+
+
{lang}wcf.acp.exceptionLog.exception.class{/lang}
+
{$chain[class]}
+
+
+
{lang}wcf.acp.exceptionLog.exception.file{/lang}
+
{$chain[file]} ({$chain[line]})
+
+ {if !$chain[information]|empty} + {foreach from=$chain[information] item=extraInformation} +
+
{$extraInformation[0]}
+
{$extraInformation[1]}
+
+ {/foreach} + {/if} +
+
{lang}wcf.acp.exceptionLog.exception.stacktrace{/lang}
+
+
    + {foreach from=$chain[stack] item=stack} +
  1. {$stack[file]} ({$stack[line]}): {$stack[class]}{$stack[type]}{$stack[function]}(…)
  2. + {/foreach} +
+
+
+{/foreach} diff --git a/ts/WoltLabSuite/Core/Api/Exceptions/RenderException.ts b/ts/WoltLabSuite/Core/Api/Exceptions/RenderException.ts new file mode 100644 index 0000000000..fc309d071a --- /dev/null +++ b/ts/WoltLabSuite/Core/Api/Exceptions/RenderException.ts @@ -0,0 +1,28 @@ +/** + * Gets the html code for the rendering of a exception log entry. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 + */ + +import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; +import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result"; + +type Response = { + template: string; +}; + +export async function renderException(exceptionId: string): Promise> { + const url = new URL(`${window.WSC_RPC_API_URL}core/exceptions/${exceptionId}/render`); + + let response: Response; + try { + response = (await prepareRequest(url).get().fetchAsJson()) as Response; + } catch (e) { + return apiResultFromError(e); + } + + return apiResultFromValue(response); +} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Exceptions/RenderException.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Exceptions/RenderException.js new file mode 100644 index 0000000000..360833b2c2 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Exceptions/RenderException.js @@ -0,0 +1,24 @@ +/** + * Gets the html code for the rendering of a exception log entry. + * + * @author Marcel Werk + * @copyright 2001-2024 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 + */ +define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.renderException = renderException; + async function renderException(exceptionId) { + const url = new URL(`${window.WSC_RPC_API_URL}core/exceptions/${exceptionId}/render`); + let response; + try { + response = (await (0, Backend_1.prepareRequest)(url).get().fetchAsJson()); + } + catch (e) { + return (0, Result_1.apiResultFromError)(e); + } + return (0, Result_1.apiResultFromValue)(response); + } +}); diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index 98da328d96..9cefa27960 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -134,6 +134,7 @@ return static function (): void { $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponse); $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponses); $event->register(new \wcf\system\endpoint\controller\core\comments\responses\UpdateResponse); + $event->register(new \wcf\system\endpoint\controller\core\exceptions\RenderException); $event->register(new \wcf\system\endpoint\controller\core\gridViews\GetRows); $event->register(new \wcf\system\endpoint\controller\core\messages\GetMentionSuggestions); $event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/exceptions/RenderException.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/exceptions/RenderException.class.php new file mode 100644 index 0000000000..39afb20410 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/exceptions/RenderException.class.php @@ -0,0 +1,84 @@ + + * @since 6.1 + */ +#[GetRequest('/core/exceptions/{id:[a-f0-9]{40}}/render')] +final class RenderException implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $this->assertExceptionLogIsAccessible(); + + $exception = $this->getException($variables['id']); + if (!$exception) { + throw new IllegalLinkException(); + } + + return new JsonResponse([ + 'template' => WCF::getTPL()->fetch('shared_exceptionLogDetails', 'wcf', [ + 'exception' => $exception, + 'exceptionID' => $variables['id'], + ]) + ]); + } + + private function assertExceptionLogIsAccessible(): void + { + if (!WCF::getSession()->getPermission('admin.management.canViewLog')) { + throw new PermissionDeniedException(); + } + } + + private function getException(string $exceptionID): ?array + { + $logFile = $this->getLogFile($exceptionID); + if (!$logFile) { + return null; + } + + try { + $exceptions = ExceptionLogUtil::splitLog(\file_get_contents($logFile)); + + return ExceptionLogUtil::parseException($exceptions[$exceptionID]); + } catch (\Exception $e) { + return null; + } + } + + private function getLogFile(string $exceptionID): ?string + { + $fileNameRegex = new Regex('(?:^|/)\d{4}-\d{2}-\d{2}\.txt$'); + $logFiles = DirectoryUtil::getInstance(WCF_DIR . 'log/', false)->getFiles(\SORT_DESC, $fileNameRegex); + foreach ($logFiles as $logFile) { + $pathname = WCF_DIR . 'log/' . $logFile; + $contents = \file_get_contents($pathname); + + if (\str_contains($contents, '<<<<<<<<' . $exceptionID . '<<<<')) { + return $pathname; + } + } + + return null; + } +} -- 2.20.1