Use custom Whoops handler for error reporting.

This commit is contained in:
flash 2018-09-16 23:03:56 +02:00
parent dd6974855a
commit 88f3bd8069
5 changed files with 133 additions and 181 deletions

View file

@ -4,20 +4,25 @@ namespace Misuzu;
define('MSZ_STARTUP', microtime(true)); define('MSZ_STARTUP', microtime(true));
define('MSZ_DEBUG', file_exists(__DIR__ . '/.debug')); define('MSZ_DEBUG', file_exists(__DIR__ . '/.debug'));
error_reporting(MSZ_DEBUG ? -1 : 0);
ini_set('display_errors', MSZ_DEBUG ? 'On' : 'Off');
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
mb_internal_encoding('UTF-8'); mb_internal_encoding('UTF-8');
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
if (MSZ_DEBUG) { $errorHandler = new \Whoops\Run;
$errorHandler = new \Whoops\Run; $errorHandler->pushHandler(
$errorHandler->pushHandler( MSZ_DEBUG
? (
PHP_SAPI === 'cli' PHP_SAPI === 'cli'
? new \Whoops\Handler\PlainTextHandler ? new \Whoops\Handler\PlainTextHandler
: new \Whoops\Handler\PrettyPageHandler : new \Whoops\Handler\PrettyPageHandler
); )
$errorHandler->register(); : ($errorReporter = new WhoopsReporter)
} );
$errorHandler->register();
require_once __DIR__ . '/src/audit_log.php'; require_once __DIR__ . '/src/audit_log.php';
require_once __DIR__ . '/src/changelog.php'; require_once __DIR__ . '/src/changelog.php';
@ -43,6 +48,11 @@ require_once __DIR__ . '/src/Users/user.php';
require_once __DIR__ . '/src/Users/validation.php'; require_once __DIR__ . '/src/Users/validation.php';
$app = new Application(__DIR__ . '/config/config.ini'); $app = new Application(__DIR__ . '/config/config.ini');
if (!empty($errorReporter)) {
$errorReporter->setReportInfo(...$app->getReportInfo());
}
$app->startDatabase(); $app->startDatabase();
if (PHP_SAPI === 'cli') { if (PHP_SAPI === 'cli') {

View file

@ -67,15 +67,14 @@ final class Application
if ($this->config === false) { if ($this->config === false) {
throw new UnexpectedValueException('Failed to parse configuration.'); throw new UnexpectedValueException('Failed to parse configuration.');
} }
}
// only use this error handler in prod mode, dev uses Whoops now public function getReportInfo(): array
if (!MSZ_DEBUG) { {
ExceptionHandler::register( return [
false, $this->config['Exceptions']['report_url'] ?? null,
$this->config['Exceptions']['report_url'] ?? null, $this->config['Exceptions']['hash_key'] ?? null,
$this->config['Exceptions']['hash_key'] ?? null ];
);
}
} }
/** /**

View file

@ -1,163 +0,0 @@
<?php
namespace Misuzu;
use ErrorException;
use Throwable;
/**
* Handles displaying and reporting exceptions after being registered.
* @package Misuzu
* @author flashwave <me@flash.moe>
*/
class ExceptionHandler
{
/**
* Url to which report objects will be POSTed.
* @var string
*/
private static $reportUrl = null;
/**
* HMAC key that will be used to sign the request.
* @var string
*/
private static $reportSign = null;
/**
* Whether debug mode is active.
* If true (or in CLI) a backtrace will be displayed.
* If false a user friendly, non-exposing error page will be displayed.
* @var bool
*/
private static $debugMode = false;
/**
* Registers the exception handler and make it so all errors are thrown as ErrorExceptions.
* @param bool $debugMode
* @param string|null $reportUrl
* @param string|null $reportSign
*/
public static function register(bool $debugMode, ?string $reportUrl = null, ?string $reportSign = null): void
{
self::$debugMode = $debugMode;
self::$reportUrl = $reportUrl;
self::$reportSign = $reportSign;
set_exception_handler([self::class, 'exception']);
set_error_handler([self::class, 'error']);
}
/**
* Same as above except unregisters
*/
public static function unregister(): void
{
restore_exception_handler();
restore_error_handler();
}
/**
* The actual handler for rendering and reporting exceptions.
* Checks if the exception is extends on HttpException,
* if not an attempt will be done to report it.
* @param Throwable $exception
*/
public static function exception(Throwable $exception): void
{
$report = !self::$debugMode && self::$reportUrl !== null;
if ($report) {
self::report($exception);
}
self::render($exception, $report);
}
/**
* Converts regular errors to ErrorException instances.
* @param int $severity
* @param string $message
* @param string $file
* @param int $line
* @throws ErrorException
*/
public static function error(int $severity, string $message, string $file, int $line): void
{
throw new ErrorException($message, 0, $severity, $file, $line);
}
/**
* Shoots a POST request to the report URL.
* @todo Implement this.
* @param Throwable $throwable
*/
private static function report(Throwable $throwable): bool
{
if (!mb_strlen(self::$reportUrl)) {
return false;
}
if (!($curl = curl_init(self::$reportUrl))) {
return false;
}
$json = json_encode([
'git' => [
'branch' => git_branch(),
'hash' => git_commit_hash(true),
],
'exception' => [
'type' => get_class($throwable),
'message' => $throwable->getMessage(),
'code' => $throwable->getCode(),
'file' => str_replace(dirname(__DIR__, 1), '', $throwable->getFile()),
'line' => $throwable->getLine(),
'trace' => $throwable->getTraceAsString(),
],
]);
$headers = [
'Content-Type: application/json;charset=utf-8',
];
if (mb_strlen(self::$reportSign)) {
$headers[] = 'X-Misuzu-Signature: sha256=' . hash_hmac('sha256', $json, self::$reportSign);
}
$setOpts = curl_setopt_array($curl, [
CURLOPT_TCP_NODELAY => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => $headers,
]);
if (!$setOpts) {
return false;
}
return curl_exec($curl) !== false;
}
/**
* Renders exceptions.
* In debug or cli mode a backtrace is displayed.
* @param Throwable $exception
* @param bool $reported
*/
private static function render(Throwable $exception, bool $reported): void
{
if (PHP_SAPI !== 'cli' && !headers_sent()) {
ob_clean();
http_response_code(500);
header('Content-Type: text/plain');
}
if (PHP_SAPI === 'cli' || self::$debugMode) {
echo $exception;
} else {
echo 'Something broke!';
if ($reported) {
echo PHP_EOL . 'Information about this error has been sent to the devs.';
}
}
}
}

