Use custom Whoops handler for error reporting.
This commit is contained in:
parent
dd6974855a
commit
88f3bd8069
5 changed files with 133 additions and 181 deletions
22
misuzu.php
22
misuzu.php
|
@ -4,20 +4,25 @@ namespace Misuzu;
|
|||
define('MSZ_STARTUP', microtime(true));
|
||||
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');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
if (MSZ_DEBUG) {
|
||||
$errorHandler = new \Whoops\Run;
|
||||
$errorHandler->pushHandler(
|
||||
$errorHandler = new \Whoops\Run;
|
||||
$errorHandler->pushHandler(
|
||||
MSZ_DEBUG
|
||||
? (
|
||||
PHP_SAPI === 'cli'
|
||||
? new \Whoops\Handler\PlainTextHandler
|
||||
: new \Whoops\Handler\PrettyPageHandler
|
||||
);
|
||||
$errorHandler->register();
|
||||
}
|
||||
)
|
||||
: ($errorReporter = new WhoopsReporter)
|
||||
);
|
||||
$errorHandler->register();
|
||||
|
||||
require_once __DIR__ . '/src/audit_log.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';
|
||||
|
||||
$app = new Application(__DIR__ . '/config/config.ini');
|
||||
|
||||
if (!empty($errorReporter)) {
|
||||
$errorReporter->setReportInfo(...$app->getReportInfo());
|
||||
}
|
||||
|
||||
$app->startDatabase();
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
|
|
|
@ -67,15 +67,14 @@ final class Application
|
|||
if ($this->config === false) {
|
||||
throw new UnexpectedValueException('Failed to parse configuration.');
|
||||
}
|
||||
}
|
||||
|
||||
// only use this error handler in prod mode, dev uses Whoops now
|
||||
if (!MSZ_DEBUG) {
|
||||
ExceptionHandler::register(
|
||||
false,
|
||||
$this->config['Exceptions']['report_url'] ?? null,
|
||||
$this->config['Exceptions']['hash_key'] ?? null
|
||||
);
|
||||
}
|
||||
public function getReportInfo(): array
|
||||
{
|
||||
return [
|
||||
$this->config['Exceptions']['report_url'] ?? null,
|
||||
$this->config['Exceptions']['hash_key'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
106
src/WhoopsReporter.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -63,15 +63,15 @@ function tpl_exists(string $path): bool
|
|||
|
||||
function tpl_render(string $path, array $vars = []): string
|
||||
{
|
||||
if (!defined('MSZ_TPL_RENDER')) {
|
||||
define('MSZ_TPL_RENDER', microtime(true));
|
||||
}
|
||||
|
||||
$path = tpl_sanitise_path($path);
|
||||
|
||||
if (count($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]);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue