1. Introduction

The class Logger is a tool to record application information easily and within a central store. It supports multiple log files and threshold configuration. Thresholds are useful to filter entries that are not meant for production environments.

To not influence the performance of an web application in a negative way the logger must be used in singleton flavour. The component ensures that every log entry is flushed into a logfile at the end of a request.

2. Architecture

The Logger implementation of the APF provides a separation between the Logger itself, the model of log entries (LogEntry), and the persistence (LogWriter):

Architecture of the APF Logger since version 1.17

The Logger is the central touch-point for the framework and applications built on the APF to store log entries. The appearance of a log entry is described by the LogEntry interface and the reference implementation SimpleLogEntry. The question where the log entry is written is answered by the LogWriter interface implementation. The Logger uses the registered writers to persist the internal buffer at the end of the request processing.

To register LogWriter implementations, the Logger provides the following methods:

  • addLogWriter($target, LogWriter $writer)
  • removeLogWriter($target)
  • getLogWriter($target)

Each LogWriter is registered with a unique target identifier ($target). The Logger uses this target to assign incoming log entries to the appropriate persistence layer. To support this mapping, each LogEntry is assigned to one dedicated log target, too.

The interface of a LogEntry is as follows:

PHP code
interface LogEntry { const SEVERITY_TRACE = 'TRACE'; const SEVERITY_DEBUG = 'DEBUG'; const SEVERITY_INFO = 'INFO'; const SEVERITY_WARNING = 'WARN'; const SEVERITY_ERROR = 'ERROR'; const SEVERITY_FATAL = 'FATAL'; public function __toString(); public function getLogTarget(); public function getSeverity(); }

getSeverity() is used by the Logger to check, whether the entry's severity is above the defined threshold or should be discarded. The return value of the getLogTarget() method is used by the Logger to assign the entry to the appropriate LogWriter.

The implementation of a LogWriter must comply with the following interface definition:

PHP code
interface LogWriter { public function writeLogEntries(array $entries); public function setTarget($target); }

Using the setTarget() method the Logger injects the target identifier. This key may be used to generate the log file name. writeLogEntries() is delegated the persistence of the applied list of log entries.

Besides the configuration of the persistence layers you can also define different formats of the log entries using different LogEntry implementations.

3. Configuration

3.1. LogWriter management

The Logger understands itself as a router for log entries as described in chapter 2 that delegates the persistence of the queued entries to registered LogWriters. For that reason, a LogWriter is necessary to persist the desired log entries to a file or a database.

Managing LogWriters with the Logger is described within the next chapters.

In case no log target has been specified the Logger quits writing the log entries with an exception. Example:
Fatal error: Uncaught exception 'LoggerException' with message 'Log writer with name "mysqlx" is not registered!' in */core/logging/Logger.php on line 433
The Logger does not include fallback mechanisms for security reasons. Fallback mechanisms - e.g. writing entries to stdout like the StdOutLogWriter does - may make sensible data available to the outside.
3.1.1. Adding a LogWriter

By default, the FileLogWriter is registered with the key specified within the Registry key InternalLogTarget (namespace: APF\core) that is apf at delivery.

In order to add a custom LogWriter you may use the addLogWriter() function. This procedure can be used for shipped and custom implementations:

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; $logger = Singleton::getInstance(Logger::class); use APF\core\logging\writer\StdOutLogWriter; $logger->addLogWriter( 'stdout', new StdOutLogWriter() );

Afterwards, the LogWriter registered with stdout can be used as follows:

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; use APF\core\logging\SimpleLogEntry; $logger = Singleton::getInstance(Logger::class); $logger->addEntry(new SimpleLogEntry( 'stdout', 'This is a log message', LogEntry::SEVERITY_INFO ));
3.1.2. Configuration of existing LogWriter

In case you intend to configure a registered LogWriter you may use getLogWriter(). Please use getRegisteredTargets() to query a list of registered log writers.

To change the configuration of the standard LogWriter please use the following code:

PHP code
$standardWriter = $logger->getLogWriter( Registry::retrieve('APF\core', 'InternalLogTarget') ); $standardWriter->setHostPrefix($_SERVER['HTTP_HOST']);

Details on the configuration of LogWriters can be taken from chapter 4.

3.1.3. Additional configuration with existing LogWriter

Using the getLogWriter() method you can use previously registered LogWriter as a basis for your custom targets. Please use the following code as a template to register the standard LogWriter under another target identifier:

PHP code
$writer = clone $logger->getLogWriter( Registry::retrieve('APF\core', 'InternalLogTarget') ); $logger->addLogWriter('new-target', $writer);

As described within the previous chapters the clone'd instance of the LogWriter can be configured as desired.

