Log managers


1Presentation

A log manager is an object that receives log messages. This allows you to process logs in a more advanced way than simply writing them to a file (as the default framework does).

Typical usage is to send logs to send logs to a centralized server (Graylog, LogStash, Fluentd, rsyslog, syslog-ng, Nagios, ELK, Datadog…), in addition to − or instead of − writing to the log/temma.log file.


2Configuration

In the etc/temma.php configuration file, the logManager directive allows you to define one or more objects that will be called to handle the logs generated by the application.
Objects can be placed in a namespace.

Here is a configuration fragment, which defines a log handler:

<?php

return [
    'application' => [
        'logManager' => 'MyLogManager'
    ]
];

It is also possible to provide a list of objects:

<?php

return [
    'application' => [
        'logManager' => [ 'MyLogManager', '\My\Other\Log\Manager' ]
    ]
];

If you want to use only log handlers, and disable writing to the log/temma.log file, you must set the logFile directive to false, null or assign it an empty string:

<?php

return [
    'application' => [
        'logFile'    => false,
        'logManager' => 'MyLogManager',
    ]
];

3Simple development

A log manager is an object that implements the \Temma\Web\LogManager interface. An instance of the object is created when the framework starts.

The log handler must contain a log() method, which will be called on each new log message, receiving four parameters:

  1. A trace identifier. It is an alphanumeric string of 4 characters which is used to identify the logs generated by the same request.
  2. The text of the log message.
  3. The priority of the message (DEBUG, NOTE, INFO, …). May be null.
  4. The "log class" of the message. May be null.

Example:

/** Log manager that writes messages to a file. */
class FileLogManager implements \Temma\Web\LogManager {
    /**
     * Write application logs to a file.
     * @param  string   $traceId  Trace identifier.
     * @param  string   $text     Message text.
     * @param  ?string  $prio     Message priority.
     * @param  ?string  $class    Message log class.
     */
    public function log(string $traceId, string $text, ?string $prio, ?string $class) : void {
        $path = "/var/log/temma/$traceId.log";
        $message = "[$priority] ($class) $text";
        file_put_contents($path, $message, FILE_APPEND);
    }
}

In this example, the log manager will create a new file for each request processed by the framework.

  • Line 2: Object definition.
  • Line 10: Definition of the log() method, which will be called at each log writing.
  • Line 11: Generation of the file path.
  • Line 12: Generation of the text message.
  • Line 13: Write the message to the file. If the file did not exist, it is created; otherwise, the message is appended to the end of the file.

4Dependency injection

Please see the Dependency Injection documentation to understand its purpose and use in Temma.

For a log manager to have access to the dependency injection component, it must implement the \Temma\Base\Loadable interface (in addition to the \Temma\Web\LogManager interface). It will then have to implement a constructor that takes a \Temma\Base\Loader object as a parameter, through which he can access other data and objects.
The component notably provides access to the framework's configuration data.

Example:

/**
 * Log manager that sends logs to an external service.
 * An "x-external-log" extended configuration must contain a "url" key,
 * containing the URL to send log messages to. 
 */
class ExternalLogManager implements \Temma\Base\Loader, \Temma\Web\LogManager {
    /** External connection URL. */
    private ?string $_url;

    /** Constructor. */
    public function __construct(\Temma\Base\Loader $loader) {
        // retrieve the URL to which logs will be sent
        $this->_url = $loader->config->xtra('external-log', 'url');
    }
    /** Sends the application logs to the external service. */
    public function log(string $traceId, string $text, ?string $prio, ?string $class) : void {
        // check connection parameters
        if (!$this->_url)
            return;
        // creation of the request
        $ctx = stream_context_create([
            'http' => [
                'method'  => 'POST',
                'header'  => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query([
                    'message'  => "($traceId) [$priority] ($class) $text",
                    'hostname' => gethostname(),
                ]),
            ],
        ]);
        // send to the external service
        file_get_contents($this->_url, false, $ctx);
    }
}

In this example, the object retrieves the external service's connection URL from the configuration and then uses it to send log messages.


5Database access

You should know that, in the initialization chain of the framework, the log handlers are created before the connection objects to the data sources are created (this so that in the event of an error during the creation of these connections, the information appears in the logs).

This means that if your log handler needs to use a connection (for example to write logs to a database), it will not be able to retrieve the connection in its constructor; it will have to do this in its log() method, and check that the connection is active.

Example:

/** Log manager that writes to database. */
class DatabaseLogManager implements \Temma\Base\Loader, \Temma\Web\LogManager {
    /** Dependency injection component. */
    private \Temma\Base\Loader $_loader;

    /** Constructor. Get the dependency injection component. */
    public function __construct(\Temma\Base\Loader $loader) {
        $this->_loader = $loader;
    }
    /** Write application logs to database. */
    public function log(string $traceId, string $text, ?string $prio, ?string $class) : void {
        // get the database connection
        $db = $this->_loader->dataSources->db ?? null;
        if (!$db)
            return;
        // log writing
        $sql = "INSERT INTO Log
                SET log_date = NOW(),
                    host = " . $db->quote(gethostname()) . ",
                    message = " . $db->quote($text) . ",
                    traceId = " . $db->quote($traceId) . ",
                    priority = " . $db->quote($priority) . ",
                    class = " . $db->quote($class);
        $db->exec($sql);
    }
}