106
src/WhoopsReporter.php Normal file
View file

@ -0,0 +1,106 @@
<?php
namespace Misuzu;
use Whoops\Exception\Formatter;
use Whoops\Handler\Handler;
final class WhoopsReporter extends Handler
{
private $reportUrl;
private $reportSign;
public function __construct(?string $reportUrl = null, ?string $reportSign = null)
{
$this->setReportInfo($reportUrl, $reportSign);
}
public function setReportInfo(?string $reportUrl = null, ?string $reportSign = null): void
{
$this->reportUrl = $reportUrl;
$this->reportSign = $reportSign;
}
public function handle()
{
echo $this->html(
$this->report()
? 'Information about this error has been sent to the devs.'
: 'Report what you were trying to a developer.'
);
return Handler::QUIT;
}
public function contentType(): string
{
return 'text/html';
}
public function html(string $text): string
{
return <<<HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Something broke!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/css/mio.css" rel="stylesheet">
</head>
<body class="main" style="justify-content: center; --site-max-width: 400px">
<div class="main__wrapper" style="flex: 0 0 auto">
<div class="container">
<div class="container__title">Something broke!</div>
<div class="container__content">
<p>{$text}</p>
</div>
</div>
</div>
</body>
</html>
HTML;
}
private function report(): bool
{
if (!mb_strlen($this->reportUrl) || !($curl = curl_init($this->reportUrl))) {
return false;
}
$json = json_encode([
'git' => [
'branch' => git_branch(),
'hash' => git_commit_hash(true),
],
'misuzu' => [
'trace_txt' => $this->getException()->getTraceAsString(),
'directory' => dirname(__DIR__, 1),
],
'exception' => Formatter::formatExceptionAsDataArray(
$this->getInspector(),
$this
),
]);
$headers = [
'Content-Type: application/json;charset=utf-8',
];
if (mb_strlen($this->reportSign)) {
$headers[] = 'X-Misuzu-Signature: sha256=' . hash_hmac('sha256', $json, $this->reportSign);
}
$setOpts = curl_setopt_array($curl, [
CURLOPT_TCP_NODELAY => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => false,
]);
if (!$setOpts) {
return false;
}
return curl_exec($curl) !== false;
}
}

View file

@ -63,15 +63,15 @@ function tpl_exists(string $path): bool
function tpl_render(string $path, array $vars = []): string function tpl_render(string $path, array $vars = []): string
{ {
if (!defined('MSZ_TPL_RENDER')) {
define('MSZ_TPL_RENDER', microtime(true));
}
$path = tpl_sanitise_path($path); $path = tpl_sanitise_path($path);
if (count($vars)) { if (count($vars)) {
tpl_vars($vars); tpl_vars($vars);
} }
if (!defined('MSZ_TPL_RENDER')) {
define('MSZ_TPL_RENDER', microtime(true));
}
return Twig::instance()->render($path, $GLOBALS[MSZ_TPL_VARS_STORE]); return Twig::instance()->render($path, $GLOBALS[MSZ_TPL_VARS_STORE]);
} }