Getting Started Quickly With PHP Logging

The previous articles in this series covered the basics of logging in C#, Java, Python, Ruby, Node.js, and JavaScript. In this post, I’ll show you how to use logging techniques in yet another very popular language: PHP.

I’ll open with a quick example of manual logging in PHP. Then we’ll revisit the details of why logging matters and what your logs should show. And lastly, I’ll show you how to set up and use the most popular PHP logging framework.

Let’s get started, then!

php_logging_scalyr

A (Super Simple) Example of PHP Logging

For the purpose of this tutorial, I’ll be using Linux with a clean installation of LAMP stack. You might need to adjust the tools and the commands if you’re using Mac or Windows instead of Linux.

Navigate to the /var/www/html folder and delete a file named index.html from there:

$ cd /var/www/html
$ rm index.html

Then create a new file index.php and open it in your favorite text editor (I’m using Emacs):

$ emacs index.php

Add the following HTML code into index.php:

<form method="post" style="margin-top: 100px; margin-left: auto; margin-right: auto; text-align: center;">
    <div>
	<label for="name">Name:</label>
	<input type="text" id="name" name="name">
    </div>
    <div>
	<label for="email">E-mail:</label>
	<input type="email" id="email" name="email">
    </div>
    <div>
	<button type="submit" name="submit">Subscribe</button>
    </div>
</form>

This HTML code will create a simple subscription form. To see the resulting page, open your browser and go to http://localhost:

Even though you can see the form in the browser, it doesn’t do anything useful right now. To make it functional, add the following PHP script at the top of the index.php file, before the HTML code:

<?php
if(isset($_POST['submit']))
{
    $name = $_POST['name'];
    $email = $_POST['email'];

    subscribe($name, $email);
}

function subscribe($name, $email) {
    // subscription logic here
}
?>

The above code will extract name and email parameters from the submitted request and pass them into the subscribe function, which will have the code that takes care of subscribing new users. But since this tutorial is about logging, I’ll leave the subscribe function’s body empty for now.

Now, let’s say you want to keep a log file that keeps track of all subscription requests alongside their respective parameters. You can do that by adding just two lines right before a call to subscribe function:

<?php
if(isset($_POST['submit']))
{
    $name = $_POST['name'];
    $email = $_POST['email'];

    $logMsg = "Subscription; user: " . $name . " email: " . $email . PHP_EOL;
    file_put_contents('server.log', $logMsg, FILE_APPEND | LOCK_EX);

    subscribe($name, $email);
}

function subscribe($name, $email) {
    // subscription logic here
}
?>

The additional two lines of code will call to PHP’s file_put_contents function that will append a new log message to the contents of server.log file.

Now if you open http://localhost in your browser, populate the input fields, and click on subscribe button, this script will create a new file named server.log in the /var/www/html directory and write the log entry into it. I can read the contents of this file using cat command:

$ cat server.log
Subscription; user: testName email: test@test.com

Congratulations: you’ve logged your first entry written in PHP!

Now, let’s back up a bit.

What Is Application Logging?

You probably already have an intuitive understanding of what logging is, but it’s still worth taking the time to go over a proper definition we can build upon. In the first article in this series, we defined application logging like this:

Application logging involves recording information about your application’s runtime behavior to a more persistent medium.

There are two important points to note here.

First, logging is all about the application’s runtime behavior. In other words, it involves recording the application’s events as a function of time.

Second, you always want to store logs in a more persistent medium. The exact meaning of “more persistent” will vary depending on your requirements and environment, but at the very least, you’ll want to be able to read the logs even if the application—or the entire server—crashes.

While this definition explains what logging is, it doesn’t explain why you’d want to use logging in your application. That’s an important question, and I’d like to address it in detail.

Why Log?

If we lived in an ideal world, our applications would just work as we planned. However, the reality is that even well-constructed applications have bugs and will be at risk of crashing at one point or another.

When you develop on your personal computer and encounter errors, they are usually relatively simple to resolve. After all, you know what you’ve been working on and what sequence of events led to the error. In addition, you can develop in debug mode, which gives you lots of additional information about the system.

