CakePHP error log - can I exclude 404 errors?
Asked Answered
A

4

5

The error log of my CakePHP app is full of 404 errors. Can I exclude these MissingControllerExceptions from appearing in the error log? Using Cake 2.3.

Ardrey answered 24/3, 2014 at 10:58 Comment(6)
Ignoring the errors would pretty much be the ostrich approach. They're thrown for a reason. Apparently your application has broken links in them and they're clicked by your users. Might be something you'd want to fix.Wheeled
@Wheeled - the errors are mainly for resources that existed on a previous version of the site but no longer exist. In these cases, there is no relevant new place to redirect to, and redirecting to the homepage would be confusing, so the 404 is not really an error I want to know about.Ardrey
Unless you are not using the proper Helpers, missing resources should not trigger a MissingControllerException. Either way, to catch any of the "old" URLs, you should use Routes to rewrite them to their new location (or a custom 404 page informing the user they're using outdated links) instead of surpressing exceptions.Wheeled
I am talking about old URLs that existed before the site was a CakePHP app, particularly image files. For example, /images/sm/image.jpg. There is no "new location" for this image. If I can stop URLs like this from triggering a MissingControllerException then I would be very happy (though I am not going to put in a redirect for every one of these files, as there are hundreds!)Ardrey
I understand that, but you don't have to create a rule for every individual image. You could for example just rewrite all the old image links and still keep a proper working Exception system. For example, in your app/webroot/.htaccess, before the Cake "overall" rewrite, just add: RewriteRule ^images/.* - [L,R=404]. That will instantly throw a 404 from Apache (so Cake isn't doing anything with it).Wheeled
PHP, Will? Am disappoint.Bebebebeerine
T
9

Simply redirecting or removing these URLs is not going to cut it.

A busy site will get hit by hundreds of "random" 404s every day, most from far east countries checking for exploits or URLs such as "/wp-admin".

Logging these with a full stack trace is completely unnecessary

Solution

You can override the default error handler in CakePHP, and log to app/tmp/logs/404.log instead.

In your app/Config/core.php file, define the class you want to handle exceptions:

Configure::write('Error', array(
    'handler' => 'MyCustomErrorHandler::handleError',
    'level' => E_ALL & ~E_DEPRECATED,
    'trace' => true
));

Create the class within app/Lib/Error and include using App::uses in your app/Config/bootstrap.php file:

App::uses('MyCustomErrorHandler', 'Lib/Error');

Make an exact copy of the original ErrorHandler class, just change the class name, and somewhere within the handleException method check which exception is being thrown, and log somewhere else. It will look a bit like this;

App::uses('ErrorHandler', 'Error');

class MyCustomErrorHandler {

    public static function handleException(Exception $exception) {

         // some code...

         if (in_array(get_class($exception), array('MissingControllerException', 'MissingActionException', 'PrivateActionException', 'NotFoundException'))) {
             $log = '404';
             $message = sprintf("[%s]", get_class($exception));
         }

         // more code...
    }

}
Tuinal answered 23/5, 2014 at 13:4 Comment(1)
Finally got round to implementing this and it works well, except I had to change the config for Exception, not Error. Also instead of copying the whole file I just extended it.Ardrey
P
14

versions above 2.4 (as mentioned by null) have an undocumented feature for this purpose.

just modify your exception config:

Configure::write('Exception', array(
    'handler' => 'ErrorHandler::handleException',
    'renderer' => 'ExceptionRenderer',
    'log' => true,
    'skipLog'=>array(
        'MissingControllerException'
    )
));

skipLog takes names of exception classes to be excluded from log.

Pashalik answered 11/5, 2015 at 8:1 Comment(2)
It is mentionned in the 2.4 migration guide. New configuration option skipLog has been added, to allow skipping certain Exception types to be logged. Configure::write('Exception.skipLog', array('NotFoundException', 'ForbiddenException')); will avoid these exceptions and the ones extending them to be be logged when 'Exception.log' config is trueConjuration
+1: for busy sites, this solution may be better (not generalizing) because error logs require filewriting. this wont sit good for servers with limited resources.Godmother
T
9

Simply redirecting or removing these URLs is not going to cut it.

A busy site will get hit by hundreds of "random" 404s every day, most from far east countries checking for exploits or URLs such as "/wp-admin".

Logging these with a full stack trace is completely unnecessary

Solution

You can override the default error handler in CakePHP, and log to app/tmp/logs/404.log instead.

In your app/Config/core.php file, define the class you want to handle exceptions:

Configure::write('Error', array(
    'handler' => 'MyCustomErrorHandler::handleError',
    'level' => E_ALL & ~E_DEPRECATED,
    'trace' => true
));

Create the class within app/Lib/Error and include using App::uses in your app/Config/bootstrap.php file:

App::uses('MyCustomErrorHandler', 'Lib/Error');

Make an exact copy of the original ErrorHandler class, just change the class name, and somewhere within the handleException method check which exception is being thrown, and log somewhere else. It will look a bit like this;

App::uses('ErrorHandler', 'Error');

class MyCustomErrorHandler {

