Logging

Logging

Symfony comes with a minimalist PSR-3 logger: Symfony\Component\HttpKernel\Log\Logger. In conformance with the twelve-factor app methodology, it sends messages starting from the WARNING level to stderr).

The minimal log level can be changed by setting the SHELL_VERBOSITY environment variable:

SHELL_VERBOSITY valueMinimum log level
-1ERROR
1NOTICE
2INFO
3DEBUG

The minimum log level, the default output and the log format can also be changed by passing the appropriate arguments to the constructor of Symfony\Component\HttpKernel\Log\Logger. To do so, override the “logger” service definition.

Logging a Message

To log a message, inject the default logger in your controller or service:

  1. use Psr\Log\LoggerInterface;
  2. public function index(LoggerInterface $logger)
  3. {
  4. $logger->info('I just got the logger');
  5. $logger->error('An error occurred');
  6. $logger->critical('I left the oven on!', [
  7. // include extra "context" info in your logs
  8. 'cause' => 'in_hurry',
  9. ]);
  10. // ...
  11. }

The logger service has different methods for different logging levels/priorities. See LoggerInterface for a list of all of the methods on the logger.

Monolog

Symfony integrates seamlessly with Monolog, the most popular PHP logging library, to create and store log messages in a variety of different places and trigger various actions.

For instance, using Monolog you can configure the logger to do different things based on the level of a message (e.g. send an email when an error occurs).

Run this command to install the Monolog based logger before using it:

  1. $ composer require symfony/monolog-bundle

The following sections assume that Monolog is installed.

Where Logs are Stored

By default, log entries are written to the var/log/dev.log file when you’re in the dev environment. In the prod environment, logs are written to var/log/prod.log, but only during a request where an error or high-priority log entry was made (i.e. error() ,critical(), alert() oremergency()).

To control this, you’ll configure different handlers that handle log entries, sometimes modify them, and ultimately store them.

Handlers: Writing Logs to different Locations

The logger has a stack of handlers, and each can be used to write the log entries to different locations (e.g. files, database, Slack, etc).

Tip

You can also configure logging “channels”, which are like categories. Each channel can have its own handlers, which means you can store different log messages in different places. See How to Log Messages to different Files.

Symfony pre-configures some basic handlers in the default monolog.yaml config files. Check these out for some real-world examples.

This example uses two handlers: stream (to write to a file) and syslog to write logs using the syslog function:

  • YAML

    1. # config/packages/prod/monolog.yaml
    2. monolog:
    3. handlers:
    4. # this "file_log" key could be anything
    5. file_log:
    6. type: stream
    7. # log to var/log/(environment).log
    8. path: "%kernel.logs_dir%/%kernel.environment%.log"
    9. # log *all* messages (debug is lowest level)
    10. level: debug
    11. syslog_handler:
    12. type: syslog
    13. # log error-level messages and higher
    14. level: error
  • XML

    1. <!-- config/packages/prod/monolog.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:monolog="http://symfony.com/schema/dic/monolog"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/monolog
    9. https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    10. <monolog:config>
    11. <!-- this "file_log" key could be anything -->
    12. <monolog:handler name="file_log"
    13. type="stream"
    14. path="%kernel.logs_dir%/%kernel.environment%.log"
    15. level="debug"/><!-- log *all* messages (debug is lowest level) -->
    16. <monolog:handler name="syslog_handler"
    17. type="syslog"
    18. level="error"/><!-- log error-level messages and higher -->
    19. </monolog:config>
    20. </container>
  • PHP

    1. // config/packages/prod/monolog.php
    2. use Symfony\Config\MonologConfig;
    3. return static function (MonologConfig $monolog) {
    4. // this "file_log" key could be anything
    5. $monolog->handler('file_log')
    6. ->type('stream')
    7. // log to var/logs/(environment).log
    8. ->path('%kernel.logs_dir%/%kernel.environment%.log')
    9. // log *all* messages (debug is lowest level)
    10. ->level('debug');
    11. $monolog->handler('syslog_handler')
    12. ->type('syslog')
    13. // log error-level messages and higher
    14. ->level('error');
    15. };

This defines a stack of handlers and each handler is called in the order that it’s defined.

Note

If you want to override the monolog configuration via another config file, you will need to redefine the entire handlers stack. The configuration from the two files cannot be merged because the order matters and a merge does not allow to control the order.

Handlers that Modify Log Entries

