Logging data

Stubbles contains a unique logging API which enables logging on different levels with different targets while customisable with what is being logged. The logging API is included in net.stubbles.util.log. To include all basic logging stuff use stubClassLoader::load('net::stubbles::util::log::log'); (includes stubLogger, stubLogAppender, stubLogData and stubLogDataFactory).

How to create the log data

The data to log is collected in a stubLogData object. (See stubBaseLogData for a reference implementation.) The stubLogData knows for which log level it should be applied (getLevel(), the return value may be a combination of the log levels explained above). The method getTarget() determines where the log data belongs. The concrete interpretation of the target depends on the log appender which takes the log data. A file log appender might use this as the basename of a file, while a database log appender might use this as the name of the table to write the log data into. Therefore it is advisable to only use ascii characters, numbers and underscores to be sure that the log appender will not mess up the log data. The whole log data contents are returned via get(). The stubLogData instance should take care that fields are seperated by stubLogData::SEPERATOR, that the fields itself do not contain this seperator and as well no line break. It is recommended to remove windows line feeds and the seperator and to replace line breaks with <nl>.

In order to always use the same stubLogData implementation one should use the stubLogDataFactory to create a concrete instance. It uses the class name stored in the stubRegistry with the key net.stubbles.util.log.class. If no such value has been set it defaults to stubBaseLogData which has two default fields: the time when the log data was created and the id of the current session.

While the stubLogData interface does not contain a constructor every concrete implementation should have constructor with the following signature:

__construct($target, $level = stubLogger::LEVEL_INFO)

in order to be used in conjunction with the stubLogDataFactory.

Example to create an instance of stubLogData:

<?php
$logData = stubLogDataFactory::create($target, stubLogger::LEVEL_INFO);
$logData->addData('foo'));
?>

This will create an instance of stubLogData independently of the concrete configured log data class. Moreover the value foo is added to $logData. To send your data to the logger use

<?php
stubLogger::logToAll($logData);
?>

If you need access to more data within your stubLogData implementation you can use the Inversion of Control container supplied by Stubbles. The stubLogDataFactory uses the binder from the registry as described in IoC in a Stubbles MVC application to inject all necessary data into the stubLogData instance.

If you just want to use the log layer you are done. The following documentation gives an insight how the logging system works and how it can be expanded.

Configuring the log layer

The logger is configured via the config/xml/logging.xml file:

<?xml version="1.0" encoding="iso-8859-1"?>
<xj:configuration
    xmlns:xj="http://xjconf.net/XJConf"
    xmlns:cfg="http://stubbles.net/util/XJConf"
    xmlns="http://stubbles.net/util/log">
  <logger id="default" level="14">
    <logAppender type="net::stubbles::util::log::stubFileLogAppender">
      <cfg:stubConfig name="logDir" method="getLogPath" />
    </logAppender>
  </logger>
  <logger id="debug" level="1">
    <logAppender type="example::log::DebugLogAppender" />
  </logger>
 </xj:configuration>

As noted above each logger has its own id and its own level for which it is responsible. In every logger the log appenders to be used within this logger are noted. The example above will result in a logger which logs stubLogger::LEVEL_INFO, stubLogger::LEVEL_WARN and stubLogger::LEVEL_ERROR. For this it uses the stubFileLogAppender which in turn gets the log path as configuration option. A second logger will only log data of the stubLogger::LEVEL_DEBUG level and will use the DebugLogAppender for this.

Saving the data: log appenders

The stubLogger itself does not know how the log data is stored. Storing the log data is the task of the stubLogAppender. A log appender takes log data and writes it to the target. The target can be a file, a database or anything else suited for log data. As a concrete example Stubbles offers a stubFileLogAppender which writes the log data into a logfile on the hard disk. Other log appenders may be created by implementing the stubLogAppender interface. This interface consists of four methods:

append(stubLogData $logData);
finalize();
setConfig(array $config);
getConfig();

While append(stubLogData $logData) takes the log data the log appender can do with it whatever it wants. The finalize() method is called when the stubLogger instance where the log appender was added to is destroyed (either explicitly by calling stubLogger::destroyInstance($id) or implicitly at the end of the request). The remaining set- and getConfig()-methods are responsible for configuring the logger.

A log appender can be added to a concrete stubLogger instance via the addLogAppender() method:

<?php
$debugLogFileAppender = new stubFileLogAppender('/path/to/dir');
$debugLogger->addLogAppender(debugLogFileAppender);
?>

Entrance to logging: stubLogger

The stubLogger class is the interface to log data into different targets. The stubLogger itself does not know where to write the log data - it just uses log appenders which in turn do the real work. A stubLogger is a collection of such appenders. Even if it looks like a singleton it is possible to have different instances of a logger. Each logger uses a different id and can be applied on different logging levels. For instance, you may have one logger that is applicable for debug logging and another one that is used for the other error levels:

<?php
$debugLogger   = stubLogger::getInstance('debug', stubLogger::LEVEL_DEBUG);
$defaultLogger = stubLogger::getInstance(stubLogger::DEFAULT_ID, (stubLogger::LEVEL_ALL - stubLogger::LEVEL_DEBUG));
?>

Now there are two possibilities to log. The first one is using the concrete instance (it is assumed that $logData is an instance of stubLogData, for reference see below):

<?php
$debugLogger->log($logData);
?>

The logger will now tell all of its log appenders to log the given data. However this handling has the drawback that there is the need to have a concrete logger instance. Because of this the second possibility seems to be a better approach:

<?php
stubLogger::logToAll($logData);
?>

This makes logging more independent of the concrete logger configuration. The logToAll() method will check on which log levels the log data should be applied and then notify each concrete logger instance that adheres to this log level. If we assume that the log level of $logData was stubLogger::LEVEL_DEBUG than only the debug instance will receive the log data.

There are four different error levels:

<?php
stubLogger::LEVEL_DEBUG   // debugging purposes
stubLogger::LEVEL_INFO    // default logging
stubLogger::LEVEL_WARN    // warnings, but not dangerous for the application
stubLogger::LEVEL_ERROR   // errors that can not be handled by the application
?>

The purpose comments above are more a recommendation then a rule. As the log level is a bit switch it is possible to combine two or more of the log levels by adding them:

<?php
stubLogger::LEVEL_INFO + stubLogger::LEVEL_WARN
?>

For convenience the log level stubLogger::LEVEL_ALL incorporates all log levels and can be used as shortcut. However it should be noted that the integer value of this log level may increase if more log levels are added to the stubLogger which may be the case or not in future versions of Stubbles.