3.1.4. Deleting LogWriter

In order to remove registered LogWriters please use removeLogWriter():

PHP code

Please note, that existing log entries cannot be persisted after removing the appropriate log writer.

3.1.5. Configuration for development environments

In development environments it may be helpful to include all log entries to the HTML source code of your application. To redirect all existing log entries to the standard output channel of the web server, you may use the following source code:

PHP code
foreach($logger->getRegisteredTargets() as $logTarget) { $logger->addLogWriter($logTarget, new StdOutLogWriter()); }

3.3. Buffer length

As noted above, the Logger collects all entries to write them to the log file at the end of the request. Facing high log traffic, the memory consumption of your application may collide with PHP's system configuration (e.g. memory_limit).

To reduce memory consumption the maximum buffer length can be defined as desired. By default, 300 entries are collected until the buffer is flushed. To adapt this value, call

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; $logger = Singleton::getInstance(Logger::class); $logger->setMaxBufferLength(500);

3.4. Threshold

In case the severity of the log entry is above the threshold - or matches the profile - it is added to the log file. Otherwise it is skipped.

To define the threshold, please use the following lines:

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; $logger = Singleton::getInstance(Logger::class); $logger->setLogThreshold(Logger::$LOGGER_THRESHOLD_WARN);

As you may take from the example, the Logger already defines standard thresholds. Furthermore, you can create custom profiles using the severity definitions of the LogEntry interface. These are:


The following code block describes how to define a custom threshold - or create a custom profile:

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; $logger = Singleton::getInstance(Logger::class); $logger->setLogThreshold(array( LogEntry::SEVERITY_TRACE, LogEntry::SEVERITY_INFO ));
Please note, that defining thresholds with the setLogThreshold() method are not restricted to any hierarchy. This means, that you can define which log entries are written to the log file or not freely.

The Logger defines the following default thresholds:


Details on the definition can be taken from API documentation.

3.5. Overriding the threshold

Please note that this functionality is available starting with version 3.1!

In production environments thresholds are usually set to a high value according to the configuration described in chapter 3.4. Often, only FATAL, ERROR and WARN entries are allowed.

This is both valid and reasonable for security and performance reasons. However, facing errors that are hard to reproduce information necessary for analysis gets lost (e.g. INFO or DEBUG entries).

In order to be prepared for this use case the Logger provides an option to override the threshold definition in certain cases dynamically.

This option of the Logger is deactivated by default or not configured respectively.
3.5.1. Functionality

The threshold definition described in chapter 3.4 is based on a fixed list of allowed severities that are included when processing log entries. Entries that don't match the definition are discarded.

To override or deactivate the threshold definition in relevant error scenarios the Logger can be configured with a list of suitable severities with assigned thresholds.

In case one of the defined thresholds is reached for the current request the Logger processes alle entries recorded during the request. This especially includes all entries that would have been lost using the normal threshold definition.

DEBUG or TRACE entries typically contain sensitive information about your application or customers. Using this option, please ensure your log information is properly protected against third-person access!
3.5.2. Configuration

Threshold definition

PHP code
$logger->setThresholdOverride([ LogEntry::SEVERITY_ERROR => 5 ]);

causes all buffered log entries to be processed in case of 5 or more LogEntry::SEVERITY_ERROR events in the current request.

Please note, that processing of log entries is delegated to the respective LogWriters. This may also influence the effective processing of log entries!

Since method setThresholdOverride() allows to pass a list of threshold definitions the statement can be even more complex. The following definition processes all log enries in case of at least one FATAL entry or at lest 5 ERROR entries or at least 20 WARN entries:

PHP code
$logger->setThresholdOverride([ LogEntry::SEVERITY_FATAL => 1, LogEntry::SEVERITY_ERROR => 5, LogEntry::SEVERITY_WARN => 20 ]);

4. Existing LogWriter

The APF ships several LogWriter implementations described within the subsequent chapters.

4.1. FileLogWriter

The FileLogWriter persists the applied log entries to a file. The name of the file contains the current date, an optional host prefix and the log target the LogWriter is registered with (e.g. 2013_01_20__127_0_0_1__apf.log).

The following chapters contain a detailed description of the FileLogWriters.

4.1.1. Log directory

Defining the log directory the constructor (mandatory) or setLogDir() (optional) can be used. Die registration of the FileLogWriters can be done as follows:

PHP code
use APF\core\logging\writer\FileLogWriter; $logger->addLogWriter('file', new FileLogWriter('/path/to/log/dir'));

Configuration of existing instances is as shown below:

PHP code
$fileWriter = $logger->getLogWriter('file'); $logger->setLogDir('/path/to/log/dir');
In order to avoid issues with accessing log directories the log path should be be defined as an absolute path!
4.1.2. Log directory permission