    public static function handleException(Exception $exception) {

         // some code...

         if (in_array(get_class($exception), array('MissingControllerException', 'MissingActionException', 'PrivateActionException', 'NotFoundException'))) {
             $log = '404';
             $message = sprintf("[%s]", get_class($exception));
         }

         // more code...
    }

}
Tuinal answered 23/5, 2014 at 13:4 Comment(1)
Finally got round to implementing this and it works well, except I had to change the config for Exception, not Error. Also instead of copying the whole file I just extended it.Ardrey
A
8

Building on robmcvey's solution, here is what I had to do for CakePHP 2.6.

Configuration

In app/Config/core.php update the Exception configuration:

Configure::write('Exception', array(
    'handler' => 'AppErrorHandler::handleException',
    'renderer' => 'ExceptionRenderer',
    'log' => true
));

In app/Config/bootstrap.php add a CakeLog configuration:

CakeLog::config('not_found', array(
    'engine' => 'FileLog',
    'types' => array('404'),
    'file' => '404',
));

The type of 404 is the logging level it will listen in on for anything being written to logs e.g. CakeLog::write('404', 'That was not found.');

Extend ErrorHandler

Create the file app/Lib/Error/AppErrorHandler.php. Here we will extend Cake's ErrorHandler, overwritting three methods; handleException(), _getMessage(), and _log().

<?php
class AppErrorHandler extends ErrorHandler {

/**
 * List of Cake Exception classes to record to specified log level.
 *
 * @var array
 */
    protected static $_exceptionClasses = array(
        'MissingControllerException' => '404',
        'MissingActionException' => '404',
        'PrivateActionException' => '404',
        'NotFoundException' => '404'
    );

    public static function handleException(Exception $exception) {
        $config = Configure::read('Exception');
        self::_log($exception, $config);

        $renderer = isset($config['renderer']) ? $config['renderer'] : 'ExceptionRenderer';
        if ($renderer !== 'ExceptionRenderer') {
            list($plugin, $renderer) = pluginSplit($renderer, true);
            App::uses($renderer, $plugin . 'Error');
        }
        try {
            $error = new $renderer($exception);
            $error->render();
        } catch (Exception $e) {
            set_error_handler(Configure::read('Error.handler')); // Should be using configured ErrorHandler
            Configure::write('Error.trace', false); // trace is useless here since it's internal
            $message = sprintf("[%s] %s\n%s", // Keeping same message format
                get_class($e),
                $e->getMessage(),
                $e->getTraceAsString()
            );

            self::$_bailExceptionRendering = true;
            trigger_error($message, E_USER_ERROR);
        }
    }

/**
 * Generates a formatted error message
 *
 * @param Exception $exception Exception instance
 * @return string Formatted message
 */
    protected static function _getMessage($exception) {
        $message = '';
        if (php_sapi_name() !== 'cli') {
            $request = Router::getRequest();
            if ($request) {
                $message .= $request->here() . " Not Found";
            }
        }
        $message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n";
        return $message;
    }

/**
 * Handles exception logging
 *
 * @param Exception $exception The exception to render.
 * @param array $config An array of configuration for logging.
 * @return bool
 */
    protected static function _log(Exception $exception, $config) {
        if (!empty(self::$_exceptionClasses)) {
            foreach ((array)self::$_exceptionClasses as $class => $level) {
                if ($exception instanceof $class) {
                    return CakeLog::write($level, self::_getMessage($exception));
                }
            }
        }
        return parent::_log();
    }
}

You can customize the $_exceptionClasses array to catch which exceptions you want and send them to different logs. The _getMessage() method has been simplified to remove the attributes.

Result

Random URLs like /exploitable-plugin will now log to tmp/logs/404.log.

2015-04-01 16:37:54 404: /exploitable-plugin Not Found
Stack Trace:
#0 /var/example.com/app/index.php(146): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#1 {main}
Annora answered 1/4, 2015 at 20:40 Comment(2)
Worked great, but also needed to add App::uses('MyCustomErrorHandler', 'Lib/Error'); inside core.php above where we defined the Exception configuration.Sekofski
To clarify isick's comment, if you added AppErrorHandler as your new handler, you should add App::uses('AppErrorHandler', 'Lib/Error'); -- not "MyCustomErrorHandler"Unpopular
C
0

I assume that your viewers hit those pages from results of search engines. Since you're a webmaster of this page, you could try to use something like Google removal tools https://www.google.com/webmasters/tools/removals . So there won't be any more liks to these pages. It is a little costly in time, but I don't see a way to exclude missing controller actions form logging.

Anyway here you have everything on logging in CakePHP: https://www.google.com/webmasters/tools/removals?pli=1

Edit:

Actually I'd found a way, but it might make you trouble in the future if you forgot about it. Add this to the end of bootstrap.php :

$matches = array();
preg_match('/^\/(.+?)\//', $_SERVER['REQUEST_URI'],$matches);
if(!App::import('Controller', $matches[1])){
   die('404 Error![some_random_chars_so_you_can_easy_find_it_in_the_future]') ;
}
Ciera answered 10/4, 2014 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.