㏒ WordPress production: To log or not to log?

Warning: Opinion article ahead!

Like any self-respecting server-side app, WordPress also features logging. While this is usually discouraged, themes and plugins can write to this log with error_log. Any PHP warnings or errors will also be dumped to the logs. That is, if logging is enabled.

How to enable logging: RTFM

To enable logging, edit the wp-config.php file.

First, add define( 'WP_DEBUG', true );. This will enable “debug” logging, as explained in the article Debugging in WordPress.

Then, add define( 'WP_DEBUG_LOG', true ); to make the log be written to a file, /wp-content/debug.log.

Finally, you don’t want any PHP warnings or errors to spill over into your site’s markup and cause problems. Add define( 'WP_DEBUG_DISPLAY', false ); and @ini_set( 'display_errors', 0 ); for good measure.

Great! Now you’re good to go. Debug logging has been enabled.

“Debug” logging?

But I take some issue with that. Why is logging labelled “debug” logging? Are there no other legitimate reasons to keep logs? WordPress documentation doesn’t think so:

It is not recommended to use WP_DEBUG or the other debug tools on live sites; they are meant for local testing and staging installs.

from: Debugging in WordPress

Perhaps this is OK for most people. WordPress is meant to be used by non-programmers, and generally by people who wouldn’t know what to do with a log file. But if you can jam with the console cowboys in cyberspace, I think you should keep logging on, even in production. Here’s why:

Health status

The WordPress ecosystem is a beautiful mess. Typically you will run all kinds of plugins of varying quality. I know that I am responsible for dumping some low-effort buggy code on to the WordPress.org repository, and, believe me, I am not the only one. When you put many pieces of software together in the same system, things can break. Yes, you have the PHP error logs which capture most errors, but it’s helpful to have a record of the errors and any error_log output. If you have live logs, you can address incidents faster. In my mind it’s a no-brainer.

Security

In case of someone breaking in to your site, and yes, this happens a lot, it may be possible to figure out what happened by looking at the logs. You may be able to figure out which plugin the hacker exploited and in what way, by looking at the failed attempts that preceded the successful break-in.

For extra credit, set up a cron job that stores backups off-site to another server. Evenif a hacker manages to clear the logs, you still have data for your forensics investigation.

Disk space

The main arguments against logging are that

  1. writing logs degrades performance, and
  2. writing logs can fill up disk space

Argument #1 is not even worth discussing in the year of our Lord 2023, when SSDs are the norm on server hardware. I can guarantee you that even if you care about performance a lot, the milliseconds wasted on logging are the least of your concerns. I’d much rather have logs available when I need them, if this is going to save me time and money by avoiding or fixing incidents.

Argument #2 is perhaps less laughable, but still invalid. The disk space argument is only brought up by people who haven’t heard of logrotate.

Let me show you my /etc/logrotate.d/wordpress file on this blog’s server:

/usr/share/nginx/alexg/wp-content/debug.log
{
        su www-data www-data
        rotate 24
        copytruncate
        weekly
        missingok
        notifempty
        compress
}

And now let me show you the log files that this generates:

$ ls -l /usr/share/nginx/alexg/wp-content/debug*
-rw-r--r-- 1 www-data www-data 105705 Mar 12 01:43 /usr/share/nginx/alexg/wp-content/debug.log
-rw-r--r-- 1 www-data www-data   3928 Mar  5 06:34 /usr/share/nginx/alexg/wp-content/debug.log.1.gz
-rw-r--r-- 1 www-data www-data   3181 Jan  2 06:09 /usr/share/nginx/alexg/wp-content/debug.log.10.gz
-rw-r--r-- 1 www-data www-data   2532 Dec 25 06:36 /usr/share/nginx/alexg/wp-content/debug.log.11.gz
-rw-r--r-- 1 www-data www-data   2991 Dec 19 04:44 /usr/share/nginx/alexg/wp-content/debug.log.12.gz
-rw-r--r-- 1 www-data www-data   3256 Dec 11 03:15 /usr/share/nginx/alexg/wp-content/debug.log.13.gz
-rw-r--r-- 1 www-data www-data   4397 Dec  5 05:30 /usr/share/nginx/alexg/wp-content/debug.log.14.gz
-rw-r--r-- 1 www-data www-data   2284 Nov 27 06:15 /usr/share/nginx/alexg/wp-content/debug.log.15.gz
-rw-r--r-- 1 www-data www-data   2866 Nov 20 06:24 /usr/share/nginx/alexg/wp-content/debug.log.16.gz
-rw-r--r-- 1 www-data www-data   3339 Nov 13 04:22 /usr/share/nginx/alexg/wp-content/debug.log.17.gz
-rw-r--r-- 1 www-data www-data  15378 Nov  6 06:20 /usr/share/nginx/alexg/wp-content/debug.log.18.gz
-rw-r--r-- 1 www-data www-data   3194 Oct 30 06:18 /usr/share/nginx/alexg/wp-content/debug.log.19.gz
-rw-r--r-- 1 www-data www-data  38415 Feb 27 06:30 /usr/share/nginx/alexg/wp-content/debug.log.2.gz
-rw-r--r-- 1 www-data www-data   3619 Oct 24 06:24 /usr/share/nginx/alexg/wp-content/debug.log.20.gz
-rw-r--r-- 1 www-data www-data   3827 Oct 16 05:11 /usr/share/nginx/alexg/wp-content/debug.log.21.gz
-rw-r--r-- 1 www-data www-data   2213 Oct  9 05:52 /usr/share/nginx/alexg/wp-content/debug.log.22.gz
-rw-r--r-- 1 www-data www-data   3332 Oct  3 06:05 /usr/share/nginx/alexg/wp-content/debug.log.23.gz
-rw-r--r-- 1 www-data www-data  13983 Sep 25 06:24 /usr/share/nginx/alexg/wp-content/debug.log.24.gz
-rw-r--r-- 1 www-data www-data   2863 Feb 19 05:09 /usr/share/nginx/alexg/wp-content/debug.log.3.gz
-rw-r--r-- 1 www-data www-data   3913 Feb 13 05:45 /usr/share/nginx/alexg/wp-content/debug.log.4.gz
-rw-r--r-- 1 www-data www-data   3276 Feb  5 06:00 /usr/share/nginx/alexg/wp-content/debug.log.5.gz
-rw-r--r-- 1 www-data www-data   3354 Jan 29 05:38 /usr/share/nginx/alexg/wp-content/debug.log.6.gz
-rw-r--r-- 1 www-data www-data   4171 Jan 22 06:12 /usr/share/nginx/alexg/wp-content/debug.log.7.gz
-rw-r--r-- 1 www-data www-data   2835 Jan 15 06:21 /usr/share/nginx/alexg/wp-content/debug.log.8.gz
-rw-r--r-- 1 www-data www-data   2694 Jan  8 05:04 /usr/share/nginx/alexg/wp-content/debug.log.9.gz

If you’re wondering how much space this takes:

$ du -ch /usr/share/nginx/alexg/wp-content/debug*
108K    /usr/share/nginx/alexg/wp-content/debug.log
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.10.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.11.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.12.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.13.gz
8.0K    /usr/share/nginx/alexg/wp-content/debug.log.14.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.15.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.16.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.17.gz
16K /usr/share/nginx/alexg/wp-content/debug.log.18.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.19.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.1.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.20.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.21.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.22.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.23.gz
16K /usr/share/nginx/alexg/wp-content/debug.log.24.gz
40K /usr/share/nginx/alexg/wp-content/debug.log.2.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.3.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.4.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.5.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.6.gz
8.0K    /usr/share/nginx/alexg/wp-content/debug.log.7.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.8.gz
4.0K    /usr/share/nginx/alexg/wp-content/debug.log.9.gz
272K    total

So, full logs for the last 24 days are taking up 272 kilobytes when compressed. Disk space well spent, if you ask me.

wp-console plugin

Even a console cowboy will agree that sometimes it’s more convenient to view the logs via the WordPress admin. For these rare occasions, I use wp-console. Next to the Console tab, which is very useful by itself, there is the Debug Log tab.

Make a habbit out of it

Let’s take a step back: WordPress is usually the main engine of a small business. Running a business means executing a set of processes repeatedly, with the hope that these processes will create value.

I like to be proactive: One of the processes that I execute repeatedly as part of my business, is to visit the site logs every 3 months. I have a calendar reminder for this, pointing to a ticket that’s all about inspecting logs. As I identify problems that I want to fix, I open separate tickets for those as well.

