Taking Advantage of Built-in Logging In PHP, Ruby and Python
In part one of this series, we discussed how logging is the foundation for solving operational problems with software. We established that there always needs to be sufficient information on hand to deal with these problems when they occur. But what should we log? Do we flood log servers with everything and anything? Do we keep logging to a bare minimum? Or do we aim somewhere in the middle? But theory is one thing; at some stage, we need to look at logging in practice.
Here in part two of the series, we’ll look at some of the pros and cons of using the built-in logging options available in three of the most popular development languages: PHP, Python, and Ruby, along with accompanying code examples.
Logging with PHP
I’ll start with PHP logging, as it’s my preferred language. Out of the box, PHP doesn’t have the same level of error logging functionality available as either Python or Ruby. Comprised of two core functions, error_log and trigger_error, PHP is able to log errors which occur, based on a range of severity levels, which you can find in the PHP manual.
These can be fatal errors, such as memory allocation issues, script parsing issues, compile time warnings, user generated warnings and more. Based on the PHP runtime configuration, error logs can be written to a file on the filesystem or sent to a Syslog server, except if the operating system is Windows.
So let’s look at a code sample. Below is a simple function, logData, which will open a connection to a Syslog daemon, write a message with an emergency priority level, then close the connection afterwards.
The logged information will look like the following:
You can see from this example, it’s easy to take textual information and log it as and when needed; from a simple string to a more complex JSON or serialized object.
However, how do you differentiate between environments, such as development, testing, staging and production? How do you set a minimum log priority level? How do you log to multiple places at once, such as email servers, filesystems, and SNMP traps? Using just error_log or syslog, PHP is not able to do this.
Logging with Ruby
When I said that PHP, out the box, doesn’t have the same level of error logging functionality available, I wasn’t meaning to be disingenuous. PHP is quite rich in functionality. But both Ruby and Python offer greater flexibility natively.
Using the built-in Logger class, Ruby can log messages to a variety of locations, including STDERR, STDOUT, or a file on the filesystem. It can also specify a priority level, including debug, info, warn, error, fatal, and unknown.
These capabilities provide the ability to log for a wide variety of purposes, from the simple to the critical. They make it easier to differentiate amongst the information logged, speeding up search. But what’s especially nice about Ruby’s built-in logging library, is that messages can also be formatted. By default, messages are written with the format below:
This will result in a message like the following:
Taking an example from the Ruby docs (v 2.1.0), we could change the format by setting the formatter as follows:
Given this functionality, based on what is being logged, the application is not forced into a one-size fits all approach. With it, you can write in a format which best suits the needs of the given event and application.
With the code above, we’ve defined a new Logger which will write to the console, along with a format of the timestamp when the message was written, followed by the message. If the message is written with at least a priority of warn, then it will be written out, otherwise it will be ignored.
This will result in a message, such as the one below, being written to stderr:
Logging with Python
Now, let’s take a look at Python. Of all three languages, Python is the most natively feature-rich. Possessing the core logging functionality of the other two, and the formatting ability of Ruby, Python additionally combines 11 handlers, which determine where the information logged will be sent.
Taken from the Python documentation, these are:
|StreamHandler||Sends logging output to streams such as sys.stdout, sys.stderr or any file-like object (or, more precisely, any object which supports write() and flush() methods).|
|FileHandler||Sends logging output to a disk file|
|RotatingFileHandler||Supports rotation of disk log files|
|TimedRotatingFileHandler||Supports rotation of disk log files at certain timed intervals|
|SocketHandler||Sends logging output to a network socket. The base class uses a TCP socket.|
|DatagramHandler||Supports sending logging messages over UDP sockets.|
|SysLogHandler||Supports sending logging messages to a remote or local Unix syslog.|
|NTEventLogHandler||Supports sending logging messages to a local Windows NT, Windows 2000 or Windows XP event log|
|SMTPHandle||Supports sending logging messages to an email address via SMTP.|
|MemoryHandler||Supports buffering of logging records in memory, periodically flushing them to a target handler. Flushing occurs whenever the buffer is full, or when an event of a certain severity or greater is seen.|
|HTTPHandler||Supports sending logging messages to a Web server, using either GET or POST semantics.|
In addition to the handlers Python also provides filters, which afford a greater level of control over which log records are sent to a given handler. If you’re interested in an in-depth discussion, check out Jeremy Jones’ post. These two features provide a very rich level of control. For example, in production you may only write logs of level warning or above to a syslog server. But in development, you may want to display information to stdout starting at debug level, providing extra information to developers. Alternatively, in production, you may have a greater need for data retention, so that records can be searched over a historical basis; which isn’t required in development or staging. Let’s now revisit the previous example using Python. In this, we’ll see something which the others can do with external libraries, but not natively. That is, add multiple handlers to a logger. Say for example, you’re writing a web-based application. Depending on the environment, you’ll have different logging needs, and a one size fits all can be quite limiting.
In the above example, courtesy of the Python Cookbook, a logger has been set up with the minimum log level recorded to debug level. Then, two log handlers are added; the first one writes to a log file and the second to a stream, which is STDERR. However, the stream handler will only output errors at a priority of error or above.
Next a formatter is applied, which will print out the time, the logger’s defined name, the priority level and the message. Now, with just one log call, information can be sent to multiple locations transparently, simplifying code required, directly impacting on application maintainability.
And that’s an overview of the built-in logging features of PHP, Ruby, and Python. To be fair, I’ve not been able to be to cover each language in-depth. But I hope you see the comparative, strengths of each one’s out-of-the-box capabilities.
In normal development, you’re not going to limit yourself to just the built-in options, but use the full complement of options at your disposal. But as you can see, there’s a bit of difference between the native capabilities of each one.
In the next part in the series, we’ll be looking at the logging libraries available in each of the three languages. For PHP-specifically, you’ll see how it is able to match the sophistication of Python.
But till then, what’s your approach to logging across environments?
Keep Reading This Series