Monolog: log different channels and multiple handlers to grouped log file + dedicated log files

Wirone picture Wirone · Feb 19, 2015 · Viewed 7.1k times · Source

I'm facing some problems with configuring Monolog to handle "nested loggers".

What I want to do:

Log from services to dedicated files (one per service) AND from all services to one file. Each logger should be also handled by monolog.handlers.console.

Why I want to do

Each service has logic, but can use other services from DI. I want to know what exactly one service logs, so I want dedicated logger (with custom channel and custom log file) for each service. But when services relies on other services, I want to read logs in chronological order in one file.

What I have

app/config.yml:

monolog:
    handlers:
        my_handler:
            type:     stream
            path:     %kernel.logs_dir%/%kernel.environment%.my.log
            level:    info
            handler:  my_bundle_handler

src/My/Bundle/Resources/config/config.yml

services:
    # LOGGERS
    my_logger:
        class: Symfony\Bridge\Monolog\Logger
        arguments: [my_logger]
        calls:
            - [pushHandler, [@monolog.handler.console]]
            - [pushHandler, [@my_bundle_handler]]
        tags:
            - { name: monolog.logger, channel: my_channel}

    # HANDLERS
    my_bundle_handler:
        abstract: true # Without it it will throw exception
        type: group
        members: [my_service_handler]
        channels: ["my_channel"]
        tags:
            - { name: log_handler }

    my_service_handler:
        class: Monolog\Handler\StreamHandler
        arguments: [%kernel.logs_dir%/%kernel.environment%.my_service.log, 100]
        channels: ["my_channel"]
        tags:
            - { name: log_handler }

It does not work as expected. It logs to my_service.log, but not to my.log.

Is there possibility to achieve what I want?

Answer

Andrew Zhilin picture Andrew Zhilin · Nov 11, 2016

Channels in monolog works exactly like you want. Sample monolog configuration

app/config.yml

monolog:
  channels: ['deletion']
  handlers:
    main:
        type:         fingers_crossed
        action_level: error
        handler:      grouped_main
        formatter: "monolog.formatter.request"
        buffer_size: 30
#        if you will set stop_buffering: true - you will get ALL events after first error. It could produce huge logs for console
        stop_buffering: false

#   this is for getsentry.com error catching
    sentry:
        type:  raven
        dsn:   '%sentry_url%'
        level: notice

    # Groups
    grouped_main:
        type:    group
        members: [sentry, streamed_main, streamed_main_brief]

    # Streams
    streamed_main:
        type:  stream
        path:  "%kernel.logs_dir%/%kernel.environment%.log"

    streamed_main_brief:
        type:  stream
        path:  "%kernel.logs_dir%/%kernel.environment%_brief.log"
        formatter: monolog.brief_formatter

    console:
        type: console
        formatter: monolog.console_formatter

    deletion:
         # log deletion related messages
        level:    debug
        type:     stream
        path:     '%kernel.logs_dir%/deletion.log'
        channels: ['deletion']
        formatter: monolog.brief_formatter

services:
    my_service:
        class: Monolog\Processor\IntrospectionProcessor
        tags:
            - { name: monolog.processor }
    monolog.console_formatter:
        class: Symfony\Bridge\Monolog\Formatter\ConsoleFormatter
        arguments:
            - "<fg=black;bg=green>[%%datetime%%]</fg=black;bg=green> %%start_tag%%%%message%%%%end_tag%%\n"
    monolog.brief_formatter:
        class: Monolog\Formatter\LineFormatter
        arguments:
            - "[%%datetime%%] %%message%%\n"
#            default format is
#            - "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"        

If you will add code like

$this->getContainer()->get("logger")->info("Sample info");
$this->getContainer()->get("monolog.logger.deletion")->info("Deletion channel info");
$this->getContainer()->get("monolog.logger.deletion")->error("Deletion channel error");
$this->getContainer()->get("monolog.logger.deletion")->info("Deletion channel info #2");

You will get 3 log files with such a content

Channel log file

deletion.log
[2016-11-11 12:43:18] Deletion channel info
[2016-11-11 12:43:18] Deletion channel error
[2016-11-11 12:43:19] Deletion channel info #2

Default env log file

dev.log
[2016-11-11 12:43:18] event.DEBUG: Notified event "console.command" to listener "Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure". [] {"file":"...."}
[2016-11-11 12:43:18] event.DEBUG: Notified event "console.command" to listener "Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand". [] {"file":"...."}
[2016-11-11 12:43:18] app.INFO: Sample info [] {"file":"..."}
[2016-11-11 12:43:18] deletion.INFO: Deletion channel info [] {"file":"...."}
[2016-11-11 12:43:18] deletion.ERROR: Deletion channel error [] {"...."}

Brief env.log

[2016-11-11 12:43:18] Notified event "console.command" to listener "Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure".
[2016-11-11 12:43:18] Notified event "console.command" to listener "Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand".
[2016-11-11 12:43:18] Sample info
[2016-11-11 12:43:18] Deletion channel info
[2016-11-11 12:43:18] Deletion channel error

Also notice, that because of stop_buffering: false notice after error will not appear at dev.log, dev_brief.log, but will appear at deletion.log

And you should try sentry - its great product and his owners are cool guys :)