Implementing PSR-3 with log4php

Jamie Munro
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

Frequently Asked Questions about Implementing PSR-3 with Log4PHP

What is the significance of PSR-3 in Log4PHP?

PSR-3 is a common interface for logging libraries. It is significant in Log4PHP as it provides a standard that allows developers to decouple their code from specific logging implementations. This means that you can switch between different logging libraries that implement the PSR-3 interface without having to change your application code. It also promotes code reusability and makes it easier to maintain and debug your code.

How do I start logging with Log4PHP?

To start logging with Log4PHP, you first need to configure it. This involves defining the appenders, layout, and loggers in an XML, PHP, or properties file. Once configured, you can create a logger instance and use it to log messages at different levels such as debug, info, warn, error, and fatal.

How do I download and install Log4PHP?

Log4PHP can be downloaded from the Apache Logging Services Project website. After downloading, you can install it by extracting the archive and including the ‘Logger.php’ file in your script. You can also install it using Composer, a dependency management tool for PHP, by running the command ‘composer require apache/log4php’.

How do I configure Log4PHP?

Log4PHP is configured using a configuration file in XML, PHP, or properties format. This file defines the appenders, layout, and loggers. Appenders are responsible for delivering log events to their destination. Layouts specify the format of log messages. Loggers are named entities that are used to log messages.

Can I use multiple appenders with Log4PHP?

Yes, you can use multiple appenders with Log4PHP. Each logger can be assigned one or more appenders. This allows you to log messages to multiple destinations such as a file, database, email, or console.

How do I log messages at different levels with Log4PHP?

Log4PHP supports multiple logging levels including trace, debug, info, warn, error, and fatal. You can log messages at these levels using the corresponding methods of the logger instance. For example, to log a debug message, you would use the ‘debug’ method.

How do I handle exceptions with Log4PHP?

Log4PHP provides the ‘LoggerThrowablePatternConverter’ class for logging exceptions. You can use this class to log the message, code, file, line, and trace of an exception.

Can I use Log4PHP with a framework?

Yes, you can use Log4PHP with a framework. It can be integrated with any PHP framework that supports PSR-3 logging, such as Laravel, Symfony, and Zend Framework.

How do I filter log messages with Log4PHP?

Log4PHP provides several filters that you can use to control which log messages are output by an appender. These include level range filter, string match filter, and logger match filter.

How do I format log messages with Log4PHP?

Log4PHP allows you to format log messages using layouts. A layout specifies the format of a log message and can include information such as the timestamp, logger name, log level, and message. You can use one of the predefined layouts or create your own.