Logs are compressed

First, I look at the logs. Today’s file is uncompressed (debug.log), while older files are gzipped (debug.*.log.gz). Instead of ungzipping the files, I usually like to inspect them in place via an ssh shell, using the zTools:

  • use zcat instead of cat
  • use zless instead of less
  • use zgrep instead of grep
  • use zfgrep instead of fgrep
  • use zegrep instead of egrep

How I inspect logs

Usually, when you look at such logs, you will see one single type of warning or notice being repeated a lot, possibly because of a plugin or theme.

Let’s say you see a “PHP Notice: Undefined variable: foo” line a lot, coming from some plugin. It’s not causing any real problems, but it gets written out with every HTTP request. You are not responsible for maintaining this plugin, but this line is obscuring your view of the logs. So, just look at the logs without it:

zfgrep -v 'PHP Notice:  Undefined variable: foo' debug.log.*.gz

Now you will see a listing of all your logs, except for that notice. The next most common notice will become apparent. Let’s say that this is a “PHP Notice: Undefined index: bar” in another plugin, that you are also not the maintainer of. You can filter for that as well. Just daisy-chain your greps using pipes:

zfgrep -v 'PHP Notice:  Undefined variable: foo' debug.log.*.gz | fgrep -v 'PHP Notice:  Undefined index: bar'

You could go into the source code of these files and add a @ character in front that line, to suppress the warnings, at least until the plugin in question is updated by the developer. But I don’t recommend it. Too much effort for too little gain. gzip does an excellent job of compressing similar strings repeated thousands of times. This is literally what huffman trees were built for.

Rinse and repeat

Continue chaining greps, until you get to an error that you care about. Maybe it’s an error that looks ugly, worries you, or just feels bad. Maybe an include is missing. Maybe you can investigate, or even fix the issue. Or maybe you feel like you have to notify the developer via the plugin’s support forum. Maybe you’ll decide that a plugin causes too many errors and is easily replaceable by another similar one.

Deprecated code

Depending on what you are doing, you may want to hide deprecation notices. At the time of this writing, even the latest version of WordPress core writes a lot of deprecation notices if you are running PHP 8. If you’re not a core developer, it’s not our job to fix them, so filter them out with: | grep -v 'PHP Deprecated.*/var/www/wordpress/wp-(includes|admin)'. See what I did there? I filtered out only Deprecation warning lines coming from the wp-includes or wp-admin directories, but not Deprecation warnings from other components (plugins or themes). Regular expressions are your friend.

Code that is deprecated in PHP8 will stop running in PHP9 when it arrives sometime this year. Before you upgrade your PHP, you may want to know which plugins are going to have problems. A plugin developer who is proactive will be fixing these.

It’s always better to be proactive with these things. Also, be mindful of the quality of the code you are running on your servers. A plugin that dumps too many warnings into the server logs (or into the browser’s console log, for that matter), is a low-quality plugin.

Conclusion (and shameless ref link plug)

If you are proactive in that way, you will catch incidents before they occur.

Of course, you can only do this if you are running your WordPress on a server with shell access. Managed hosting is good for most people, but if you are the hands-on type of person, you will want to have shell access. With ssh access you can harden your server’s security yourself, and then noone can ruin your server for you. Be an grown-up, ruin it yourself!

This blog runs on a DigitalOcean droplet. I honestly cannot recommend them enough. I am happy because my droplet is always available, with great up-time, great documentation, great admin screens, never even needed to contact support. They have a good choice of data centers around the world, an API for spinning up new instances programmatically, and if there is ever any downtime or even a network slowdown, they let you know way in advance. Taking snapshots of your droplet is a breeze. Some of the lowest prices I’ve seen, starting from $4 per month for a low traffic site. If you don’t feel like setting up everything up by yourself, they also have a WordPress-WooCommerce Droplet in their marketplace.

If you sign up with this referral link you get $200 of credit which you can spend over the first 60 days. And yes, if you spend $25 or more with them, I get $25 for my trouble too, so thank you for that!

DigitalOcean Referral Badge

1 thought on “㏒ WordPress production: To log or not to log?

Leave a Reply

Your email address will not be published. Required fields are marked *