Error Handling and Logging

If we try to access the URL http://hostname/blog/index.php?page=EditPost&id=100, we will see the following error page because the post with ID 100 does not exist in our blog system yet. We would like to customize this error page so that it looks more consistent with the other blog pages in layout. We also want to log this kind of errors to study user behaviors. In this section, we will accomplish these two tasks.

Info: An important task in a Web application is error handling which is often associated logging. There are two types of errors that may occur in a PRADO application: those caused by developers and those by end-users. The former should be resolved before the application is put into production, while the latter are usually within the initial design scope and should be handled nicely (e.g. log the error and display a special page instructing the end-user what to do next.) PRADO implements a very flexible yet powerful framework for error handling and logging.

Customizing Error Handling

PRADO implicitly loads a TErrorHandler module to handle errors. We would like to customize this module so that our blog system can display a customized page for errors caused by end-users. We thus modify the application configuration as follows:

......
<modules>
    ......
    <module class="Application.BlogErrorHandler" />
    ......
</modules>
......

The class BlogErrorHandler as specified in the above is a new error handler module that we will create next. It extends and replaces the default TErrorHandler module.

We create a file named protected/BlogErrorHandler.php as follows. The class BlogErrorHandler overrides two methods of TErrorHandler:

  • getErrorTemplate() - this method returns the template string used for displaying a particular user error.
  • handleExternalError() - this method is invoked when a user error occurs and displays the error.
Prado::using('System.Exceptions.TErrorHandler');
Prado::using('Application.BlogException');

class BlogErrorHandler extends TErrorHandler
{
	/**
	 * Retrieves the template used for displaying external exceptions.
	 * This method overrides the parent implementation.
	 */
	protected function getErrorTemplate($statusCode,$exception)
	{
		// use our own template for BlogException
		if($exception instanceof BlogException)
		{
			// get the path of the error template file: protected/error.html
			$templateFile=Prado::getPathOfNamespace('Application.error','.html');
			return file_get_contents($templateFile);
		}
		else // otherwise use the template defined by PRADO
			return parent::getErrorTemplate($statusCode,$exception);
	}

	/**
	 * Handles external error caused by end-users.
	 * This method overrides the parent implementation.
	 * It is invoked by PRADO when an external exception is thrown.
	 */
	protected function handleExternalError($statusCode,$exception)
	{
		// log the error (only for BlogException)
		if($exception instanceof BlogException)
			Prado::log($exception->getErrorMessage(),TLogger::ERROR,'BlogApplication');
		// call parent implementation to display the error
		parent::handleExternalError($statusCode,$exception);
	}
}

In the above, we specify that when a BlogException is thrown, we use a new template protected/error.html to display the error. Therefore, we need to create the BlogException class and replace all the occurances of THttpException in our code (such as EditUser and ReadPost pages). We also need to create the error template protected/error.html. The BlogException class extends THttpException and is empty. The class file is saved as protected/BlogException.php.

class BlogException extends THttpException
{
}

Below is the content in our error template protected/error.html. Note, the template is not a PRADO template because it only recognizes very limited number of tokens, such as %%ErrorMessage%%, %%ServerAdmin%%.

<html>
<head>
<title>%%ErrorMessage%%</title>
</head>
<body>
<div id="page">
<div id="header">
<h1>My PRADO Blog</h1>
</div>
<div id="main">
<p style="color:red">%%ErrorMessage%%</p>
<p>
The above error happened when the server was processing your request.
</p>
<p>
If you think this is a server error, please contact the <a href="mailto:%%ServerAdmin%%">webmaster</a>.
</p>
</div>
</body>
</html>

Logging Errors

In the handleExternalError() method of BlogErrorHandler, we invoke Prado::log() to log the error if it is of type BlogException. The error is logged in memory. To save the logs into permanent medium such as file or database, we need to enable appropriate error logging routes. This is done in the application configuration as follows:

......
<modules>
    ......
    <module id="log" class="System.Util.TLogRouter">
        <route class="TFileLogRoute" Categories="BlogApplication" />
    </module>
    ......
</modules>
......

In the above, we add a log route that saves logs into a file. We also specify the category filter as BlogApplication such that only the log messages of the selected categories are saved. This helps reduce the size of the log file and also improves its readability.

Testing

To see how our blog systems reponds to an invalid user request, we test the URL http://hostname/blog/index.php?page=posts.ReadPost&id=100. We shall see the following error page which is different from what we saw earlier on.

If we search under the directory protected/runtime, we will find a file named prado.log. This is the log file that we just configured to save the error messages. The file may contain contents like the following,

Jun 28 22:15:27 [Error] [BlogApplication] Unable to find the specified post.
Jun 29 08:42:57 [Error] [BlogApplication] Unable to find the specified post.