Implementing PSR-3 with log4php

Jamie Munro
Share

With the recent passage of PSR-3, a standard for a common interface for logging libraries, it seems appropriate to discuss the implementation with my favorite logging library. log4php is an open source Apache project that is a very versatile logging framework.

Through log4php’s configuration files, logging has a variety of output locations, so it’s possible to send specific log levels to different output locations. For example: debug, info, and notice are used mainly for general logging and debugging, and so it makes sense to direct these to a standard log file, whereas critical and above typically mean something has gone horribly wrong with our application and it makes more sense to send these to ourselves via email for immediate diagnosis.

Getting Dependencies

Before we can start writing code, we need to our dependencies. The PSR-3 interface class is required; it’s currently found on GitHub and can be installed via composer from Packagist. Also, the log4php framework is required, which can be downloaded from Apache, with PEAR, or composer.

Here’s a sample composer.json file that sets everything up:

{
    "require": {
        "psr/log": "dev-master",
        "apache/log4php": "2.3.0"
    }
}

Wrapping log4php

With the necessary third-party libraries installed, we can create a generic logging class which implements the PSR-3 interface and uses log4php.

Eight different log levels are exposed by the PSR-3 interface: debug, info, notice, warning, error, critical, alert, and emergency. log4php contains six logging levels: trace, debug, info, warn, error, and fatal. We have to map the PSR-3 levels to appropriate log4php levels. The below class implements interface and manages a log4php logger instance to map the levels.

<?php
require_once 'vendor/autoload.php';

class MyLogger implements PsrLogLoggerInterface
{
    private $logger;

    public function __construct($logger = 'main', $config = null) {
        Logger::configure($config);
        $this->logger = Logger::getLogger($logger);
    }

    /**
     * System is unusable.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function emergency($message, array $context = array()) {
        $this->logger->fatal($this->interpolate($message, $context));
    }

    /**
     * Action must be taken immediately.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function alert($message, array $context = array()) {
       $this->logger->fatal($this->interpolate($message, $context));
    }

    /**
     * Critical conditions.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function critical($message, array $context = array()) {
        $this->logger->fatal($this->interpolate($message, $context));
    }

    /**
     * Runtime errors that do not require immediate action but should
     * be logged and monitored.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function error($message, array $context = array()) {
        $this->logger->error($this->interpolate($message, $context));
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function warning($message, array $context = array()) {
        $this->logger->warn($this->interpolate($message, $context));
    }
    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function notice($message, array $context = array()) {
        $this->logger->info($this->interpolate($message, $context));
    }

    /**
     * Interesting events.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function info($message, array $context = array()) {
        $this->logger->info($this->interpolate($message, $context));
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function debug($message, array $context = array()) {
        $this->logger->debug($this->interpolate($message, $context));
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     * @return null
     */
    public function log($level, $message, array $context = array()) {
        throw new Exception('Please call specific logging message');
    }

    /**
     * Interpolates context values into the message placeholders.
     * Taken from PSR-3's example implementation.
     */
    protected function interpolate($message, array $context = array()) {
        // build a replacement array with braces around the context
        // keys
        $replace = array();
        foreach ($context as $key => $val) {
            $replace['{' . $key . '}'] = $val;
        }

        // interpolate replacement values into the message and return
        return strtr($message, $replace);
    }
}

Apart from implementing the PSR-3 interface, this class contains a constructor that accepts 2 optional parameters named $logger and $config. The first parameter allows you to specify a name for the logger and log4php will maintain a reference to it allowing you to maintain multiple logger instances at the same time with different names. The second parameter allows you to specify a configuration file. If none is specified, log4php will use its default configuration.

The newly created MyLogger class can now be tested with the following example:

<?php
$logger = new MyLogger();
$logger->debug('My debug test');

The default log4php setting for debug will output the message to standard out, so the results of this example will be sent to the web browser.

log4php Configuration

log4php ships with a default configuration so we get started using it right away, but it also provides the ability to override the configuration using XML or PHP code. The most popular supported configuration is XML.

Let’s create a config.xml file with a new appender that will tell log4php to log all messages with warn and above to a file:

<configuration xmlns="http://logging.apache.org/log4php/">
 <appender name="myAppender" class="LoggerAppenderFile">
  <param name="file" value="myLog.log"/>
 </appender>
 <root>
  <level value="WARN"/>
   <appender_ref ref="myAppender"/>
  </root>
</configuration>

We now need to pass the name of the new configuration file to MyLogger when we invoke its constructor.

<?php
$logger = new MyLogger('main', 'config.xml');
$logger->debug('My debug test');

If you re-run the example, when the debug message is called, log4php will ignore it because debug is below warn and our configuration only logs warn and above.

Conclusion

By leveraging the PSR-3 logging interface and log4php, standards-compliant logging is now extremely simple to add to any of our projects. To learn more about log4php, visit the Quick Start guide. For more about PSR-3, read the standard on GitHub.

Image via Fotolia