I've been reading on in particular 'error logging' And I have come up with the function 'error_log' which seem to be a good tool to use to handle the error logging. But how is the smoothest and best way to use it?
If I have a
try { //try a database connection... } catch (PDOException $e) { error_log($e->getMessage(), 3, "/var/tmp/my-errors.log"); }
This would log the error in the my-errors.log file. But what If I sometime need to change the position of where the file is, a new folder, or something. If I have tons of files I need to change them all.
Now I started of thinking to use a variable to set the path to the error log. Sure that could work, but what If I want to use the error_log in a function or class method? Then I would need to set the variable as global, but that is considered bad practise! But what If I shouldn't use the function deep in a class, wouldn't that also be considered bad practise? What is a good solution here?
<?php function legit() { try { if (1 == 1) { throw new Exception('There was an error here'); } } catch (Exception $e) { throw new Exception('throw the error to the try-catch outside the function...'); } } try { legit(); } catch (Exception $e) { echo 'error here' . $e->getMessage(); //log it }
This is an example of what I was talking about above (Not having the logging deep in a class/function... Is it a good way?)
Furtheron:
I am not quite sure how I should use the Exceptions in general. Let's say I want to do a INSERT to a database with SQL inside a method, would I use a try/catch and then rethrow the exception if it fails? Is that considered good practise? Examples please.
error log. The file that stores instances of errors and failures encountered by the system. error log entry. A record in the system error log that describes a hardware failure, a software failure, or an operator message. An error log entry contains captured failure data.
Enable Error Logging in php. To log errors in PHP, open the php. ini file and uncomment/add the following lines of code. If you want to enable PHP error logging in individual files, add this code at the top of the PHP file. ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL);
An error message often contains many details about where the issue popped up and what went wrong. It's an efficient and automated way to detect new exceptions and receive context for the raised exception. In short, logging is crucial for your monitoring tool to track various metrics, such as error rates.
Firstly, I'd like to commend you for looking at the standard error methods within PHP. Unfortunately error_log
has some limitations as you found out.
This is a long answer, read on to find out about:
trigger_error
and set_error_handler
TL;DR Use trigger_error
for raising errors and set_error_handler
for logging them.
When things don't go as expected in your program, you will often want to raise an error so that someone or something is notified. An error is for a situation where the program may continue, but something noteworthy, possibly harmful or erroneous has occurred. At this point many people want to log the error immediately with their logging package of choice. I believe this is exactly the wrong thing to do. I recommend using trigger_error
to raise the error so that it can be handled with a callback set by set_error_handler
. Lets compare these options:
Logging the error directly
So, you have chosen your logging package. Now you are ready to spread the calls to your logger wherever an error occurs in your code. Lets look at a single call that you might make (I'll use a similar logger to the one in Jack's answer):
Logger::getLogger('standard')->error('Ouch, this hurts');
What do you need in place to run this code?
Class: Logger Method: getLogger Return: Object with method 'error'
These are the dependencies that are required to use this code. Everyone who wants to re-use this code will have to provide these dependencies. This means that a standard PHP configuration will no longer be sufficient to re-use your code. With the best case, using Dependency Injection you still require a logger object to be passed into all of your code that can emit an error.
Also, in addition to whatever the code is responsible for, it also has responsibility for logging the error. This goes against the Single Responsibility Principle.
We can see that logging the error directly is bad.
trigger_error to the rescue
PHP has a function called trigger_error
which can be used to raise an error just like the standard functions do. The error levels that you use with it are defined in the error level constants. As a user you must use one of the user errors: E_USER_ERROR
, E_USER_WARNING
or the default value E_USER_NOTICE
(other error levels are reserved for the standard functions etc.). Using a standard PHP function to raise the error allows the code to be re-used with any standard PHP installation! Our code is no longer responsible for logging the error (only making sure that it is raised).
Using trigger_error
we only perform half of the error logging process (raising the error) and save the responsibility of responding to the error for the error handler which will be covered next.
Error Handler
We set a custom error handler with the set_error_handler
function (see the code setup). This custom error handler replaces the standard PHP error handler that normally logs messages in the web server error log depending on the PHP configuration settings. We can still use this standard error handler by returning false
within our custom error handler.
The custom error handler has a single responsibility: to respond to the error (including any logging that you want to do). Within the custom error handler you have full access to the system and can run any sort of logging that you want. Virtually any logger that uses the Observer design pattern will be ok (I'm not going to go into that as I believe it is of secondary importance). This should allow you to hook in new log observers to send the output to where you need it.
You have complete control to do what you like with the errors in a single maintainable part of your code. The error logging can now be changed quickly and easily from project to project or within a single project from page to page. Interestingly even @
suppressed errors make it to the custom error handler with an errno
of 0 which if the error_reporting
mask is respected should not be reported.
When Good Errors go Bad - Fatal Errors
It is not possible to continue from certain errors. The following error levels can not be handled from a custom error handler: E_ERROR
, E_PARSE
, E_CORE_ERROR
, E_CORE_WARNING
, E_COMPILE_ERROR
, E_COMPILE_WARNING
. When these sorts of errors are triggered by a standard function call the custom error handler is skipped and the system shuts down. This can be generated by:
call_this_function_that_obviously_does_not_exist_or_was_misspelt();
This is a serious mistake! It is impossible to recover from, and the system is about to shut down. Our only choice is to have a register_shutdown_function
deal with the shutdown. However this function is executed whenever a script completes (successful, as well as unsuccessful). Using this and error_get_last
some basic information can be logged (the system is almost shutdown at this point) when the last error was a fatal error. It can also be useful to send the correct status code and show an Internal Server Error type page of your choosing.
Exceptions can be dealt with in a very similar way to basic errors. Instead of trigger_error
an exception will be thrown by your code (manually with throw new Exception
or from a standard function call). Use set_exception_handler
to define the callback you want to use to handle the exception with.
SPL
The Standard PHP Library (SPL) provides exceptions. They are my preferred way of raising exceptions because like trigger_error
they are a standard part of PHP which does not introduce extra dependencies to your code.
What to do with them?
When an exception is thrown there are three choices that can be made:
At each level of the stack these choices are made. Eventually once it bubbles up to the highest level the callback you set with set_exception_handler
will be executed. This is where your logging code belongs (for the same reasons as the error handling) rather than spread throughout catch
statements in your code.
Error Handler
function errorHandler($errno , $errstr, $errfile, $errline, $errcontext) { // Perform your error handling here, respecting error_reporting() and // $errno. This is where you can log the errors. The choice of logger // that you use is based on your preference. So long as it implements // the observer pattern you will be able to easily add logging for any // type of output you desire. } $previousErrorHandler = set_error_handler('errorHandler');
Exception Handler
function exceptionHandler($e) { // Perform your exception handling here. } $previousExceptionHandler = set_exception_handler('exceptionHandler');
Shutdown Function
function shutdownFunction() { $err = error_get_last(); if (!isset($err)) { return; } $handledErrorTypes = array( E_USER_ERROR => 'USER ERROR', E_ERROR => 'ERROR', E_PARSE => 'PARSE', E_CORE_ERROR => 'CORE_ERROR', E_CORE_WARNING => 'CORE_WARNING', E_COMPILE_ERROR => 'COMPILE_ERROR', E_COMPILE_WARNING => 'COMPILE_WARNING'); // If our last error wasn't fatal then this must be a normal shutdown. if (!isset($handledErrorTypes[$err['type']])) { return; } if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } // Perform simple logging here. } register_shutdown_function('shutdownFunction');
Errors
// Notices. trigger_error('Disk space is below 20%.', E_USER_NOTICE); trigger_error('Disk space is below 20%.'); // Defaults to E_USER_NOTICE // Warnings. fopen('BAD_ARGS'); // E_WARNING fopen() expects at least 2 parameters, 1 given trigger_error('Warning, this mode could be dangerous', E_USER_WARNING); // Fatal Errors. // This function has not been defined and so a fatal error is generated that // does not reach the custom error handler. this_function_has_not_been_defined(); // Execution does not reach this point. // The following will be received by the custom error handler but is fatal. trigger_error('Error in the code, cannot continue.', E_USER_ERROR); // Execution does not reach this point.
Exceptions
Each of the three choices from before are listed here in a generic way, fix it, append to it and let it bubble up.
1 Fixable:
try { $value = code_that_can_generate_exception(); } catch (Exception $e) { // We decide to emit a notice here (a warning could also be used). trigger_error('We had to use the default value instead of ' . 'code_that_can_generate_exception\'s', E_USER_NOTICE); // Fix the exception. $value = DEFAULT_VALUE; } // Code continues executing happily here.
2 Append:
Observe below how the code_that_can_generate_exception()
does not know about $context
. The catch block at this level has more information which it can append to the exception if it is useful by rethrowing it.
try { $context = 'foo'; $value = code_that_can_generate_exception(); } catch (Exception $e) { // Raise another exception, with extra information and the existing // exception set as the previous exception. throw new Exception('Context: ' . $context, 0, $e); }
3 Let it bubble up:
// Don't catch it.
It has been requested to make this answer more applicable to a larger audience, so here goes.
Preamble
Error handling is usually not the first thing you will want to think about when writing an application; as an indirect result it gets bolted on as the need arises. However, it doesn't have to cost much to leverage existing mechanisms in PHP either.
It's a fairly lengthy article, so I've broken it down into logical sets of text.
Triggering errors
Within PHP there are two distinct ways for errors to get triggered:
imagecreatefromjpeg
could not open a file),trigger_error
,These are usually printed on your page (unless display_errors
is switched off or error_reporting
is zero), which should be standard for production machines unless you write perfect code like me ... moving on); those errors can also be captured, giving you a glimpse into any hitch in the code, by using set_error_handler
explained later.
Throwing exceptions
Exceptions are different from errors in three main ways:
Exception
class; this allows you to catch and handle specific exceptions but let others bubble down the stack until they're caught by other code. See also: http://www.php.net/manual/en/language.exceptions.php An example of throwing exceptions is given later on.
Handling errors
Capturing and handling errors is pretty straightforward by registering an error handler, e.g.:
function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, array $errcontext = array()) { // $errcontext is very powerful, it gives you the variable state at the point of error; this can be a pretty big variable in certain cases, but it may be extremely valuable for debugging // if error_reporting() returns 0, it means the error control operator was used (@) printf("%s [%d] occurred in %s:%d\n%s\n", $errstr, $errno, $errfile, $errline, print_r($errcontext, true)); // if necessary, you can retrieve the stack trace that led up to the error by calling debug_backtrace() // if you return false here, the standard PHP error reporting is performed } set_error_handler('my_error_handler');
For kicks, you can turn all the errors into an ErrorException
as well by registering the following error handler (PHP >= 5.1):
function exception_error_handler($errno, $errstr, $errfile, $errline) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } set_error_handler("exception_error_handler");
Handling exceptions
In most cases you handle exceptions as close as possible to the code that caused it to allow for backup plans. For instance, you attempt to insert a database record and a primary key constraint exception is thrown; you can recover by updating the record instead (contrived as most databases can handle this by themselves). Some exceptions just can't be handled locally, so you want those to cascade down. Example:
function insertRecord($user, $name) { try { if (true) { throw new Exception('This exception should not be handled here'); } // this code is not executed $this->db->insert('users', array('uid' => $user, 'name' => $name)); } catch (PDOException $e) { // attempt to fix; an exception thrown here will cascade down throw $e; // rethrow exception // since PHP 5.3.0 you can also nest exceptions throw new Exception("Could not insert '$name'", -1, $e); } catch (WhatEverException $e) { // guess what, we can handle whatever too } }
The slippery exception
So what happens when you don't catch an exception anywhere? You can catch that too by using set_exception_handler
.
function my_exception_handler(Exception $exception) { // do your stuff here, just don't throw another exception here } set_exception_handler('my_exception_handler');
This is not encouraged unless you have no meaningful way to handle the exception anywhere in your code.
Logging the error / exception
Now that you're handling the error you have to log it somewhere. For my example, I use a project that Apache ported from Java to PHP, called LOG4PHP. There are others, but it illustrates the importance of a flexible logging facility.
It uses the following concepts:
Basic usage to illustrate different message levels:
Logger::getLogger('main')->info('We have lift off'); Logger::getLogger('main')->warn('Rocket is a bit hot'); Logger::getLogger('main')->error('Houston, we have a problem');
Using these concepts you can model a pretty powerful logging facility; for example, without changing above code, you can implement the following setup:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With