2

Would it be possible to get a full example of how is it possible to configure Monolog to store its logs into MongoDB using Symfony 2.6 and Doctrine 2?

Francesco Casula
  • 26,184
  • 15
  • 132
  • 131

1 Answers1

8

Full configuration

/app/parameters.yml

mongodb_server: "mongodb://localhost:27017"
mongodb_username: "vagrant"
mongodb_password: "password"
mongodb_database: "testdb"

/app/config.yml

# Doctrine2 MongoDB Bundle
# http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
doctrine_mongodb:
    default_database: %mongodb_database%
    connections:
    default:
        server: %mongodb_server%
        options:
            password: %mongodb_password%
            username: %mongodb_username%
            db: %mongodb_database%
            connect: true
    log:
        server: %mongodb_server%
        options:
            password: %mongodb_password%
            username: %mongodb_username%
            db: %mongodb_database%
            connect: true
    document_managers:
    default:
        auto_mapping: true
    log:
        auto_mapping: false
        logging: false

/app/services.yml

mongolog:
    class: Doctrine\MongoDB\Connection
    factory_service: doctrine_mongodb.odm.log_connection
    factory_method: getMongoClient

/app/config_dev.yml

In this example I decided to store everything (debug level) as always into the dev.log and just errors, warnings and notices on mongo.

monolog:
    handlers:
    main:
        type:   stream
        path:   "%kernel.logs_dir%/%kernel.environment%.log"
        level:  debug
    console:
        type:   console
        bubble: false
        verbosity_levels:
            VERBOSITY_VERBOSE: INFO
            VERBOSITY_VERY_VERBOSE: DEBUG
        channels: ["!doctrine"]
    console_very_verbose:
        type:   console
        bubble: false
        verbosity_levels:
            VERBOSITY_VERBOSE: NOTICE
            VERBOSITY_VERY_VERBOSE: NOTICE
            VERBOSITY_DEBUG: DEBUG
        channels: ["doctrine"]
    mongo:
        type:   mongo
        level:  notice # change as desired
        mongo:
            id: mongolog
            database: %mongodb_database%
            collection: logs

/app/config_prod.yml

monolog:
    handlers:
    main:
        type:         fingers_crossed
        action_level: error
        handler:      mongo
    nested:
        type:  stream
        path:  "%kernel.logs_dir%/%kernel.environment%.log"
        level: debug
    console:
        type:  console
    mongo:
        type: mongo
        level: notice
        mongo:
            id: mongolog
            database: %mongodb_database%
            collection: logs

Now let's trigger a PHP notice and check if it'll be stored on MongoDB properly :-)

<?php trigger_error('hello world!', E_USER_NOTICE);

Adding HTTP request headers to Monolog record

/app/services.yml

kernel.listener.exception_listener:
    class: AppBundle\EventListener\ExceptionListener
    arguments:
        - @logger
    tags:
        - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }

AppBundle\EventListener\ExceptionListener

<?php

namespace AppBundle\EventListener;

use Monolog\Handler\MongoDBHandler;
use Symfony\Bridge\Monolog\Logger;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

/**
 * Class ExceptionListener
 * @package AppBundle\EventListener
 * @author Francesco Casula <fra.casula@gmail.com>
 */
class ExceptionListener extends ExceptionHandler
{
    /**
     * @var Logger
     */
    private $logger;

    /**
     * @param Logger $logger
     */
    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @return Logger
     */
    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * @param GetResponseForExceptionEvent $event
     */
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        foreach ($this->getLogger()->getHandlers() as $handler) {
            if ($handler instanceof MongoDBHandler) {
                $handler->pushProcessor(function (array $record) use ($event) {
                    $record['extra']['headers'] = $event->getRequest()->headers->all();
                    return $record;
                });

                break;
            }
        }
    }
}
Francesco Casula
  • 26,184
  • 15
  • 132
  • 131
  • Francesco, this is really cool and, as far as I can say, it works. One question though. How to ready this data? There is no Document Object in your tutorial/configuration, so is it possible to read from MongoDB using Doctrine without these objects? Just raw data? How? – pawel.kalisz Apr 16 '16 at 11:45
  • Hi @pawel.kalisz, we've been using this method for quite some time in production. We're storing basically the exception with the whole stack trace and we found MongoChef to be a very good interface to connect to our MongoDB instance and go through the logs. Sometimes we also run the queries via the Mongo shell (command line). In that case just make sure you narrow down not just the record set but also its projection (db.collection.find second parameter, see https://docs.mongodb.org/manual/reference/method/db.collection.find/) – Francesco Casula Apr 16 '16 at 15:42
  • If you want to use just PHP I guess you can still use Doctrine or just the plain PHP Mongo classes. – Francesco Casula Apr 16 '16 at 15:44