log4php example

This log4php example demonstrates how to the log4php library in a non-trivial way. log4php is a version of the Apache log4J package ported to PHP5. Please refer to the apache project for complete details.

log4php is a powerful logging mechanism that allows you to use a configuration file to specify what types of messages you want to route to various destinations (eg: file, email, database record). This makes it easy to change what messages are logged or where they are sent without tinkering around with code in the application.

In concept log4php has three core entities that work together:

(1) Loggers – loggers know what level of message to capture, they use one or more Appenders; loggers can be defined in a heirarchial manner, always stemming from the root logger. Loggers are named, and the periods in the name indicate the heirarchial levels (phpfunk, phpfunk.descendant, phpfunk.descendant.furtherDescenant, etc).

(2) Appenders – determine where the logged messages are sent and use Layouts to determine how the message should be formatted.

(3) Layouts specify the formating of the logged message.

In this example we want to capture all log messages to a file called default.log. We also want to capture serious errors to error.log and send an error message via email to the webmaster to let him know something is wrong.

First lets look at how simple the code is. I’m throwing an Exception just for fun. The configuration that I’ll explain below is the key to harnessing the power of log4php.

require_once 'log4php/Logger.php';
Logger::configure('logconfig.xml');

// get logger by name, if this name isn't listed 
// as a logger name in the configuration 
// file you will get the root level logger
$logger = Logger::getLogger('phpfunk');
$logger->info("beginning log4php example");
try {
    throw new Exception("log4php example error");
}
catch(Exception $e) {
    $logger->error($e->getMessage());
}

The log files now contain:
::::::::::::::
/tmp/default.log
::::::::::::::
2011-04-13 13:25:35 [INFO] phpfunk: beginning log4php example (at /home/tom/testlog.php line 8)
2011-04-13 13:25:35 [ERROR] phpfunk: log4php example error (at /home/tom/testlog.php line 12)
::::::::::::::
/tmp/error.log
::::::::::::::
2011-04-13 13:25:35 [ERROR] phpfunk: log4php example error (at /home/tom/testlog.php line 12)

And in addition an email was sent with the same message body as the message captured in error.log.

Now lets dig into the log4php configuration. I’m using an XML file for configuration because I want to use a Filter in the Appender. At the bottom of the configuration file I set the logging level and appender to use for the root logger and another logger named phpfunk, which descends from the root because the root logger is always the top most logger.

Appenders are referenced by name in the appender_ref element in the logger mark up. For phpfunk I have specified two appenders, a file and one to send messages via email. Both of those appenders further use filtering to control what level messages are captured in the logs; in this case ERROR and FATAL messages.

The root logger records DEBUG and higher messages. The Appender it uses writes these messages to default.log.

I want to use phpfunk logger, for this example, to capture all messages to default.log, but only capture ERROR and FATAL messages to error.log and also to email ERROR and FATAL messages to the webmaster. If I set the default logger level to ERROR for phpfunk then any INFO level messages it receives are discarded. By setting the level to ANY I ensure that INFO level messages get passed up to the default logger’s Appender and appear in default.log.

Here is the log4php XML configuration and you can download the example log4php configuration.

<?xml version="1.0"?>
<log4php:configuration xmlns:log4php="http://logging.apache.org/log4php/">
    <appender name="default" class="LoggerAppenderFile">
        <param name="file" value="/tmp/default.log" />
        <param name="datePattern" value="Ymd" />
        <layout class="LoggerLayoutPattern">
            <param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
        </layout>
    </appender>
    <appender name="errorLog" class="LoggerAppenderFile">
        <param name="file" value="/tmp/error.log" />
        <param name="datePattern" value="Ymd" />
        <layout class="LoggerLayoutPattern">
            <param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
        </layout>
        <filter class="LoggerFilterLevelRange">
            <param name="LevelMin" value="error" />
            <param name="LevelMax" value="fatal" />
        </filter>
        <filter class="LoggerFilterDenyAll" />
    </appender>
    <appender name="emailNotice" class="LoggerAppenderMail">
        <param name="from" value="webmaster@phpfunk.com" />
        <param name="to" value="tom@phpfunk.com" />
        <param name="subject" value="Error" />
        <layout class="LoggerLayoutPattern">
            <param name="ConversionPattern" value="%d{Y-m-d H:i:s} [%p] %c: %m (at %F line %L)%n" />
        </layout>
        <filter class="LoggerFilterLevelRange">
            <param name="LevelMin" value="error" />
            <param name="LevelMax" value="fatal" />
        </filter>
        <filter class="LoggerFilterDenyAll" />
    </appender>
    <root>
        <level value="DEBUG" />
        <appender_ref ref="default" />
    </root>
    <logger name="phpfunk" additivity="true">
        <!-- with additivity set to TRUE the default is also called, eg parent -->
        <level value="ALL" />
        <appender_ref ref="errorLog" />
        <appender_ref ref="emailNotice" />
    </logger>
</log4php:configuration>

So you see with a single configuration file its easy to switch around what level messages are captured, how they are handled (written to file, emailed) and how they are formatted. Hopefully you will find this log4php example to be a nice addition to the information presented in the log4php project documentation.