But at some point, you need to release the application to your users. They will use it in original ways and in environments you couldn’t have imagined, and there will be many of them, potentially using the application concurrently. Plus your application will need to function for much longer periods of time. Debugging errors in this situation is impossible without additional information.

Enter logging.

Having a record of your application’s past runtime behavior allows you to investigate all kinds of undesired behavior post-factum. In other words, even if your application crashes and loses all its runtime state, you’ll still be able to understand what led to that crash using application’s logs.

But the usefulness of logs doesn’t stop there. Logs become priceless when users report bugs: they can audit your application, including regulatory audits required in specific business domains; they can be processed and analyzed in real time to warn you about potential performance issues; and more.

In short: application logging is a must for production applications because it gives you visibility into your application’s runtime history.

What Should You Log?

The simple logging approach described above fulfills the persistence requirement because it stores the logs in a file. However, it’s not great for recording runtime behavior because it doesn’t tell you when exactly each specific event happened.

This leads to a more general question: what information should be logged?

It’s a widespread practice to include at least the following information in each log entry:

  • Timestamp that states when exactly the event described by the log entry happened. To make your life easier, use properly formatted ISO 8601 date/time in UTC.
  • Event context. For example, it would be a bad idea to log “name: testName; email: test@test.com” because a month from now, you’ll probably forget what this means. Here’s some context to make the log entry more descriptive: “Subscription; user: testName email: test@test.com.”
  • Log severity level, such as “error,” “warning,” “info,” etc. This information provides additional context and allows for easy filtering of logs by severity.

I suggest that you treat the above list as the absolute baseline for your logging strategy and don’t log less information than that. Otherwise, you’ll have a really hard time reading your logs and making sense of them.

Keep in mind that these are very basic guidelines. They will get you started, but there is much more to be said about logging. I recommend you read this article about logging best practices and also consider reading OWASP’s Logging Cheat Sheet at some point to get a wider perspective on this important topic.

Make Logging Easier with the Monolog Framework

In the example at the beginning of this post, I showed you how to do basic logging in PHP. However, my solution didn’t log timestamps and severity levels, which as you now know are two mandatory pieces of information that must be included in each log entry.

Sure, I can add more code and enhance my solution to accommodate more advanced needs, but that would be a waste. See, I do logging only because I need to—it’s not really part of my application’s functionality. Therefore, each minute I spend on implementing logging solutions is one less minute I have to work on the core functionality of my application.

Luckily, my logging needs are very similar to the needs of millions of other developers, so this problem has already been solved in form of so-called logging frameworks. These are third-party libraries that I can import and use in my project without having to concern myself with exactly how they work. In other words, there’s no need for me to reinvent the wheel when it comes to logging.

The most popular logging framework for PHP is Monolog. In the next sections, I’ll show you how you can use it in your projects.

1. Installing Monolog PHP Logging Framework

If you’re developing your application using a PHP framework (e.g., Laravel), chances are this framework already integrates with Monolog. Check your framework’s documentation for details.

Since I’m working with a very simple PHP project, I can show you how to install and use Monolog from scratch.

First, you’ll need to install the PHP Composer package manager. The following commands will achieve that:

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ php composer-setup.php --install-dir=/usr/local/bin --filename=composer
$ php -r "unlink('composer-setup.php');"

Once you’ve got Composer on your machine, go to the root directory of your project at /var/www/html/ and execute the following command:

$ composer require monolog/monolog

This command will create vendor/directory and import Monolog files there. The next step will be to make use of Monolog from within the PHP code.

2. Using Monolog

To use Monolog in your code, you’ll need to load it as a dependency. For this purpose, Composer conveniently generated an autoload.php file that you can access from your code.

Put the following code at the top of your PHP script in an index.php file:

require __DIR__ . '/vendor/autoload.php';

And right after that, specify two specific Monolog dependencies that you’re going to use:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

Logger is the object that generates logs, and StreamHandler is a special object that Logger uses internally to write logs to files. As you might guess, Monolog supports different handlers that allow you to route the logs into different mediums.

