1

I have a Symfony2 -project and it writes the logs into different files beautifully, but I would like it to write the logs into a remote database(mongodb) as well. I would like to keep the actual log files in the servers as a backup in case something goes wrong with the database connection.

Question 1: Is it even possible to save the same logs into two different places at the same time?

Question 2: How do I save the logs into the mongodb? I don't necessarily need specific mongodb-instructions, but some guidelines on how to write into a remote db with monologger. The mongodb-specific instructions are also welcome if available. ;)

Question 3(OPTIONAL): Can I get a full error stack into the logs somehow? Where could one find a full list of what data the Monolog can actually write and how to write?

GotBatteries
  • 1,346
  • 1
  • 19
  • 28

2 Answers2

1

There was a very good Blogpost sometime back for logging to a mysql database with monolog and doctrine. I can't find it anymore so i will just add the neccessary Files here and you can adjust it. The whole logic is done in the DatabaseHandler so you can just change from mysql inserts to a handling for your mongodb. This code is not mine if anyone knows the original post please comment.

BacktraceLoggerListener.php

namespace UtilsBundle\EventListener;

use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

class BacktraceLoggerListener{
private $_logger;

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

public function onKernelException(GetResponseForExceptionEvent $event)
{
    $this->_logger->addError($event->getException());
}
}

DatabaseHandler.php

namespace UtilsBundle\Logger;



use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;

/**
 * Stores to database
 *
 */
class DatabaseHandler extends AbstractProcessingHandler{
protected $_container;

/**
 * @param string $stream
 * @param integer $level The minimum logging level at which this handler will be triggered
 * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
 */
public function __construct($level = Logger::DEBUG, $bubble = true)
{
    parent::__construct($level, $bubble);
}

/**
 *
 * @param type $container
 */
public function setContainer($container)
{
    $this->_container = $container;
}

/**
 * {@inheritdoc}
 */
protected function write(array $record)
{

    // Ensure the doctrine channel is ignored (unless its greater than a warning error), otherwise you will create an infinite loop, as doctrine like to log.. a lot..
    if( 'doctrine' == $record['channel'] ) {

        if( (int)$record['level'] >= Logger::WARNING ) {
            error_log($record['message']);
        }

        return;
    }
    // Only log errors greater than a warning
    // TODO - you could ideally add this into configuration variable
    if( (int)$record['level'] >= Logger::NOTICE ) {

        try
        {
            // Logs are inserted as separate SQL statements, separate to the current transactions that may exist within the entity manager.
            $em = $this->_container->get('doctrine')->getManager();
            $conn = $em->getConnection();

            $created = date('Y-m-d H:i:s');

            $serverData = ""; //$record['extra']['server_data'];
            $referer = "";
            if (isset($_SERVER['HTTP_REFERER'])){
                $referer= $_SERVER['HTTP_REFERER'];
            }

            $stmt = $em->getConnection()->prepare('INSERT INTO system_log(log, level, server_data, modified, created)
                                    VALUES(' . $conn->quote($record['message']) . ', \'' . $record['level'] . '\', ' . $conn->quote($referer) . ', \'' . $created . '\', \'' . $created . '\');');
            $stmt->execute();

        } catch( \Exception $e ) {

            // Fallback to just writing to php error logs if something really bad happens
            error_log($record['message']);
            error_log($e->getMessage());
        }
    }
}
}

We used xml here but this can be done in services.yml too

services.xml

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services     http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
    <service id="utils.database.logger" class="UtilsBundle\Logger\DatabaseHandler">
        <call method="setContainer">
            <argument type="service" id="service_container" />
        </call>
    </service>

    <service id="utils.backtrace.logger.listener" class="UtilsBundle\EventListener\BacktraceLoggerListener">
        <argument type="service" id="logger" />
        <tag name="monolog.logger" channel="backtrace" />
        <tag name="kernel.event_listener"  event="kernel.exception" method="onKernelException" />
    </service>
</services>

And lastly add the handler to your monolog config in config_**.yml so here for production for example

config_prod.yml

monolog:
handlers:
    main:
        type:         rotating_file
        action_level: error
        max_files: 10
        handler:      nested
    nested:
        type:  stream
        path:  "%kernel.logs_dir%/%kernel.environment%.log"
        level: debug
    console:
        type:  console
    database:
        type: service
        level: notice
        id: utils.database.logger
        channels: ["!translation"]

Hope that helps

Simon Paul
  • 26
  • 3
  • What does that "BacktraceLoggerListener" do? It's not used in the config_prod.yml or anywhere else in this example. – GotBatteries Sep 06 '16 at 07:20
  • PS: I think it's considered to be bad practice to inject the whole service container. Someone correct me if I'm wrong. – GotBatteries Sep 06 '16 at 13:30
  • The BacktraceLoggerListener is registered to the kernel exception event in services.xml And yes it is considered bad practice but i am lazy what can i say ;) You can only inject the things needed too. – Simon Paul Sep 07 '16 at 15:44
  • I'll mark this as the accepted one since it got me closest to the solution. Thanks for the help! – GotBatteries Sep 07 '16 at 16:03
0

Hope I can some things up for you:

Question 1: Yes its possible. E.G. you can do smt. like:

    $this->logger->pushHandler(new StreamHandler('/path/to/logs/123_info.log',    Logger::INFO));
    $this->logger->pushHandler(new StreamHandler('/path/to/logs/456_warning.log',    Logger::INFO));

So if $this->logger->addInfo("testinfo"); this is getting logged in both streams.

Question 2: There is a MongoDBHandler as according to the StreamHandler. You should be able do configure it and pass it along to the pushHandler method or if you want to have it in your services look at MongoDBConfiguration.

Question 3: This should help: Configure Monolog

Hope that helps.

Community
  • 1
  • 1
CiTNOH
  • 186
  • 1
  • 8