In case the log directory applied to the constructor or to setLogDir() doesn't exist the FileLogWriter will create it lazily. To define the desired file mask the FileLogWriter provides the setLogFolderPermissions() function. Please use the method as follows to the define the umask:

PHP code
$fileWriter = $logger->getLogWriter('file'); $fileWriter->setLogFolderPermissions(0755);
4.1.3. Host prefix

In clustered web server environments log files are often written to a shared filesystem. This eases analysis and archiving but often causes concurrency issues and slows down the application. This is because waiting for write locks blocks the application and increases lock traffic.

One common solution is to split log files into host-dependent files. Often, this is done by adding the host name to the file name. Moreover, this enables you to analyze issues with a dedicated node.

Using 1.15's host prefix you can define a server-dependent prefix for each log file:

PHP code
$fileWriter = $logger->getLogWriter('file'); $fileWriter->setHostPrefix('node1');
To define a generic prefix without the need to create host-specific deployment tasks, you may want to use environment or server configuration values to define the prefix. This may be done using the $_SERVER super global variable:
PHP code
$fileWriter = $logger->getLogWriter('file'); $fileWriter->setHostPrefix($_SERVER['SERVER_ADDR']);

4.2. StdOutWriter

The StdOutLogWriter writes the log entries to the standard output channel of the web server. This means that your visitor will see the log entries.

Log entries may contain sensible data of your application. For security reasons the StdOutWriter should only be used in development environments!

Registration of the StdOutLogWriter can be done within your bootstrap file as follows:

PHP code
use APF\core\logging\writer\StdOutLogWriter; $logger->addLogWriter('stdout', new StdOutLogWriter());

Log entries with the stdout target identifier will be displayed within the HTML source code of your application.

4.3. DatabaseLogWriter

The DatabaseLogWriter enables you to flush the collected entries to a database table. For this reason, please create a table within any database with the following SQL statement:

