Compare commits
4 Commits
c0197c259f
...
d3e6100d17
Author | SHA1 | Date | |
---|---|---|---|
|
d3e6100d17 | ||
|
bec3c36657 | ||
|
a7dcdf4ee7 | ||
|
ec873145d2 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
composer.lock
|
composer.lock
|
||||||
/vendor/
|
/vendor/
|
||||||
|
42
LICENSE
42
LICENSE
@ -1,21 +1,21 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2019 Dave Mc Nicoll
|
Copyright (c) 2019 Dave Mc Nicoll
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# thebugs
|
# thebugs
|
||||||
A PSR-15 compliant library helping to trace errors in development and production environment.
|
A PSR-15 compliant library helping to trace errors in development and production environment.
|
||||||
|
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "mcnd/thebugs",
|
"name": "mcnd/thebugs",
|
||||||
"description": "A PSR-15 compliant library helping to trace errors in development and production environment",
|
"description": "A PSR-15 compliant library helping to trace errors in development and production environment",
|
||||||
"keywords" : ["debug","error","bug"],
|
"keywords" : ["debug","error","bug"],
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Dave Mc Nicoll",
|
"name": "Dave Mc Nicoll",
|
||||||
"email": "info@mcnd.ca"
|
"email": "info@mcnd.ca"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {}
|
"require": {},
|
||||||
}
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"TheBugs\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
32
src/Email/EmailConfiguration.php
Normal file
32
src/Email/EmailConfiguration.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TheBugs\Email;
|
||||||
|
|
||||||
|
class EmailConfiguration
|
||||||
|
{
|
||||||
|
const AUTH_TYPE_SMTP = 1;
|
||||||
|
|
||||||
|
protected $type;
|
||||||
|
|
||||||
|
public $smtpHost = "";
|
||||||
|
|
||||||
|
public $smtpUsername = "";
|
||||||
|
|
||||||
|
public $smtpPassword = "";
|
||||||
|
|
||||||
|
public $smtpPort = 25;
|
||||||
|
|
||||||
|
public $smtpUseTLS = false;
|
||||||
|
|
||||||
|
public $toAddress = "";
|
||||||
|
|
||||||
|
public $fromAddress = "";
|
||||||
|
|
||||||
|
public $fromName = "";
|
||||||
|
|
||||||
|
public function __construct($type = self::AUTH_TYPE_SMTP)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
src/Email/MailerInterface.php
Normal file
12
src/Email/MailerInterface.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TheBugs\Email;
|
||||||
|
|
||||||
|
interface MailerInterface
|
||||||
|
{
|
||||||
|
public function send(string $subject, string $message, bool $html = true) : bool;
|
||||||
|
|
||||||
|
public function setFrom($from) : self;
|
||||||
|
|
||||||
|
public function setTo($to) : self;
|
||||||
|
}
|
45
src/Email/SwiftMailer.php
Normal file
45
src/Email/SwiftMailer.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TheBugs\Email;
|
||||||
|
|
||||||
|
class SwiftMailer implements MailerInterface
|
||||||
|
{
|
||||||
|
protected $emailConfiguration;
|
||||||
|
|
||||||
|
protected $transport;
|
||||||
|
|
||||||
|
protected $from;
|
||||||
|
|
||||||
|
protected $to;
|
||||||
|
|
||||||
|
public function __construct(EmailConfiguration $configuration) {
|
||||||
|
$this->emailConfiguration = $configuration;
|
||||||
|
|
||||||
|
$this->transport = ( new \Swift_SmtpTransport($this->emailConfiguration->smtpHost, $this->emailConfiguration->smtpPort) )
|
||||||
|
->setUsername($this->emailConfiguration->smtpUsername)
|
||||||
|
->setPassword($this->emailConfiguration->smtpPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(string $subject, string $message, bool $html = true) : bool
|
||||||
|
{
|
||||||
|
$swiftObj = ( new \Swift_Message($subject) )
|
||||||
|
->setFrom( $this->from )
|
||||||
|
->setTo( $this->to )
|
||||||
|
->setBody( $message, $html ? 'text/html' : 'text/plain' );
|
||||||
|
|
||||||
|
return ( new \Swift_Mailer($this->transport) )->send($swiftObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFrom($from) : MailerInterface
|
||||||
|
{
|
||||||
|
$this->from = $from;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTo($to) : MailerInterface
|
||||||
|
{
|
||||||
|
$this->to = $to;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
src/EmailErrorMiddleware.php
Normal file
44
src/EmailErrorMiddleware.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace TheBugs;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface,
|
||||||
|
Psr\Http\Message\ServerRequestInterface,
|
||||||
|
Psr\Http\Server\MiddlewareInterface,
|
||||||
|
Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class EmailErrorMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
protected /* EmailConfiguration */ $emailConfiguration;
|
||||||
|
|
||||||
|
protected /* Callable */ $callable;
|
||||||
|
|
||||||
|
protected /* Mailer */ $mailer;
|
||||||
|
|
||||||
|
public function __construct(Email\EmailConfiguration $emailConfiguration, Email\MailerInterface $mailer, Callable $callable)
|
||||||
|
{
|
||||||
|
$this->emailConfiguration = $emailConfiguration;
|
||||||
|
$this->callable = $callable;
|
||||||
|
$this->mailer = $mailer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||||
|
{
|
||||||
|
$response = $handler->handle($request);
|
||||||
|
|
||||||
|
if ( $response->getStatusCode() === 500 ) {
|
||||||
|
$this->mailer->setTo($this->emailConfiguration->toAddress);
|
||||||
|
$this->mailer->setFrom([$this->emailConfiguration->fromAddress => $this->emailConfiguration->fromName]);
|
||||||
|
|
||||||
|
$bugReport = $response->getBody()->__toString();
|
||||||
|
|
||||||
|
if ( false === ( $this->mailer->send(error_get_last()['message'], $bugReport, true) ) ) {
|
||||||
|
error_log("Impossile to send an email bug report from " . static::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->callable->call($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace TheBugs\Exception;
|
namespace TheBugs\Exception;
|
||||||
|
|
||||||
class JavascriptException extends \Exception {
|
class JavascriptException extends \Exception {
|
||||||
|
|
||||||
protected $url;
|
protected $url;
|
||||||
|
|
||||||
protected $stacktrace;
|
protected $stacktrace;
|
||||||
|
|
||||||
public function __construct(string $message, int $code, Exception $previous = null, string $file, int $line, string $url, array $trace)
|
public function __construct(string $message, int $code, Exception $previous = null, string $file, int $line, string $url, array $trace)
|
||||||
{
|
{
|
||||||
$this->message = $message;
|
$this->message = $message;
|
||||||
$this->code = $code;
|
$this->code = $code;
|
||||||
$this->previous = $previous;
|
$this->previous = $previous;
|
||||||
$this->file = $file;
|
$this->file = $file;
|
||||||
$this->line = $line;
|
$this->line = $line;
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
$this->stacktrace = $trace;
|
$this->stacktrace = $trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString() {
|
public function __toString() {
|
||||||
return __CLASS__ . ": [{$this->url}:{$this->code}]: {$this->message}\n" . json_encode($this->stacktrace);
|
return __CLASS__ . ": [{$this->url}:{$this->code}]: {$this->message}\n" . json_encode($this->stacktrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,70 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace TheBugs;
|
namespace TheBugs;
|
||||||
|
|
||||||
use Psr\Http\Message\ResponseFactoryInterface,
|
use Psr\Http\Message\ResponseFactoryInterface,
|
||||||
Psr\Container\ContainerInterface,
|
Psr\Container\ContainerInterface,
|
||||||
Psr\Http\Message\ResponseInterface,
|
Psr\Http\Message\ResponseInterface,
|
||||||
Psr\Http\Message\ServerRequestInterface,
|
Psr\Http\Message\ServerRequestInterface,
|
||||||
Psr\Http\Server\MiddlewareInterface,
|
Psr\Http\Server\MiddlewareInterface,
|
||||||
Psr\Http\Server\RequestHandlerInterface;
|
Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
use Zend\Diactoros\Response,
|
use Zend\Diactoros\Response,
|
||||||
Zend\Diactoros\ServerRequest,
|
Zend\Diactoros\ServerRequest,
|
||||||
Zend\Diactoros\Stream,
|
Zend\Diactoros\Stream,
|
||||||
Zend\Diactoros\Uri;
|
Zend\Diactoros\Uri;
|
||||||
|
|
||||||
class JavascriptMiddleware implements MiddlewareInterface
|
class JavascriptMiddleware implements MiddlewareInterface
|
||||||
{
|
{
|
||||||
const FILTERS = [
|
const FILTERS = [
|
||||||
"code" => null,
|
"code" => null,
|
||||||
"headers" => [
|
"headers" => [
|
||||||
"User-Agent" => "TheBugs/1.0",
|
"User-Agent" => "TheBugs/1.0",
|
||||||
],
|
],
|
||||||
];
|
# Callable functions
|
||||||
|
"functions" => []
|
||||||
/**
|
];
|
||||||
* Filter list, allows custom invokable
|
|
||||||
* @var array
|
/**
|
||||||
*/
|
* Filter list, allows custom invokable too
|
||||||
protected $filters = [];
|
* @var array
|
||||||
|
*/
|
||||||
public function __construct($options = [])
|
protected $filters = [];
|
||||||
{
|
|
||||||
foreach(static::FILTERS as $key => $default) {
|
public function __construct($options = [])
|
||||||
$this->filters[$key] = ( $options[$key] ?? false ) ? $options[$key] : $default;
|
{
|
||||||
}
|
foreach(static::FILTERS as $key => $default) {
|
||||||
}
|
$this->filters[$key] = ( $options[$key] ?? false ) ? $options[$key] : $default;
|
||||||
|
}
|
||||||
public function throwError($errorDetails)
|
}
|
||||||
{
|
|
||||||
throw new Exception\JavascriptException($errorDetails['message'], 0, null, $errorDetails['location'], $errorDetails['line'], $errorDetails['url'], (array) $errorDetails['stack']);
|
public function throwError($errorDetails)
|
||||||
}
|
{
|
||||||
|
throw new Exception\JavascriptException($errorDetails['message'], 0, null, $errorDetails['location'], $errorDetails['line'], $errorDetails['url'], (array) $errorDetails['stack']);
|
||||||
/**
|
}
|
||||||
* Process a server request and return a response.
|
|
||||||
*/
|
/**
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
* Process a server request and return a response.
|
||||||
{
|
*/
|
||||||
if ( ( $this->filters['code'] ?? false ) && ($this->filters['code'] !== $request->getCode())) {
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
return $handler->handle($request);
|
{
|
||||||
}
|
# if ( ( $this->filters['code'] ?? false ) && ($this->filters['code'] !== $request->getCode())) {
|
||||||
|
# return $handler->handle($request);
|
||||||
foreach($this->filters['headers'] as $key => $value) {
|
# }
|
||||||
if ( ! in_array($value, $request->getHeader($key)) ) {
|
|
||||||
return $handler->handle($request);
|
foreach($this->filters['headers'] as $key => $value) {
|
||||||
}
|
if ( ! in_array($value, $request->getHeader($key)) ) {
|
||||||
}
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
$this->throwError(json_decode($request->getBody(), true));
|
}
|
||||||
}
|
|
||||||
|
foreach($this->filters['functions'] as $func) {
|
||||||
}
|
if ( $func->call($this) ) {
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->throwError(json_decode($request->getBody(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Whoops\Run as Whoops;
|
|
||||||
use Whoops\Handler\PrettyPageHandler as Handler;
|
|
||||||
|
|
||||||
$whoops = new Whoops();
|
|
||||||
$whoops->allowQuit(false);
|
|
||||||
$whoops->writeToOutput(false);
|
|
||||||
$whoops->pushHandler(new Handler());
|
|
||||||
|
|
||||||
IF CLI {
|
|
||||||
$handler->handleUnconditionally(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = $whoops->handleException($this->exception);
|
|
46
templates/js_debug.js
Normal file
46
templates/js_debug.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
class ErrorHandler
|
||||||
|
{
|
||||||
|
constructor(options) {
|
||||||
|
if ( options ) {
|
||||||
|
if ( "url" in options ) {
|
||||||
|
this.url = options['url'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.catchError();
|
||||||
|
}
|
||||||
|
|
||||||
|
catchError() {
|
||||||
|
window.onerror = function(message, url, line, column, error) {
|
||||||
|
fetch(this.url ? this.url : window.location.href, {
|
||||||
|
method: "post",
|
||||||
|
headers: {
|
||||||
|
'Accept': "application/json",
|
||||||
|
'Content-Type': "application/json",
|
||||||
|
'User-Agent': "TheBugs/1.0"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
url: url,
|
||||||
|
line: line,
|
||||||
|
column: column,
|
||||||
|
stack: error.stack,
|
||||||
|
location: window.location.toString()
|
||||||
|
})
|
||||||
|
}).then( response => response ).then(data => {
|
||||||
|
console.info("Error reported", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
return this._url;
|
||||||
|
}
|
||||||
|
|
||||||
|
set url(set) {
|
||||||
|
return this._url = set;
|
||||||
|
}
|
||||||
|
}
|
34
templates/xhr_debug.php
Normal file
34
templates/xhr_debug.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script>
|
||||||
|
(send => {
|
||||||
|
XMLHttpRequest.prototype.send = function() {
|
||||||
|
this.addEventListener('load', function(e) {
|
||||||
|
switch(this.status) {
|
||||||
|
case 500:
|
||||||
|
let response = JSON.parse(this.response),
|
||||||
|
content = JSON.stringify(response.error.trace, null, 2),
|
||||||
|
element = document.createElement('div'),
|
||||||
|
body = document.querySelector("body");
|
||||||
|
|
||||||
|
body.appendChild(element);
|
||||||
|
element.outerHTML = `
|
||||||
|
<div ondblclick="this.parentNode.removeChild(this);">
|
||||||
|
<dialog style="top:10vh;width:75%;z-index:1500;background:#444;color:#444;border:0px solid #2f2a2a;padding:2px;position:fixed; max-height:80vh;">
|
||||||
|
<div style="background:#fff">
|
||||||
|
<div style="padding:15px; color:#fff; border-bottom:2px solid #444;background:#c61f1f;font-weight:bold">${response.error.message}</div>
|
||||||
|
<pre style="overflow-y:auto;max-height:65vh;padding:15px;">${content}</pre>
|
||||||
|
<div style="padding:8px 15px; color:#fff; border-top:2px solid #444;text-align:right;background:#c61f1f;font-weight:bold">${response.error.file}:${response.error.line}</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<div style="position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000;opacity:0.5"></div>
|
||||||
|
</div>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
body.querySelector("dialog").showModal();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
send.apply(this, arguments);
|
||||||
|
};
|
||||||
|
})(XMLHttpRequest.prototype.send);
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user