filp / whoops

PHP errors for cool kids
http://filp.github.io/whoops/
MIT License
13.2k stars 604 forks source link

Replay monolog errors in whoops #215

Closed garygreen closed 8 years ago

garygreen commented 10 years ago

Is it possible to 'replay' errors generated in monolog format (i.e. file or JSON format) and re-generate a whoops error screen? I have a list of log files that are in Laravel's default monolog format and wonder how difficult it would be to create a log viewer that shows the exceptions in whoops -- is this currently possible?

denis-sokolov commented 10 years ago

No, it's not possible at the moment. But you can use the Whoops programatically.

If you have an Exception object, the following will display it:

$handler = new PrettyPageHandler();
$handler->setInspector(new Inspector($exception));
$handler->handle();

If you don't have an Exception object, you unfortunately can't create a custom Inspector, because we don't have an interface. Instead I suggest creating a fakse Exception that overrides $this->file, $this->line and the rest.

garygreen commented 10 years ago

Thanks Denis. Would you be willing to accept a pull request for implementing an interface to the inspector?

denis-sokolov commented 10 years ago

I would love to. However, do note that unfortunately, the current implementation of Inspector violates the Law of Demeter by exposing getException(). Even worse, the entire purpose of the Inspector is damaged by the fact that Handlers expect a setException call on them.

I'd say a good design would hide the Exceptions behind the Inspector interface. Unfortunately, this may be a big bigger refactoring than you'd be ready to do. :)

The task would be simple but big in scope: replace all queries that ask Exception for data with calls to the Inspector that hides those calls. But there seem to be more than 20 places to change. =7

garygreen commented 10 years ago

@denis-sokolov I tried the method that you suggested however I received the error Call to a member function getHandlers() on a non-object PrettPageHandler.php:98. I have for now replaced this reference to handlers with a blank empty object and that seems to fix it.

A few things:

  1. Fixed (see below) Exception title: due to the way in which I have to create a 'fake' exception object, it will always have that as the title for the exception -- is there a way to override this?
  2. Fixed (see below) Stack traces: In the generated monolog files for Laravel they have a stack trace -- what is your suggestion on simulating the stack trace manually too? I took at look into the Inspector which seems to have a frames variable but no method of setting the frames themselves? Of course if the files have changed then the actual content of the files or files themselves might not exist, but the actual file , function and line number is known in the log file (exactly the information displayed in the left pane) -- maybe have, by default, an option to not automatically display the code within that file once clicked on, as not to confuse/give false information if the file has since updated, if you know what I mean?

I've added below my implementation for simulating an exception so far and also an example of the log file that I am ultimately looking to 'replay' in whoops


\ Update**: I've fixed the above two by creating my own inspector class which extends the original inspector and acting on methods getFrames() and getExceptionName(), new screenshot as below:

class CustomInspector extends Whoops\Exception\Inspector
{
    public function getExceptionName()
    {
        return 'Exception Name';
    }

    public function getFrames()
    {
        $frames = array();
        $frames[] = array(
            'file' => __DIR__ . '/events.php',
            'line' => 2,
            'function' => 'test',
        );
        $frames[] = array(
            'file' => __DIR__ . '/helpers.php',
            'line' => 6,
            'function' => 'test2',
        );
        $this->frames = new Whoops\Exception\FrameCollection($frames);
        return $this->frames;
    }
}

class CustomException extends Exception {
    public function __construct($message, $code = 0)
    {
        parent::__construct($message, $code);
    }

    public function setFile($file)
    {
        $this->file = $file;
    }

    public function setLine($line)
    {
        $this->line = $line;
    }
}

$exception = new CustomException('test');
$exception->setFile(__DIR__ . '/test.php');
$exception->setLine(10);
$inspector = new CustomInspector($exception);
$handler = new Whoops\Handler\PrettyPageHandler();
$handler->setInspector($inspector);
$handler->addDataTable('Information', array(
    "memory_usage" => "3.25 MB",
    "url"          => "/_common/_core/server/svr-download.asp?fle=/ftp/resource-downloads/xx.pdf",
    "ip"           => "127.0.0.1",
    "http_method"  => "GET",
    "server"       => "xxx",
    "referrer"     => null,
    "file"         => null,
    "line"         => null,
    "class"        => null,
    "function"     => "call_user_func_array",
));
$handler->handle();
die;

custom whoops 2


class CustomException extends Exception {
    public function __construct($message, $code = 0)
    {
        parent::__construct($message, $code);
    }

    public function setFile($file)
    {
        $this->file = $file;
    }

    public function setLine($line)
    {
        $this->line = $line;
    }
}

$exception = new CustomException('test');
$exception->setFile(__DIR__ . '/helpers.php');
$exception->setLine(10);
$handler = new Whoops\Handler\PrettyPageHandler();
$handler->setInspector(new Whoops\Exception\Inspector($exception));
$handler->handle();
die;

Result:

custom whoops

Replaying log:

[2014-06-17 00:07:48] production.ERROR: exception 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException' in /home/lasg/site/bootstrap/compiled.php:5310 Stack trace:

0 /home/lasg/site/bootstrap/compiled.php(4677): Illuminate\Routing\RouteCollection->match(Object(Illuminate\Http\Request))

#1 /home/lasg/site/bootstrap/compiled.php(4665): Illuminate\Routing\Router->findRoute(Object(Illuminate\Http\Request))
#2 /home/lasg/site/bootstrap/compiled.php(4657): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#3 /home/lasg/site/bootstrap/compiled.php(706): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#4 /home/lasg/site/bootstrap/compiled.php(687): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request))
#5 /home/lasg/site/bootstrap/compiled.php(1144): Illuminate\Foundation\Application->handle(Object(Illuminate\Http\Request), 1, true)
#6 /home/lasg/site/bootstrap/compiled.php(7264): Illuminate\Http\FrameGuard->handle(Object(Illuminate\Http\Request), 1, true)
#7 /home/lasg/site/bootstrap/compiled.php(7861): Illuminate\Session\Middleware->handle(Object(Illuminate\Http\Request), 1, true)
#8 /home/lasg/site/bootstrap/compiled.php(7808): Illuminate\Cookie\Queue->handle(Object(Illuminate\Http\Request), 1, true)
#9 /home/lasg/site/bootstrap/compiled.php(10828): Illuminate\Cookie\Guard->handle(Object(Illuminate\Http\Request), 1, true)
#10 /home/lasg/site/bootstrap/compiled.php(648): Stack\StackedHttpKernel->handle(Object(Illuminate\Http\Request))
#11 /home/lasg/site/public/index.php(49): Illuminate\Foundation\Application->run()
#12 {main} [] {"memory_usage":"3.25 MB","url":"/_common/_core/server/svr-download.asp?fle=/ftp/resource-downloads/xxxx.pdf","ip":"127.0.0.1","http_method":"GET","server":"xxx","referrer":null,"file":null,"line":null,"class":null,"function":"call_user_func_array"}
garygreen commented 10 years ago

My only request is by default whoops always displays the _GET, _POST, _FILES, _SERVER, _SESSION, _COOKIE, _ENV variables etc -- it would be good if this could be reset with a call to the PrettyPageHandler (I think the extraTables should just become a tables with the default values as above) with a method for resetDataTable()

denis-sokolov commented 10 years ago

That's all cool news! I think modifying the displayed environment and request variables is quite possible to implement easily.

denis-sokolov commented 8 years ago

Closing due to inactivity. If anyone wants to work on this, feel free to speak up.