SQL statement
CREATE TABLE IF NOT EXISTS `{$this->logTable}` ( `target` varchar(10) NOT NULL default '', `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP, `severity` varchar(10) NOT NULL default '', `message` text NOT NULL );

Thereby, {$this->logTable} is the name of the table the DatabaseLogWriter is registered with. Having this virtualization in place you are provided the possibility to register different instances of the DatabaseLogWriter pointing to different log tables.

In order to use this LogWriter please use the following code sample:

PHP code
use APF\core\logging\writer\DatabaseLogWriter; $logger->addlogWriter('db', new DatabaseLogWriter('my-connection', 'app_logs'));

The first argument of the constructor refers to the database connection that is created with the ConnectionManager, the second parameter names the log table.

The DatabaseLogWriter uses the target identifier as one content within the log table. This allows you to re-use one log table for different use cases (if desired).

4.4. GraphiteLogWriter

The GraphiteLogWriter - in combination with the GraphiteLogEntry - is an implementation for the system monitoring tool Graphite. Using the GraphiteLogEntry you can easily send monitoring metricts to a statsd daemon.

Registering the GraphiteLogWriter is as follows:

PHP code
use APF\core\logging\writer\GraphiteLogWriter; $logger->addlogWriter('graphite', new GraphiteLogWriter('my-graphite-host', '1234'));

Due to the fact that the implementation of the GraphiteLogEntry already does the necessary formatting, the GraphiteLogWriter takes the entry separator as an optional configuration attribute. Please use setEntrySeparator() to apply the desired entry separator (default: \n).

Using the GraphiteLogWriter is as follows:

PHP code
use APF\core\singleton\Singleton; use APF\core\benchmark\BenchmarkTimer; $t = Singleton::getInstance('APF\core\benchmark\BenchmarkTimer'); $renderingTime = round(floatval($t->getTotalTime() * 1000)); use APF\core\logging\entry\GraphiteLogEntry; $logger->addEntry(new GraphiteLogEntry( 'graphite', 'rendering-time', 'ms', $renderingTime, LogEntry::SEVERITY_INFO ));
Using the Python implementation of statds you may be facing issues with sending multiple metrics at once. To solve that, you may deactivate the bulk-send mode of the GraphiteLogWriter. Please use the third constructor argument creating the LogWriter or pass false to the setBatchWrites() method:
PHP code
// configuration per constructor use APF\core\logging\writer\GraphiteLogWriter; $logger->addlogWriter( 'graphite', new GraphiteLogWriter('my-graphite-host', '1234', false) ); // configuration per method call $writer = $logger->getLogWriter('graphite'); $writer->setBatchWrites(false);

5. Usage

To use the logger an instance must be obtained using

PHP code
use APF\core\singleton\Singleton; use APF\core\logging\Logger; $logger = Singleton::getInstance(Logger::class);

$logger now contains a reference on the logger in the current scope of application. To add a log message with the content MESSAGE to the log file FILENAME the code snippet

PHP code

can be used. The third parameter of the method logEntry() (optional) specifies the SEVERITY of the message provided. Please find the available severity levels within the LogEntry interface.

You may also want to use the addEntry() method. It takes an instance of an LogEntry interface implementation:

PHP code
use APF\core\logging\SimpleLogEntry; $log->addEntry(new SimpleLogEntry('FILENAME', 'MESSAGE'));
In case the application does excessive logging it may be good to save memory to flush the log buffer more than one time within the request. For this reason, since 1.14 the Logger includes a threshold that causes the log buffer to auto-flush when the number of entries reaches this number. Using setMaxBufferLength() you can configure the value that has initially been set within the class. After flushing, the log buffer is empty again.

6. LogWriter implementation

The interface described in chapter 2 includes the methods writeLogEntries() and setTarget(). The first one takes care about the persistence of the applied entries, the second one receives the target identifier.

During the registration process of the LogWriter the Logger::addLogWriter() injects the target identifier using the LogWriter::setTarget() to the current instance.

The LogWriter interface does not define a constructor to be able to choose the signature according to the purpose of the implementation.

It is recommended to define the constructor to take all required parameters that are required for the LogWriter implementation. This ensures that no errors occur during the execution.

As an implementation example we will use a LogWriter that passes the applied entries to the syslog daemon. To do so, we can use the syslog(int $priority, string $message) PHP function. The implementation is as follows:

PHP code
use APF\core\logging\LogWriter; class SysLogWriter implements LogWriter { private $target; public function writeLogEntries(array $entries) { define_syslog_variables(); openlog($this->target, LOG_PID, LOG_LOCAL0); foreach ($entries as $entry) { syslog( $this->getMappedSeverity($entry->getSeverity()), $entry ); } closelog(); } public function setTarget($target) { $this->target = $target; } private function getMappedSeverity($severity) { if ($severity == LogEntry::SEVERITY_DEBUG) { return LOG_DEBUG; } if ($severity == LogEntry::SEVERITY_ERROR) { return LOG_ERR; } if ($severity == LogEntry::SEVERITY_FATAL) { return LOG_EMERG; } if ($severity == LogEntry::SEVERITY_TRACE) { return LOG_NOTICE; } if ($severity == LogEntry::SEVERITY_WARNING) { return LOG_WARNING; } return LOG_INFO; } }

In order to use this implementation, please register it with the Logger as follows:

PHP code
$logger->addLogWriter('syslog', new SysLogWriter());

7. LogEntry implementation

The interface described in chapter 2 contains the methods getLogTarget(), getSeverity(), and __toString(). The first one returns the log target, the second one the severity definition of the log entry and the last one creates the format of the log message content.

Adding a log entry using logEntry() (uses the SimpleLogEntry implementation internally) or addEntry() (any kind of implementations possible) the Logger pre-sorts the applied entries according to the log target (return value of the getLogTarget() method) for processing the entries at the end of the request.

The LogEntry interface does not define a constructor to be able to choose the signature according to the purpose of the implementation.

It is recommended to define the constructor to take all required parameters that are required for generating the log entry format.

As an implementation example we will create a CsvLogEntry that formats the applied content as comma separated values. The implementation is as follows:

PHP code
use APF\core\logger\LogEntry; class CsvLogEntry implements LogEntry { private $target; private $severity; private $message; private $date; private $time; public function __construct($target, $message, $severity) { $this->date = date('Y-m-d'); $this->time = date('H:i:s'); $this->target = $target; $this->message = $message; $this->severity = $severity; } public function __toString() { return $this->date . ';' . $this->time . ';' . $this->severity . ';' . $this->message; } public function getLogTarget() { return $this->target; } public function getSeverity() { return $this->severity; } }

In case you want to use a CsvLogEntry instead of the standard implementation SimpleLogEntry you can use the Logger::addEntry():

PHP code
use VENDOR\logging\entries\CsvLogEntry; $logger->addEntry(new CsvLogEntry( 'csv-file', 'There is something wrong!', LogEntry::SEVERITY_ERROR ));

In order to create a CSV file with the above implementation an appropriate LogWriter must be registered for the csv-file target:

PHP code
use APF\core\logging\writer\FileLogWriter; $logger->addLogWriter('csv-file', new FileLogWriter('../logs'));


Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.

In order to provide a state-of-the-art web experience and to continuously improve our services we are using cookies. By using this web page you agree to the use of cookies. For more information, please refer to our Privacy policy.