Question

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.

Was it helpful?

Solution 2

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...
    }

}

OTHER TIPS

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.

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}

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]') ;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top