Instead of writing log files somewhere, some handlers are used to filter or modify log entries before sending them to other handlers. One powerful, built-in handler called fingers_crossed is used in the prod environment by default. It stores all log messages during a request but only passes them to a second handler if one of the messages reaches an action_level. Take this example:

  • YAML

    1. # config/packages/prod/monolog.yaml
    2. monolog:
    3. handlers:
    4. filter_for_errors:
    5. type: fingers_crossed
    6. # if *one* log is error or higher, pass *all* to file_log
    7. action_level: error
    8. handler: file_log
    9. # now passed *all* logs, but only if one log is error or higher
    10. file_log:
    11. type: stream
    12. path: "%kernel.logs_dir%/%kernel.environment%.log"
    13. # still passed *all* logs, and still only logs error or higher
    14. syslog_handler:
    15. type: syslog
    16. level: error
  • XML

    1. <!-- config/packages/prod/monolog.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:monolog="http://symfony.com/schema/dic/monolog"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/monolog
    9. https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    10. <monolog:config>
    11. <!-- if *one* log is error or higher, pass *all* to file_log -->
    12. <monolog:handler name="filter_for_errors"
    13. type="fingers_crossed"
    14. action-level="error"
    15. handler="file_log"
    16. />
    17. <!-- now passed *all* logs, but only if one log is error or higher -->
    18. <monolog:handler name="file_log"
    19. type="stream"
    20. path="%kernel.logs_dir%/%kernel.environment%.log"
    21. level="debug"
    22. />
    23. <!-- still passed *all* logs, and still only logs error or higher -->
    24. <monolog:handler name="syslog_handler"
    25. type="syslog"
    26. level="error"
    27. />
    28. </monolog:config>
    29. </container>
  • PHP

    1. // config/packages/prod/monolog.php
    2. use Symfony\Config\MonologConfig;
    3. return static function (MonologConfig $monolog) {
    4. $monolog->handler('filter_for_errors')
    5. ->type('fingers_crossed')
    6. // if *one* log is error or higher, pass *all* to file_log
    7. ->actionLevel('error')
    8. ->handler('file_log')
    9. ;
    10. // now passed *all* logs, but only if one log is error or higher
    11. $monolog->handler('file_log')
    12. ->type('stream')
    13. ->path('%kernel.logs_dir%/%kernel.environment%.log')
    14. ->level('debug')
    15. ;
    16. // still passed *all* logs, and still only logs error or higher
    17. $monolog->handler('syslog_handler')
    18. ->type('syslog')
    19. ->level('error')
    20. ;
    21. };

Now, if even one log entry has an error level or higher, then all log entries for that request are saved to a file via the file_log handler. That means that your log file will contain all the details about the problematic request - making debugging much easier!

Tip

The handler named “file_log” will not be included in the stack itself as it is used as a nested handler of the fingers_crossed handler.

All Built-in Handlers

Monolog comes with many built-in handlers for emailing logs, sending them to Loggly, or notifying you in Slack. These are documented inside of MonologBundle itself. For a full list, see Monolog Configuration.

How to Rotate your Log Files

Over time, log files can grow to be huge, both while developing and on production. One best-practice solution is to use a tool like the logrotate Linux command to rotate log files before they become too large.

Another option is to have Monolog rotate the files for you by using the rotating_file handler. This handler creates a new log file every day and can also remove old files automatically. To use it, set the type option of your handler to rotating_file:

  • YAML

    1. # config/packages/prod/monolog.yaml
    2. monolog:
    3. handlers:
    4. main:
    5. type: rotating_file
    6. path: '%kernel.logs_dir%/%kernel.environment%.log'
    7. level: debug
    8. # max number of log files to keep
    9. # defaults to zero, which means infinite files
    10. max_files: 10
  • XML

    1. <!-- config/packages/prod/monolog.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:monolog="http://symfony.com/schema/dic/monolog"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/monolog
    9. https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
    10. <monolog:config>
    11. <!-- "max-files": max number of log files to keep
    12. defaults to zero, which means infinite files -->
    13. <monolog:handler name="main"
    14. type="rotating_file"
    15. path="%kernel.logs_dir%/%kernel.environment%.log"
    16. level="debug"
    17. max-files="10"
    18. />
    19. </monolog:config>
    20. </container>
  • PHP

    1. // config/packages/prod/monolog.php
    2. use Symfony\Config\MonologConfig;
    3. return static function (MonologConfig $monolog) {
    4. $monolog->handler('main')
    5. ->type('rotating_file')
    6. ->path('%kernel.logs_dir%/%kernel.environment%.log')
    7. ->level('debug')
    8. // max number of log files to keep
    9. // defaults to zero, which means infinite files
    10. ->maxFiles(10);
    11. };

Using a Logger inside a Service

If your application uses service autoconfiguration, any service whose class implements Psr\Log\LoggerAwareInterface will receive a call to its method `setLogger() with the default logger service passed as a service.

If you want to use in your own services a pre-configured logger which uses a specific channel (app by default), you can either autowire monolog channels or use the monolog.logger tag with the channel property as explained in the Dependency Injection reference.

Adding extra Data to each Log (e.g. a unique request token)

Monolog also supports processors: functions that can dynamically add extra information to your log entries.

See How to Add extra Data to Log Messages via a Processor for details.

Learn more

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.