So far, you’ve loaded Monolog and imported specific dependencies. Now instantiate a new Logger and name it SimpleLogger:

$logger = new Logger('SimpleLogger');

Next, use StreamHandler to make sure the logger stores the logs into your existing server.log file:

$logger->pushHandler(new StreamHandler(__DIR__.'/server.log', Logger::DEBUG));

The second parameter in StreamHandler’s constructor defines the lowest severity level it picks up. Since you want it to capture all log entries in this case, specify the lowest severity level, which is DEBUG.

After setting up Logger object, you can replace your old logging implementation with Monolog’s Logger. So, delete this line:

 file_put_contents('server.log', $logMsg, FILE_APPEND | LOCK_EX);

and replace it with the following call:

$logger->info($logMsg);

As you can see, even though you had to do a bit of up-front setup, this process made logging much more concise and simple. Given that in real applications, you might want to log hundreds of events, this conciseness will become very handy.

All in all, your index.php file should look like this:

<?php

require __DIR__ . '/vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('SimpleLogger');
$logger->pushHandler(new StreamHandler(__DIR__.'/server.log', Logger::DEBUG));

if(isset($_POST['submit']))
{
    $name = $_POST['name'];
    $email = $_POST['email'];

    $logMsg = "Subscription; user: " . $name . " email: " . $email . PHP_EOL;
    $logger->info($logMsg);
    
    subscribe($name, $email);
}

function subscribe($name, $email) {
    // subscription logic here
}
?>

<form method="post" style="margin-top: 100px; margin-left: auto; margin-right: auto; text-align: center;">
    <div>
	<label for="name">Name:</label>
	<input type="text" id="name" name="name">
    </div>
    <div>
	<label for="email">E-mail:</label>
	<input type="email" id="email" name="email">
    </div>
    <div>
	<button type="submit" name="submit">Subscribe</button>
    </div>
</form>

3. Reading Monolog Logs

If you go to http://localhost in your browser again, specify the same user details, and click on subscribe, a new log entry will be added to server.log file. Let’s contrast the original log entry from the first example with the one generated by Monolog.

Here’s the original log entry:

Subscription; user: testName email: test@test.com

And here’s what Monolog produced:

[2018-09-11 16:17:01] SimpleLogger.INFO: Subscription; user: testName email: test@test.com  [] []

As you see, Monolog automatically added the following information to the log entry:

  • Timestamp
  • Logger name
  • Log severity level

And the best part is that you didn’t need to write all this stuff yourself.

The only issue with this log entry is that the timestamp is not UTC. There are at least two ways to fix that:

  1. Specify UTC timezone when instantiating Logger.
  2. Specify UTC timezone for the entire PHP script.

In most cases, you’ll want your entire application to use UTC time, so I suggest adding the following method call at the top of your PHP script:

date_default_timezone_set('UTC');

All date and time function calls following this line will use UTC timezone by default—very useful.

What Now?

I’ve explained to you the basics of logging in PHP, which should be enough to get you started. Still, you might want to search for more advanced information to solidify your understanding. Simply follow the links in this post, and you’ll be alright.

If you’re going to use Monolog (as you probably should), invest some time into reading its documentation. This logging framework is extremely powerful and configurable, which allows you to support basically any logging requirements.

As your applications grow in size and complexity, and as your log files become more complex, you might find it challenging to make sense of them even if you follow all best practices. That’s where log aggregation tools, like the one Scalyr makes, can be lifesavers. These tools help you aggregate, process, search, and visualize your logs, making it easier to find what you need in the vast ocean of log entries.

So go ahead and use application logging in your PHP projects! And remember: you should probably err on the side of logging more than you currently think you need.

This post was written by Vasiliy Zukanov. Vasily worked in the semiconductor industry and then became a software engineer specializing in Android. After several years of 9-5 jobs, he decided to go into Android dev freelancing. Currently, he consults and creates Android video courses for developers.

Leave a Reply

Your email address will not be published. Required fields are marked *