The Joy of Rotating Logs in Symfony
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 subscribe to the rss feed or follow me @ToddEidson on Twitter to be notified of future blog posts.Date Published: 24 November, 2017