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}