The Joy of Rotating Logs in Symfony

logs

Switching from my hand-made, hodgepodge, cobbled together collection of libraries that was trying to act more and more like a "framework" every day to a full-blown real, properly developed, properly tested framework like Symfony continues to be the best decision I've made in my programming career thus far. I've learned so much about clean code, design patterns, SOLID principles and many other programming best practices from working within Symfony's MVC (Model View Controller) pattern and reading its documentation and code when I need to extend it to bow to my every whim.

One great feature of Symfony is the highly configurable logging, implemented via monolog.
Way back in my pre-Symfony days, when I encountered an exception in development, my first step was firing up xdebug and stepping through the code line by line to inspect variables and watch where the logic was taking me to try and find the root cause of the bug. Now my first step is to head to the log file and see what was happening at the time of the exception (for JSON calls, anyway. The log is spit out on the screen if the result of the call was to render a page).

I prefer text logging verses logging to a database, for the simplicity of writing and reading.
One problem with text logging is that the files can grow too big, too fast. Most text editors will choke on a log file that is too big. Or, the worst case scenario may be encountered, which is the log file becomes so large that it eats up all available disk space on the server, bringing the server to its knees. Thankfully I've only encountered this once, due to leaving a debug flag on in production. Which brings up another great thing about using Symfony, thanks to the app_dev.php file, no more debug flags.

To combat the issue of log files growing too large, one of the first things I do in any new Symfony project is to set the log type to rotating_file which is a standard feature in monolog, but beautifully exposed via the Symfony configuration file.

This can be set in both your prod and dev config files, to give the desired behavior for each environment.

Development

In the config_dev.yml simply change the main handler type from stream to rotating_file and just below that add the max_files to keep. I set mine to 1 in dev, since I don't believe that a development log file needs to persist past a single day. Voilà, a fresh clean log file every morning when I access my log in the development environment.

# app/config/config_dev.yml
monolog:
    handlers:
        main:
            # change this line
            type: rotating_file
            # add this line
            max_files: 1
            path: '%kernel.logs_dir%/%kernel.environment%.log'
            level: debug           
            channels: ['!event']
        console:
            type: console
            process_psr_3_messages: false
            channels: ['!event', '!doctrine', '!console']
        # To follow logs in real time, execute the following command:
        # `bin/console server:log -vv`
        server_log:
            type: server_log
            process_psr_3_messages: false
            host: 127.0.0.1:9911

Production

In your project's production environment configuration file - config_prod.yml, we'll make the same changes, but under the nested handler. The nested handler is another great aspect of logging with monolog from inside of a Symfony project. If the main handler is set to a fingers_crossed type, it caches the log for every request. The only time that it logs anything, is if the action_level threshold is passed. In the example below, if a log level of error or higher is caught, it passes the cached log to the handler set in the handler parameter. This allows logging of the full request stack, but only in the event that an error occurs. This way only meaningful logs are stored, but we get the entire log for that request. This is another feature that keeps are logs from growing too large.

# app/config/config_prod.yml
monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
        nested:
            # change this line
            type:  rotating_file
            # add this line
            max_files: 7
            path:  "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug           
        console:
            type: console
            process_psr_3_messages: false

Log files location

Another thing I like to do with the logging, is change the location of where my log files are stored. By default, the log files are stored in project_root/var/logs. My deployment process creates each new release in a releases directory in my production environment, and my webroot is nothing more than a symlink that that points to the most recent releases directory. If I keep the default log file location, each release uses its own directory to store log files. By moving the logs directory up one level, each release uses a shared log location so my log files persist across new releases of my project. Another benefit to this is not having to worry about setting write permissions for the www-data user that Apache uses for the log directory in each new release. It is easy to alter the getLogDir() method in your AppKernel class to use one level up, or any other custom location in which you wish to store your log files.

    // project_root/app/AppKernel.php
    public function getLogDir()
    {
        // I've inserted the '../' to move the logs up one directory from the project root
        return dirname(__DIR__).'/../var/logs';
    }

Easy log file viewing

As easy as it is to view log files in development, to logs in your production environment, you have to log in to your production server, navigate to the log directory and then open them with the log viewer/text editor of your choice. It was those couple extra steps that inspired me, after not finding any existing solutions that I liked, to write a Symfony bundle that allows you to view your logs from your web browser.
My WebLogViewerBundle includes color-coded, collapsable log levels, and formatted JSON and SQL. It can be included via Composer (packagist) and the source can be found on GitHub.

Conclusion

Hopefully, you can see the benefits and ease of using rotating log files in your Symfony projects. How do you configure your logging in Symfony?

If you liked this post, you can follow me @ToddEidson on Twitter for the best way (for now) to be notified of future blog posts.

Date Published: 24 November, 2017

Tags: symfony logging

About Todd

Todd Eidson is a full stack application developer and life-long learner. He is a pragmatic programmer, a believer in clean coding, and an evangelist for extensible and reusable code. His hobbies include collecting old jazz records and going to live music performances.

North Central Ohio, US
toddeidson[dot]info

Obligatory Disclaimer

All opinions are my own, probably wrong, and subject to change without notice.

© 2017 Todd Eidson. All content is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.