[GH-ISSUE #55] writing to doctrine database #14

Closed
opened 2026-03-04 02:11:21 +03:00 by kerem · 17 comments
Owner

Originally created by @kristofvc on GitHub (Feb 16, 2012).
Original GitHub issue: https://github.com/Seldaek/monolog/issues/55

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?

Originally created by @kristofvc on GitHub (Feb 16, 2012). Original GitHub issue: https://github.com/Seldaek/monolog/issues/55 So I wrote a custom handler in symfony2: ``` php <?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: ``` yaml kunstmaan_admin.handler.log: class: Kunstmaan\AdminBundle\Modules\LogHandler arguments: - @service_container tags: - { name: log_handler } ``` and I add it to the monolog: ``` yaml 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?
kerem closed this issue 2026-03-04 02:11:22 +03:00
Author
Owner

@Seldaek commented on GitHub (Feb 16, 2012):

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

<!-- gh-comment-id:4000552 --> @Seldaek commented on GitHub (Feb 16, 2012): Yes you should use the DBAL directly to avoid this recursive logging of a log of a log... So you can do something like: ``` php $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
Author
Owner

@kristofvc commented on GitHub (Feb 16, 2012):

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?

<!-- gh-comment-id:4001276 --> @kristofvc commented on GitHub (Feb 16, 2012): 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?
Author
Owner

@Seldaek commented on GitHub (Feb 16, 2012):

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..

<!-- gh-comment-id:4001524 --> @Seldaek commented on GitHub (Feb 16, 2012): 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..
Author
Owner

@stof commented on GitHub (Feb 16, 2012):

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

<!-- gh-comment-id:4008120 --> @stof commented on GitHub (Feb 16, 2012): you need to use a separate DBAL connection for which you disable the logging of queries
Author
Owner

@Seldaek commented on GitHub (Feb 16, 2012):

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

<!-- gh-comment-id:4008415 --> @Seldaek commented on GitHub (Feb 16, 2012): Using the raw PDO object will work too.. no need to connect twice to the DB.
Author
Owner

@damienalexandre commented on GitHub (May 28, 2012):

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?

<!-- gh-comment-id:5962982 --> @damienalexandre commented on GitHub (May 28, 2012): 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](https://github.com/Seldaek/monolog/blob/master/doc/extending.md) 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?
Author
Owner

@stof commented on GitHub (May 28, 2012):

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.

<!-- gh-comment-id:5963240 --> @stof commented on GitHub (May 28, 2012): 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.
Author
Owner

@stof commented on GitHub (May 28, 2012):

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)

<!-- gh-comment-id:5963251 --> @stof commented on GitHub (May 28, 2012): 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)
Author
Owner

@ghost commented on GitHub (Aug 13, 2012):

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``

<!-- gh-comment-id:7685640 --> @ghost commented on GitHub (Aug 13, 2012): 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``
Author
Owner

@wimpog commented on GitHub (Sep 22, 2016):

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]
<!-- gh-comment-id:248787329 --> @wimpog commented on GitHub (Sep 22, 2016): 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. ``` php 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**: ``` yaml 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] ```
Author
Owner

@bentcoder commented on GitHub (Nov 29, 2016):

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

<!-- gh-comment-id:263730986 --> @bentcoder commented on GitHub (Nov 29, 2016): Fully working example is here: http://www.inanzzz.com/index.php/post/53en/storing-symfony-log-messages-in-database-with-custom-monolog-handler
Author
Owner

@wimpog commented on GitHub (Nov 30, 2016):

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
.

<!-- gh-comment-id:263767575 --> @wimpog commented on GitHub (Nov 30, 2016): 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> > . >
Author
Owner

@seddighi78 commented on GitHub (Aug 31, 2019):

@BentCoder Thanks but not worked for Symfony 4.3.3

<!-- gh-comment-id:526811823 --> @seddighi78 commented on GitHub (Aug 31, 2019): @BentCoder Thanks but not worked for Symfony 4.3.3
Author
Owner

@bentcoder commented on GitHub (Aug 31, 2019):

@BentCoder Thanks but not worked for Symfony 4.3.3

Well, you are nearly 3 years late :)

<!-- gh-comment-id:526815480 --> @bentcoder commented on GitHub (Aug 31, 2019): > @BentCoder Thanks but not worked for Symfony 4.3.3 Well, you are nearly 3 years late :)
Author
Owner

@seddighi78 commented on GitHub (Aug 31, 2019):

oh! 😄 I found the solution thanks

<!-- gh-comment-id:526816336 --> @seddighi78 commented on GitHub (Aug 31, 2019): oh! :smile: I found the solution thanks
Author
Owner

@fabianoroberto commented on GitHub (Dec 11, 2019):

oh! 😄 I found the solution thanks

@seddighi78 how you solved?

<!-- gh-comment-id:564572548 --> @fabianoroberto commented on GitHub (Dec 11, 2019): > oh! 😄 I found the solution thanks @seddighi78 how you solved?
Author
Owner

@seddighi78 commented on GitHub (Dec 12, 2019):

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']
<!-- gh-comment-id:565081553 --> @seddighi78 commented on GitHub (Dec 12, 2019): 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'] ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/monolog#14
No description provided.