PSR-3 Logger Interface: standard log per PHP

PSR-3 Logger Interface del PHP-FIG (PHP Framework Interop Group) si propone come uno standard comune per il log degli applicativi in PHP.

The main goal is to allow libraries to receive a Psr\Log\LoggerInterface object and write logs to it in a simple and universal way.

L’obiettivo è quello di far sì che le varie componenti software possano utilizzare un oggetto di tipo Psr\Log\LoggerInterface per scrivere messaggi di log in modo semplice ed universale.

Anche gli applicativi più complessi come i framework POTREBBERO estendere l’interfaccia del logger per arricchirla di funzioni, ma DOVREBBERO assicurarsi di mantenerne la compatibilità. In questo modo tutti i componenti e le librerie utilizzate potrebbero scrivere nei log centralizzati dell’applicativo.

La parola implementatore (implementor) utilizzata nel testo dello standard PSR3 rappresenta qualcuno che implementa la LoggerInterface per inserire un logger in una libreria o framework .
Gli utenti dei logger vengono definiti utenti (user).

Per il significato delle altre parole chiave si rimanda alle note dell’articolo sul PSR-1.

PSR-3: specifiche

LoggerInterface: l’Interfaccia di base

La LoggerInterface espone otto metodi per scrivere i log utilizzando i rispettivi livelli dello standard RFC 5424: debug, info, notice, warning, error, critical, alert, emergency. Tali livelli vengono definiti come costanti.

<?php

namespace Psr\Log;

class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}

Un nono metodo log accetta come primo argomento il livello di log.
La chiamata a questo metodo con una delle costanti di livello DEVE avere lo stesso effetto della chiamata al metodo specifico del relativo livello. Chiamare questo metodo con un livello non definito DEVE lanciare un’eccezione di tipo Psr\Log\InvalidArgumentException se l’implementazione non prevede tale livello. Gli utenti NON DOVREBBERO utilizzare un livello personalizzato senza essere certi che l’attuale implementazione lo supporti.

Psr\Log\LoggerInterface: il codice

Di seguito il codice dell’interfaccia LoggerInterface.

<?php

namespace Psr\Log;

interface LoggerInterface
{
    /**
     * Il sistema è inutilizzabile
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function emergency( $message, array $context = [] );

    /**
     * È necessario agire immediatamente.
     *
     * Esempio: Il sito web è down, database non disponibile, ... 
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function alert( $message, array $context = [] );

    /**
     * Condizione critica
     *
     * Esepio: Componente non trovato, eccezione inaspettata, ...
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function critical( $message, array $context = [] );

    /**
     * Errori di runtime che non richiedono un'azione immediata 
     * ma in genere dovrebbero essere loggati e monitorati.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function error( $message, array $context = [] );

    /**
     * Occorrenze eccezionali che non sono errori
     * Esempio: uso di api deprecate.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function warning( $message, array $context = [] );

    /**
     * Eventi normali ma significativi.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function notice( $message, array $context = [] );

    /**
     * Eventi interessanti
     * Esempio: Log-in utente, esecuzione query, ...
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function info( $message, array $context = [] );

    /**
     * Informazioni dettagliate sul debug.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    public function debug( $message, array $context = [] );

    /**
     * Creazione del Log con un livello arbitrario.
     *
     * @param mixed   $level
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log( $level, $message, array $context = [] );
}

La gestione dell’eccezione di Psr\Log\InvalidArgumentException estende la classe nativa di php InvalidArgumentException e deve essere lanciata dal logger qualora vengano passati ai metodi dei paramentri non corretti.

<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException {
    // class body
}

PSR-3: messaggi

Ogni metodo accetta una stringa come messaggio o un oggetto con un metodo __toString (). Gli implementatori POSSONO implementare una gestione personalizzata per gli oggetti passati al metodo. In caso contrario, gli implementatori DEVONO eseguire il type casting del valore passato in stringa.

Il messaggio PUÒ contenere un placeholder che gli implementatori possono sostituire con i valori presi dall’array del parametro context.

I nomi dei placeholder DEVONO corrispondere alle chiavi del context array.

I nomi dei placeholder DEVONO essere delimitati fra due parentesi graffe singole {placeholder}. NON DEVE esserci spazio tra i delimitatori e il nome placeholder.

I nomi dei placeholder DOVREBBERO essere composti solo dai caratteri A-Z, a-z, 0-9, underscore _, e punto .. L’uso di altri caratteri è riservato per future modifiche delle specifiche dei placeholder.

Gli implementatori POSSONO usare i placeholder per implementare escaping strategy o gestire la traduzione dei messagi. Gli utenti non DOVREBBERO effettuare l’escape preventivo dei placeholder poiché non possono sapere in quale contesto i dati inseriti nei messaggi verranno visualizzati.

Il PHP-FIG fornisce una semplice funzione di esempio per comprendere meglio la funzione dei placeholder nei messaggi di log.

<?php

/**
 * Sostituisce i placeholder dei messaggi con i rispettivi valori.
 */
function interpolate($message, array $context = []) {
    // Crea l'array delle sostituzioni
    $replace = array();
    foreach ($context as $key => $val) {
        // controlla che il valore possa essere usato come stringa.
        if (!is_array($val) &amp;&amp; (!is_object($val) || method_exists($val, '__toString'))) {
            $replace['{' . $key . '}'] = $val;
        }
    }

    // Sostituisce i placeholder con i rispettivi valori.
    return strtr($message, $replace);
}

// Messaggio contenente il placeholder fra graffe
$message = "User {username} created";

// Il context array con i dati da sostituire ai placeholder
$context = array('username' => 'bolivar');

// stampa "User bolivar created"
echo interpolate($message, $context);

Context data

Ogni metodo accetta un array contenente dati contestuali (context data) per gestire ulteriori informazioni che non possano essere ridotte alla stringa del messagio. L’array può contenere qualsiasi tipo di dato. Gli implementatori DEVONO utilizzare i dati di contesto con moderazione. Un context data NON DEVE generare un’eccezione né errori php di tipo error, warning o notice

Se nei context data viene passato un oggetto di tipo Exception, questo DEVE essere passato nella chiave 'exception'. Poiché il log delle eccezioni è un’esigenza diffusa, questa regola dello standard PSR-3 consente di registrare questo tipo di evento ed il relativo stack trace. Gli implementatori DEVONO verificare che il dato contenuto nella chiave 'exception' sia realmente un oggetto di tipo Exception dato che l’elemento dell’array potebbe contenere qualsiasi tipo di dato.

PSR-3: classi helper ed interfacce

La classe di esempio Psr\Log\AbstractLogger facilita l’implementazione di LoggerInterface estendendola e implementando il metodo log generico. Gli altri otto metodi richiamano il metodo log passandogli la corretta costante di livello, messaggio ed eventuale contesto.

<?php

namespace Psr\Log;

abstract class AbstractLogger implements LoggerInterface {

