Server-side Debugging

So far, we've seen some basic techniques for debugging problems in client-side code (Javascript), and problems in the way that data is being passed back in forth between the client and the server. But what happens when the problem is in the server-side code, i.e., in the PHP?

display_errors

Many PHP developers are used to seeing server-side errors show up in their browsers automatically, because PHP's display_errors directive is set in their PHP configuration file by default to on. This is a bad idea, for a number of reasons:

It's a potential security risk

As we discussed in the last section, it's important to distinguish between the code that is running on the server, and the response that it sends back to the client.

In general, code can contain sensitive information that we do not want to share with the end user - for example, passwords and API keys. If something goes wrong with the database connection code, it will generate a stack trace that contains the database credentials. By dumping this trace into the response, your application is risking making this information public:

Mysql password dumped to browser in PHP

Therefore we want to keep a tight rein on what makes it into the response, and this means turning off display_errors.

You might say, "well wait a minute, that's only a problem when I'm in production! I can still use display_errors in development, right?"

The answer is yes, you can, but there are some other reasons that I strongly recommend against it.

It doesn't work for all types of requests

For traditional GET and POST requests, such as navigating to a page or submitting a form using your browser's default submission handling, the default behavior of most browsers is to directly display the response it receives in the main viewport. This is done automatically, so we don't even really think of it as a request-response transaction.

However with the rise of "Web 2.0" and more complex web applications, we've seen the widespread adoption of AJAX for submitting requests and working with response data, all without refreshing the page. Often times, the user doesn't even know that a request has been issued!

In these cases, any error messages we receive from PHP via the server's response will get held up inside the XHR object that Javascript uses to process the request. We'll never see these error messages unless we either:

  1. Explicitly tell our AJAX handler to dump them into the DOM, or;
  2. Manually inspect the contents of the response using the browser console tools.

This is hardly a convenient and predictable way to get at debugging and error messages!

It's just plain confusing

Dumping server-side error and debugging messages into the HTTP response further blurs the distinction between server-side and client-side concerns for new developers. It's no wonder that some novice PHP developers end up thinking that their PHP script is being run in their browser!

At the end of the day, the HTTP response should be a carefully crafted piece of content designed to be consumed in a specific way by the client - not a dump of every possible type of output that the server might generate.

I should point out that there is a difference between developer-facing messages, and user-facing messages. User-facing messages are messages which are intended for the end user - things like "Please choose at least one owl" or "Sorry, your owlId is incorrect!" These are generally explicitly generated by your application, and can and should be sent in an HTTP response.

Developer-facing messages, on the other hand, should only be seen by developers and system administrators. These are things like the MySQL credentials error and stack trace we saw earlier. These are the types of messages we want to avoid injecting into our HTTP response.

So then, how do we deliver debugging and error messages to our tech team?

Logging

Fortunately, we have means for shunting messages to a log file.

To begin with, PHP natively supports error logging by setting the log_errors directive to on, and specifying a path to an error log file (what we will call the php error log) with the error_log directive. Enabling log_errors and disabling display_errors will cause PHP to dump error messages to the log instead of the response.

To manually invoke the error logger, we can use the error_log function. This will let us write to the php error log:

// Print a simple string to the log
error_log("Fetching owls from database...");

// Print an array or object to the log
error_log(print_r($owls, true));

We can use this as a simple alternative to using echo to print debugging information directly to the response.

Debug facade

It'd be nice if we could separate our reports of actual, unexpected errors from routine debugging statements, by sending them to separate log files. UserFrosting supports this as well, and creates the following log files in the app/logs/ directory:

  • debug.log
  • errors.log
  • mail.log

debug.log is meant for dumping routine debugging statements. To log something to the debugging log, simply use the Debug facade:

use UserFrosting\Sprinkle\Core\Facades\Debug;

...

Debug::debug("Fetching owls from database...");

Debug::debug("Owls found:", $owls);

Debug is a facade for a Monolog logger instance, whose debug method takes a string as the first parameter and an optional array as a second parameter, and writes them to a log file. Monolog also supports more advanced logging capabilities - check their documentation for more details.

errors.log is where most run-time error messages generated by uncaught exceptions are sent. Slim, the microframework upon which UserFrosting is built, converts all PHP errors into exceptions. Thus, the majority of application error messages will be found in this log. You can read more about how the error-handling system as a whole works in Chapter 10.

mail.log is a special logger for mail-related activity. The underlying phpMailer instance that we use reports its SMTP activity to this log. The level of detail can be specified with the mail.smtp_debug configuration value, using the values specified in the PHPMailer documentation:

  • 0 No output
  • 1 Commands
  • 2 Data and commands
  • 3 As 2 plus connection status
  • 4 Low-level data output