Some might already know the improved error handler parts of the Tools plugin from CakePHP 2 or 3.
Here I rewrite and publish those now for the 2020 CakePHP 4 scope.
Motivation
The main goal of the error.log
is to notify about internal errors of the system. By default, there would also be a lot of noise in there, though.
Most 404s triggered should not be part of your error log, for example.
Every single access from bots or outdated links into your web app would be in that same error.log
.
This cloaks the actual errors happening that require fixing.
With bots trying a lot of combinations or URLs, this can easily become a bigger problem.
You can either completely ignore them using the ignore list of skipLog
to silence, or better yet put them into their own space (file).
The latter allows you to at least look through them once in a while and find certain patterns.
For example, you could have switched over some URLs but forgot to put 301 redirects in place, that could be an improvement for search engines to
link to the new content.
So now let’s see how we can separate 404s from actual errors.
Usage
In our app.php we first want to introduce the new log type:
'Log' => [
'debug' => [
'scopes' => false,
],
'error' => [
'scopes' => false,
],
'404' => [
'className' => 'File',
'file' => '404',
'levels' => ['error'],
'scopes' => ['404'],
],
],
Note how I also made sure the existing types "error" and "debug" are scoped to false to prevent those errors to be logged twice.
Now we want to replace the core error handling with the improved one of the Tools plugin.
Make sure the plugin is installed, and that you follow the docs.
In your config/bootstrap.php
you need to load the improved ErrorHandler now by switching the use statements:
// Switch Cake\Error\ErrorHandler to
use Tools\Error\ErrorHandler;
...
(new ErrorHandler(Configure::read('Error')))->register();
And in your src/Application.php
, the same for the middleware:
use Cake\Http\BaseApplication;
// Switch Cake\Error\Middleware\ErrorHandlerMiddleware to
use Tools\Error\Middleware\ErrorHandlerMiddleware;
...
->add(new ErrorHandlerMiddleware())
These two will automatically switch the ErrorLogger now to the one that can properly reassign those 404s.
It is important to note that any 404 that is caused by an internal link is still treated as normal error though.
So if you link from your own website to a dead link on that same page, the "referer" here indicates that this is not
an issue invoked from the outside and thus also something you need to fix.
Switching logger
You can still switch the logger here if you want to get rid of files.
This is highly recommended once you have left the stage of testing/experimenting.
Files are hard to read/filter, and they are bound to the server itself.
One easy to use option is the DatabaseLog plugin that stores them in an SQLite or an actual database (e.g. Mysql/Postgres).
The logs become filterable, duplicates can be cleaned up easily and you got more control over certain errors/issues triggering notifications.
So you can, for example, listen for certain errors and send out emails asynchronously to you where needed. This way you get alerted right away over certain critical issues.
We just have to switch out the class name here, once the plugin is installed:
'Log' => [
'debug' => [
'scopes' => false,
'className' => 'DatabaseLog.Database',
],
'error' => [
'scopes' => false,
'className' => 'DatabaseLog.Database',
],
'404' => [
'className' => 'DatabaseLog.Database',
'file' => '404',
'levels' => ['error'],
'scopes' => ['404'],
],
],
Tip: You can test all that with the Sandbox app and the downloadable source code.
Other loggers
Of course, you can also look into other even more sophisticated ways of storing logs, e.g. using Monolog and Logstash.
Those will become necessary once you reach a certain amount of logs coming in.
Until then the database log solution could suffice as a good tradeoff.
Update 2024-01: Cake 5
For CakePHP 5+, the scopes should be set to null
instead:
'scopes' => null,
Also, use the new trap class instead:
use Cake\Error\ErrorTrap;
use Tools\Error\ExceptionTrap;
(new ErrorTrap(Configure::read('Error')))->register();
(new ExceptionTrap(Configure::read('Error')))->register();
For details see docs.