lucinda/logging

High-performance API performing logging for PHP applications into files or SysLog

v4.1.2 2022-06-05 07:28 UTC

README

Table of contents:

About

This API is a skeleton (requires binding by developers) logging system built on principles of simplicity and flexibility. Unlike Monolog, the industry standard in our days, it brings no tangible performance penalties and has near-zero learning curve just by keeping complexity to a minimum while offering you the ability to extend functionalities.

diagram

The whole idea of logging is reduced to just three steps:

  • configuration: setting up an XML file where one or more loggers are set for each development environment
  • binding points: binding user-defined components defined in XML/code to API prototypes in order to gain necessary abilities
  • logging: creating a Wrapper instance based on above XML and using it to log

API is fully PSR-4 compliant, only requiring PHP8.1+ interpreter and SimpleXML extension. To quickly see how it works, check:

  • installation: describes how to install API on your computer, in light of steps above
  • unit tests: API has 100% Unit Test coverage, using UnitTest API instead of PHPUnit for greater flexibility
  • example: shows a deep example of API functionality based on unit test for Wrapper

All classes inside belong to Lucinda\Logging namespace!

Configuration

To configure this API you must have a XML with a loggers tags inside:

<loggers path="...">
	<{ENVIRONMENT}>
		<logger class="..." {OPTIONS}/>
		...
	</{ENVIRONMENT}>
	...
</loggers>

Where:

  • loggers: (mandatory) holds global logging policies.
    • {ENVIRONMENT}: name of development environment (to be replaced with "local", "dev", "live", etc)

Example:

<loggers>
    <local>
        <logger class="Lucinda\Logging\Driver\File\Wrapper" path="messages" format="%d %v %e %f %l %m %u %i %a" rotation="Y-m-d"/>
    </local>
    <live>
        <logger class="Lucinda\Logging\Driver\File\Wrapper" path="messages" format="%d %v %e %f %l %m %u %i %a" rotation="Y-m-d"/>
        <logger class="Lucinda\Logging\Driver\SysLog\Wrapper" application="unittest" format="%v %e %f %l %m %u %i %a"/>
    </live>
</loggers>

Binding Points

In order to remain flexible and achieve highest performance, API takes no more assumptions than those absolutely required! It offers developers instead an ability to bind to its prototypes via XML:

XML Attribute @ Tag Class Prototype Ability Gained
class @ logger AbstractLoggerWrapper Registers a logger

API already has following AbstractLoggerWrapper implementation embedded:

But developers can bind their own (check: How to Bind a Custom Logger)

Logging

Now that XML is configured, you can get a logger to save and use later on whenever needed by querying Wrapper:

$object = new Lucinda\Logging\Wrapper(simplexml_load_file(XML_FILE_NAME), DEVELOPMENT_ENVIRONMENT);
$logger = $object->getLogger();

Logger returned is a Logger that hides complexity of logger(s) underneath through a common interface centered on logging operations. Each Logger must have a AbstractLoggerWrapper whose job is to generate it based on info in XML.

NOTE: because XML parsing is somewhat costly, it is recommended to save $logger somewhere and reuse it throughout application lifecycle.

Installation

First choose a folder where API will be installed then write this command there using console:

composer require lucinda/logging

Then create a configuration.xml file holding configuration settings (see configuration above) and a index.php file (see logging above) in project root with following code:

require(__DIR__."/vendor/autoload.php");
$object = new Lucinda\Logging\Wrapper(simplexml_load_file("configuration.xml"), "local");
$logger = $object->getLogger();
$logger->info("test");

Above has logged a "test" message with LOG_INFO priority in messages__YYYY-MM-DD.log file if same loggers tag as in example above is used.

Unit Tests

For tests and examples, check following files/folders in API sources:

NOTE: on first run only, test.php will fail on syslog tests but from that moment on it will consistently pass

Reference Guide

Interface Logger

Logger interface provides blueprints for level-oriented logging using following methods:

Method Arguments Returns Description
emergency \Throwable $exception void logs a \Throwable using LOG_ALERT priority
alert \Throwable $exception void logs a \Throwable using LOG_CRIT priority
critical \Throwable $exception void logs a \Throwable using LOG_ERR priority
error \Throwable $exception void logs a \Throwable using LOG_WARNING priority
warning string $message void logs a string using LOG_WARNING priority
notice string $message void logs a string using LOG_NOTICE priority
debug string $message void logs a string using LOG_DEBUG priority
info string $message void logs a string using LOG_INFO priority

Usage example:

https://github.com/aherne/php-logging-api/blob/master/drivers/File/Logger.php

Abstract Class LoggerWrapper

AbstractLoggerWrapper abstract class implements conversion of data in XML to a Logger instance via following public methods:

Method Arguments Returns Description
__construct SimpleXMLElement $xml void Reads XML and delegates to setLogger method
getLogger void Logger Gets Logger generated based on XML

and following prototype method that needs to be implemented by developers:

Method Arguments Returns Description
setLogger SimpleXMLElement $xml void Reads XML and generates a Logger object

Usage example:

https://github.com/aherne/php-logging-api/blob/master/drivers/File/Wrapper.php

Specifications

Some guides helping developers to get the best of this API:

How are log lines formatted

As one can see above, logger tags whose class is Driver\File\Wrapper and Driver\SysLog\Wrapper support a format attribute whose value can be a concatenation of:

  • %d: current date using Y-m-d H:i:s format.
  • %v: syslog priority level constant value matching to Logger method called.
  • %e: name of thrown exception class ()
  • %f: absolute location of file that logged message or threw a Throwable
  • %l: line in file above where message was logged or Throwable/Exception was thrown
  • %m: value of logged message or Throwable message
  • %e: class name of Throwable, if log origin was a Throwable
  • %u: value of URL when logging occurred, if available (value of $_SERVER["REQUEST_URI"])
  • %a: value of USER AGENT header when logging occurred, if available (value of $_SERVER["HTTP_USER_AGENT"])
  • %i: value of IP when logging occurred, if available (value of $_SERVER["REMOTE_ADDR"])

Example:

<logger format="%d %f %l" .../>

How to bind a custom logger

Let us assume you want to bind a new SQL logger to this API. First you need to implement the logger itself, which must extend Logger and implement its required log method:

class SQLLogger extends Lucinda\Logging\Logger
{
    private $schema;
    private $table;

    public function __construct(string $schema, string $table)
    {
        $this->schema = $schema;
        $this->table = $table;
    }

    protected function log($info, int $level): void
    {
        // log in sql database based on schema, table, info and level
    }
}

Now you need to bind logger above to XML configuration. To do so you must create another class extending AbstractLoggerWrapper and implement its required setLogger method:

class SQLLoggerWrapper extends Lucinda\Logging\AbstractLoggerWrapper
{
    protected function setLogger(\SimpleXMLElement $xml): Lucinda\Logging\Logger
    {
        $schema = (string) $xml["schema"];
        $table = (string) $xml["table;
        return new SQLLogger($schema, $table);
    }
}

In that case if "psr-4" attribute in composer.json associates "Lucinda\Project\" with "src/" folder then SQLLoggerWrapper must be placed in src/Loggers folder then you finally need to bind it to XML:

<loggers>
    <local>
        <logger class="Lucinda\Project\Loggers\SQLLoggerWrapper" table="logs" schema="logging_local"/>
    </local>
    <live>
        <logger class="Lucinda\Project\Loggers\SQLLoggerWrapper" table="logs" schema="logging_production"/>
    </live>
</loggers>