8

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?

Wirone
  • 3,304
  • 1
  • 29
  • 48

1 Answers1

11

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 :)

Andrew Zhilin
  • 1,654
  • 16
  • 11
  • Yeah I thought it would be better to use MonologBundle's config (handlers with channels defined), but provided DI config was already there (existing app, previous developer had some own conventions) and I did not want to refactor it that much. But it seems like it's only way. Thank you very much for your example, I'll try it (we have pending issue about loggers refactoring). – Wirone Nov 14 '16 at 15:34
  • After really long time I finally managed to refactor our loggers with MonologBundle's config and yes, everything can be achieved with handlers stack. It's not intuitive sometimes, but there is a lot of handlers, processors and formatters out-of-the-box so each log record can go where you want. Thanks. – Wirone Mar 07 '17 at 15:19
  • As an alternative you can use Monolog-Cascade https://github.com/theorchard/monolog-cascade which is framework agnostic and rely on a simple yaml, json or php config file – Raph Mar 20 '18 at 15:12
  • Monolog-cascade is not so up-to-date though, see https://github.com/Seldaek/monolog/pull/1580. – wearego Dec 10 '21 at 10:14