    public function emergency($message, array $context = []) {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    public function alert($message, array $context = []) {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    public function critical($message, array $context = []) {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    public function error($message, array $context = [])) {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    public function warning($message, array $context = []) {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    public function notice($message, array $context = []) {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    public function info($message, array $context = []) {
        $this->log(LogLevel::INFO, $message, $context);
    }

    public function debug($message, array $context = []) {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}

Similmente ad AbstractLogger è possibile creare un trait Psr\Log\LoggerTrait utile nel caso si voglia implementare il logger in diverse classi. Poiché i traits non possono implementare delle interfacce, in questo caso si dovrà di fatto riscrivere il codice di LoggerInterface.

namespace Psr\Log;

trait LoggerTrait {

    public function emergency($message, array $context = []) {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    public function alert($message, array $context = []) {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    public function critical($message, array $context = array[]) {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    public function error($message, array $context = [])) {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    public function warning($message, array $context = []) {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    public function notice($message, array $context = [])
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    public function info($message, array $context = [])
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    public function debug($message, array $context = [])
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    abstract public function log($level, $message, array $context = []);
}

Veniamo ora al “piuttosto che niente”! Psr\Log\NullLogger implementa AbstractLogger e PUÒ essere usato dagli utenti, se non gli viene fornito un apposito log, da utilizzare con una fall-back. E’ una sorta di “black hole” in cui inviare qualcosa che potrà essere loggato.

<?php

namespace Psr\Log;

class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, $message, array $context = array())
    {
        // class body
    }
}

Psr\Log\LoggerAwareInterface contiene solo un metodo setLogger(LoggerInterface $logger) che PUÒ essere utilizzato dai framework per collegarsi automaticamente a istanze del logger.

<?php

namespace Psr\Log;

interface LoggerAwareInterface {
    public function setLogger(LoggerInterface $logger);
}

Il trait Psr\Log\LoggerAwareTrait PUÒ essere utilizzato per implementare facilmente la medesima interfaccia in qualsiasi classe così da avere accesso all’oggetto $this->logger.

PSR-3 Logger package

Le interfacce e le classi descritte, nonché le relative classi di eccezione e una suite di test per verificare l’implementazione sono fornite come parte del pacchetto psr/log sul sito ufficiale di PHP-FIG

← Previous post

Next post →

2 Comments

  1. Michele

    articolo veramente interessante!

  2. cesarebordi

    Grazie! Spero ti sia stato utile!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *