Seldaek / monolog

Sends your logs to files, sockets, inboxes, databases and various web services
https://seldaek.github.io/monolog/
MIT License
20.95k stars 1.9k forks source link

writing to doctrine database #55

Closed kristofvc closed 12 years ago

kristofvc commented 12 years ago

So I wrote a custom handler in symfony2:

<?php

namespace Kunstmaan\AdminBundle\Modules;

use Symfony\Component\DependencyInjection\Container;

use Doctrine\ORM\EntityManager;

use Kunstmaan\AdminBundle\Entity\LogItem;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;

class LogHandler extends AbstractProcessingHandler
{
    private $initialized = false;
    private $container;

    public function __construct(Container $container, $level = Logger::DEBUG, $bubble = true){
        $this->container = $container;
        parent::__construct($level, $bubble);
    }

    protected function write(array $record){
        if (!$this->initialized) {
            $this->initialize();
        }

        $logitem = new LogItem();        
        $logitem->setChannel($record['channel']);
        $logitem->setLevel($record['level']);
        $logitem->setMessage($record['formatted']);
        $logitem->setCreatedAt($record['datetime']);

        $em = $this->container->get('doctrine')->getEntityManager();
        $em->persist($logitem);
        $em->flush();
    }

    private function initialize(){
        $this->initialized = true;
    }
}

In my config I defined this handler:

    kunstmaan_admin.handler.log:
        class: Kunstmaan\AdminBundle\Modules\LogHandler
        arguments:
            - @service_container 
        tags:
            - { name: log_handler }

and I add it to the monolog:

monolog:
    handlers:
        main:
            type: stream
            path: %kernel.logs_dir%/%kernel.environment%.log
            level: debug
        doctrine:
            type: service
            id: kunstmaan_admin.handler.log
            level: info

I get the following error:

Fatal error: Maximum function nesting level of '100' reached, aborting! in /home/projects/bancontact/data/bancontact/vendor/doctrine-dbal/lib/Doctrine/DBAL/Logging/DebugStack.php on line 54

Think this is because doctrine tries to write to the log, and I use doctrine to safe a logitem, etc.

Is there a way around?

Seldaek commented 12 years ago

Yes you should use the DBAL directly to avoid this recursive logging of a log of a log... So you can do something like:

        $conn = $this->container->get('doctrine')->getEntityManager()->getConnection();
        $conn->executeQuery('INSERT INTO log table (a, b, c) VALUES (?, ?, ?)', array($a, $b, $c));

You can also look at this for inspiration: https://github.com/Seldaek/monolog/blob/master/doc/extending.md

kristofvc commented 12 years ago

I tried that already, same error. Also tried to push a nullhandler to the logger before the persist and push, and pop it afterwards. But also, same error. Any other suggestions?

Seldaek commented 12 years ago

Oh right, executeQuery won't work since it logs. You can call getWrappedConnection() on the connection, and that will give you the raw PDO instance with which you can work.

The NullHandler should work IMO, but I guess I'm overlooking something that makes it fail..

stof commented 12 years ago

you need to use a separate DBAL connection for which you disable the logging of queries

Seldaek commented 12 years ago

Using the raw PDO object will work too.. no need to connect twice to the DB.

damienalexandre commented 12 years ago

I'm trying to acheave what you have describe but I get a ServiceCircularReferenceException:

Circular reference detected for service "doctrine.dbal.default_connection", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> doctrine.dbal.logger -> monolog.logger.doctrine -> my.monologhandler.pdo -> doctrine.dbal.default.wrapped_connection".

I think this is more a Symfony2 DIC related issue (as there is no Logger left in my doctrine.dbal.default.wrapped_connection, the Exception should not be thrown) but maybe this example can help.

The documentation miss a point: reusing an existing DBAL connection, and I think it's realy important.

doctrine.dbal.default.wrapped_connection:
    factory_service: doctrine.dbal.default_connection
    factory_method: getWrappedConnection
    class: PDO

my.monologhandler.pdo:
    class: sojeans\BackBundle\Monolog\Handler\PDOHandler
    arguments:
        - '@doctrine.dbal.default.wrapped_connection'
    tags:
        - { name: log_handler }

Any idea on how we can solve (and then document all over the web) this issue?

stof commented 12 years ago

there is a circular reference here: you need to create the doctrine.dbal.default_connection service to create the doctrine.dbal.default.wrapped_connection`` (as it is used a factory service) and this service uses the logger.

stof commented 12 years ago

btw, I don't think you should use the same connection: it would put your logging queries inside the transactions, meaning you would loose them when the transaction is rollbacked (which is generally the case where you need your logs)

ghost commented 12 years ago

I am getting circular reference error Can someone please explain how to create as mentioned above

doctrine.dbal.default_connection service to create the doctrine.dbal.default.wrapped_connection``

wimpog commented 7 years ago

Hello, This is happening due to circular referencing caused by the $em->flush().

I have got this to work by adding the following code to only log messages coming from the 'app' channel. All others, including the ones caused by $em->flush() will be ignored.

protected function write(array $record){
    if (!$this->initialized) {
       $this->initialize();
    }

    # Only accept logs from the 'app' channel.
    # Logs from other channels (e.g. request, security, event)
    # will be ignored to avoid circular reference error on flush()
    if ($record['channel'] !== 'app') {
        return;
    }

# the rest is the same ... 

Alternatively, you can add the channel name in the config.yml:

monolog:
    handlers:
        main:
            type: stream
            path: %kernel.logs_dir%/%kernel.environment%.log
            level: debug
        doctrine:
            type: service
            id: kunstmaan_admin.handler.log
            level: info
            channels: [app]
bentcoder commented 7 years ago

Fully working example is here: http://www.inanzzz.com/index.php/post/53en/storing-symfony-log-messages-in-database-with-custom-monolog-handler

wimpog commented 7 years ago

Great! Thank you! I've implemented it in a slightly different way, filtering out anything but the 'app' channel, and also have log rotation. Thanks!

On Tue, Nov 29, 2016 at 6:13 PM, BentCoder notifications@github.com wrote:

Fully working example is here: http://www.inanzzz.com/index. php/post/53en/storing-symfony-log-messages-in-database-with- custom-monolog-handler

ā€” You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Seldaek/monolog/issues/55#issuecomment-263730986, or mute the thread https://github.com/notifications/unsubscribe-auth/AAVMzUYbEpUh8ruHEP2ki3N1invT1WQfks5rDLGkgaJpZM4AAkIQ .

seddighi78 commented 5 years ago

@BentCoder Thanks but not worked for Symfony 4.3.3

bentcoder commented 5 years ago

@BentCoder Thanks but not worked for Symfony 4.3.3

Well, you are nearly 3 years late :)

seddighi78 commented 5 years ago

oh! :smile: I found the solution thanks

fabianoroberto commented 4 years ago

oh! šŸ˜„ I found the solution thanks

@seddighi78 how you solved?

seddighi78 commented 4 years ago

By creating a new Logger file in Logger/DatabaseLogger.php with this code:

namespace App\Logger;

use Psr\Log\LoggerInterface;

class DatabaseLogger implements LoggerInterface
{
    private $logger;

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

    /**
     * System is unusable.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->logger->emergency($message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->logger->alert($message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->logger->critical($message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->logger->error($message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->logger->warning($message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->logger->notice($message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->logger->info($message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->logger->debug($message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array())
    {
        $this->logger->log($level, $message, $context);
    }
}

And then register to services:

App\Logger\DatabaseLogger:
        arguments: ['@monolog.logger.database']