10

I need a way to capture PHP fatal errors (but also notices and warnings) and logging them by using Monolog.

I found that Monolog 1.6+ has the ErrorHandler::register() method, but I can't figure out how to use it in a Symfony2 (production) application, and how properly configure it in config.yml.

fdellutri
  • 963
  • 1
  • 7
  • 16
  • Usually symfony is configured that all php errors, warnings and notices are converted to exception and get handled by symfony, so they get already logged. Catching fatals depends on your php version. – Emii Khaos Mar 12 '14 at 18:08
  • Thank you @Pazi. I'm using PHP 5.5 and when a Fatal error is issued I cannot find anything reported in the symfony logs, instead I found errors logged in the apache error log... – fdellutri Mar 12 '14 at 18:31
  • Tried this? http://symfony.com/doc/current/cookbook/logging/monolog_email.html – Cerad Mar 12 '14 at 18:56

2 Answers2

11

Thanks to @jenechka, who pointed me to the right direction, I think I found a solution:

services.yml:

    vir.exception.listener:
    class: %vir.exception.listener.class%
    arguments: ["@logger"]
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

Error Handler:

<?php

namespace Mitecube\VoglioilruoloBundle\Listener;

use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Psr\Log\LoggerInterface;

class VoglioilruoloErrorHandler extends ErrorHandler {

    private $logger;
    private $prevErrorHandler;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
        $this->prevErrorHandler = set_error_handler(array($this, 'handle'));
        register_shutdown_function(array($this, 'handleFatal'));
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
    }

    public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
    {
        $this->logger->error($level . ": " . $message . " - in file " . $file . " - at line " . $line);
        return parent::handle($level, $message, $file, $line, $context);
    }

} 

In this way I able to log each error to monolog. I don't think this solution can be considered a "best practice", so I'm still looking for a better solution.

fdellutri
  • 963
  • 1
  • 7
  • 16
  • Hi @fdellutri, really useful question. I am also looking for a way to setup Monolog to catch Fatal Errors and I havent found a simple solution, like using monolog yml configuration. Have you found a better solution ? – Sergio Costa Sep 25 '14 at 14:38
  • @SergioCosta the proposed solution is the only that I found to solve this issue. This solution is in production and it works as expected. – fdellutri Sep 25 '14 at 16:32
1

Create exception listener as described here http://symfony.com/doc/current/cookbook/service_container/event_listener.html

And see here http://symfony.com/doc/current/reference/dic_tags.html#monolog-logger how to send logger as argument to your listener

Service example configuration:

# src/Acme/DemoBundle/Resources/config/services.yml
parameters:
    # ...

services:
    # ...
    kernel.listener.your_listener_name:
        class: Acme\DemoBundle\EventListener\AcmeExceptionListener
        arguments: ["@logger"]
        tags:
            - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
            - { name: monolog.logger, channel: tema }

Listener example:

// src/Acme/DemoBundle/EventListener/AcmeExceptionListener.php
namespace Acme\DemoBundle\EventListener;

use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Psr\Log\LoggerInterface;

class ExceptionListener extends ExceptionHandler
{
    private $logger;
    private $prevExceptionHandler;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;

        // Set our handle method as fatal exception handler.
        // It is required to extend Symfony\Component\Debug\ExceptionHandler
        $this->prevExceptionHandler = set_exception_handler(array($this, 'handle'));
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
    }

    /**
     * Handles non fatal exceptions (normal way).
     */
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // You get the exception object from the received event
        $exception = $event->getException();

        // Log exception.
        $this->logger->error($exception->getMessage());

        // ...
    }

    /**
     * Overwrite ExceptionHandler method.
     */
    public function handle(\Exception $exception) {
        // Call our custom handler.
        $this->onFatalErrorException($exception);

        // Call exception handler that was overridden.
        // Or try to call parent::handle($exception)
        if (is_array($this->prevExceptionHandler) && $this->prevExceptionHandler[0] instanceof ExceptionHandler) {
            $this->prevExceptionHandler[0]->handle($exception);
        }
    }

    public function onFatalErrorException(\Exception $exception)
    {
        // Do anything you want...
        $this->logger->error('Hey, I got it: '. $exception->getMessage());
    }
}

UPDATED: I have improved exception listener and now it handles fatal exceptions. Tested (on dev environment)!

Jekis
  • 4,274
  • 2
  • 36
  • 42
  • Hi @jenechka, this method works for exceptions raised, but PHP errors doesn't raise an exception. To catch a fatal, first of all, you need to register an error handler (see http://stackoverflow.com/questions/277224/how-do-i-catch-a-php-fatal-error). I think I need to register as error handler a my own method, and log the error. So, the questionon could be: "which is the best way to register an error handler in Symfony2? – fdellutri Mar 13 '14 at 10:23
  • @fdellutri I have updated my answer. Now it should work. – Jekis Mar 13 '14 at 12:32
  • I posted a solution to my question. Now it works, but I don't know if it is a best practice. – fdellutri Mar 13 '14 at 17:35