diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -46,7 +46,7 @@ 'rsrc/css/application/config/config-options.css' => '16c920ae', 'rsrc/css/application/config/config-template.css' => '20babf50', 'rsrc/css/application/config/setup-issue.css' => '5eed85b2', - 'rsrc/css/application/config/unhandled-exception.css' => '9da8fdab', + 'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d', 'rsrc/css/application/conpherence/color.css' => 'b17746b0', 'rsrc/css/application/conpherence/durable-column.css' => '2d57072b', 'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e', @@ -877,7 +877,7 @@ 'syntax-highlighting-css' => '8a16f91b', 'tokens-css' => 'ce5a50bd', 'typeahead-browse-css' => 'b7ed02d2', - 'unhandled-exception-css' => '9da8fdab', + 'unhandled-exception-css' => '9ecfc00d', ), 'requires' => array( '01384686' => array( diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -118,6 +118,12 @@ $database_exception = $ex; } + // If we're in developer mode, set a flag so that top-level exception + // handlers can add more information. + if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { + $sink->setShowStackTraces(true); + } + if ($database_exception) { $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue( $database_exception, diff --git a/src/aphront/response/AphrontUnhandledExceptionResponse.php b/src/aphront/response/AphrontUnhandledExceptionResponse.php --- a/src/aphront/response/AphrontUnhandledExceptionResponse.php +++ b/src/aphront/response/AphrontUnhandledExceptionResponse.php @@ -4,8 +4,20 @@ extends AphrontStandaloneHTMLResponse { private $exception; + private $showStackTraces; + + public function setShowStackTraces($show_stack_traces) { + $this->showStackTraces = $show_stack_traces; + return $this; + } + + public function getShowStackTraces() { + return $this->showStackTraces; + } + + public function setException($exception) { + // NOTE: We accept an Exception or a Throwable. - public function setException(Exception $exception) { // Log the exception unless it's specifically a silent malformed request // exception. @@ -61,10 +73,36 @@ $body = $ex->getMessage(); $body = phutil_escape_html_newlines($body); + $classes = array(); + $classes[] = 'unhandled-exception-detail'; + + $stack = null; + if ($this->getShowStackTraces()) { + try { + $stack = id(new AphrontStackTraceView()) + ->setTrace($ex->getTrace()); + + $stack = hsprintf('%s', $stack); + + $stack = phutil_tag( + 'div', + array( + 'class' => 'unhandled-exception-stack', + ), + $stack); + + $classes[] = 'unhandled-exception-with-stack'; + } catch (Exception $trace_exception) { + $stack = null; + } catch (Throwable $trace_exception) { + $stack = null; + } + } + return phutil_tag( 'div', array( - 'class' => 'unhandled-exception-detail', + 'class' => implode(' ', $classes), ), array( phutil_tag( @@ -79,6 +117,7 @@ 'class' => 'unhandled-exception-body', ), $body), + $stack, )); } diff --git a/src/aphront/sink/AphrontHTTPSink.php b/src/aphront/sink/AphrontHTTPSink.php --- a/src/aphront/sink/AphrontHTTPSink.php +++ b/src/aphront/sink/AphrontHTTPSink.php @@ -5,14 +5,22 @@ * Normally this is just @{class:AphrontPHPHTTPSink}, which uses "echo" and * "header()" to emit responses. * - * Mostly, this class allows us to do install security or metrics hooks in the - * output pipeline. - * * @task write Writing Response Components * @task emit Emitting the Response */ abstract class AphrontHTTPSink extends Phobject { + private $showStackTraces = false; + + final public function setShowStackTraces($show_stack_traces) { + $this->showStackTraces = $show_stack_traces; + return $this; + } + + final public function getShowStackTraces() { + return $this->showStackTraces; + } + /* -( Writing Response Components )---------------------------------------- */ diff --git a/src/view/widget/AphrontStackTraceView.php b/src/view/widget/AphrontStackTraceView.php --- a/src/view/widget/AphrontStackTraceView.php +++ b/src/view/widget/AphrontStackTraceView.php @@ -10,7 +10,6 @@ } public function render() { - $user = $this->getUser(); $trace = $this->trace; $libraries = PhutilBootloader::getInstance()->getAllLibraries(); diff --git a/webroot/index.php b/webroot/index.php --- a/webroot/index.php +++ b/webroot/index.php @@ -44,6 +44,7 @@ try { $response = new AphrontUnhandledExceptionResponse(); $response->setException($main_exception); + $response->setShowStackTraces($sink->getShowStackTraces()); PhabricatorStartup::endOutputCapture(); $sink->writeResponse($response); diff --git a/webroot/rsrc/css/application/config/unhandled-exception.css b/webroot/rsrc/css/application/config/unhandled-exception.css --- a/webroot/rsrc/css/application/config/unhandled-exception.css +++ b/webroot/rsrc/css/application/config/unhandled-exception.css @@ -8,12 +8,12 @@ background: #fff; border: 1px solid #c0392b; border-radius: 3px; - padding: 0 8px; + padding: 8px; } .unhandled-exception-detail .unhandled-exception-title { color: #c0392b; - padding: 12px 8px; + padding: 4px 8px 12px; border-bottom: 1px solid #f4dddb; font-size: 16px; font-weight: 500; @@ -23,3 +23,31 @@ .unhandled-exception-detail .unhandled-exception-body { padding: 16px 12px; } + +.unhandled-exception-with-stack { + max-width: 95%; +} + +.unhandled-exception-stack { + background: #fcfcfc; + overflow-x: auto; + overflow-y: hidden; +} + +.unhandled-exception-stack table { + border-spacing: 0; + border-collapse: collapse; + width: 100%; + border: 1px solid #d7d7d7; +} + +.unhandled-exception-stack th { + background: #e7e7e7; + border-bottom: 1px solid #d7d7d7; + padding: 8px; +} + +.unhandled-exception-stack td { + padding: 4px 8px; + white-space: nowrap; +}