11 gennaio 2009

Scrivere file di log con php

E' indispensabile sia in fase di sviluppo che di manutenzione di un sito php utilizzare dei files di log per intercettare gli errori o comunque ricostruire il comportamento o il valore delle variabili prima che lo stesso si verifichi.
Per venire in contro agli sviluppatori ci sono framework come log4j tramite cui è possibile "smistare" la scrittura dei log su diversi files.
Personalmente ho ritenuto più semplice e leggero implementare una mia versione di un manager per i log.

Innanzitutto vediamo i parametri di configurazione che utilizzerà il Manager dei log:
 // logging 
$log_path = "log/";
$debugLogFile = "debug.log";
$errorLogFile = "error.log";

define("LOGPATH", $log_path);
define("DEBUGFILE", $debugLogFile);
define("DEBUGLOG", 'DEBUGLOG');
define("ERRLOG", 'ERRLOG');
define("ERRORFILE", $errorLogFile);

Le righe relative alle define hanno essenzialmente lo scopo di dichiarare delle costanti utilizzabili in qualsiasi scope dell'applicazione.

Di seguito l'implementazione del Manager vero e proprio:
/***
* Classe per il logger
*
* @author Alessandro Franzi
*
*/
Class ManageLogger{

private $FILELIST = array();
private $HANDLER = array();
private $ROOT = "";
private $WIN32 = false;
private $FORMAT = "%b %d %H:%M:%S";
private $DEBUG = true;

/***
* Costruttore della classe logger
*/
public function ManageLogger ( $LogRoot, $FileArray = "" ){
if(empty($LogRoot)){
// se non è stata specificata la root del logger
if($this->DEBUG){
error_log("[ErrorLog]: C'è bisogno della Root Folder per creare il logger",0);
}
return;
}
$this->set_root($LogRoot);
if(!empty($FileArray)){
// se non è vuoto l'array contenente i nomi dei files di log
$this->initialize($FileArray);
}
return;
}

/***
* Inizializza l'array dei file di log
*/
public function initialize ($FileArray){
if ( (gettype($FileArray)) != "array"){
if($this->DEBUG){
error_log("[ErrorLog]: Tipo di Array per i file di log invalido",0);
}
return;
}
while ( list ($key,$val) = each ($FileArray) ){
if ( (!empty($key)) and (!empty($val)) ){
// associo i vari file di log al relativo compito
$val = "$this->ROOT"."$val";
$this->FILELIST[$key] = $val;
$this->HANDLER[$key] = null;
}
}
return;
}

/***
* Metodo per scrivere il log
*
*/
public function log ($Handle,$LogEntry)
{
global $php_errormsg;

$filename = $this->FILELIST[$Handle];
$TimeStamp = strftime($this->FORMAT,time());
// associo la mia formattazione alla stringa
$LogEntry = "$TimeStamp - $LogEntry";

if (!$this->HANDLER[$Handle]){
$fd = fopen($filename,"a");
if ( (!$fd) or (empty($fd)) ){
if($this->DEBUG)
{
error_log("[ErrorLog]: Errore fatale sul file: $php_errormsg",0);
}
return;
}
fwrite($fd,"$LogEntry\n");
$this->HANDLER[$Handle] = $fd;
}else{
fwrite($this->HANDLER[$Handle],"$LogEntry\n");
}
return;
}

/***
* Metodo per chiudere i file di log
*/
public function close_logs (){
while ( list ($Handle, $Val) = each ($this->FILELIST) ){
if($this->HANDLER[$Handle]){
$TimeStamp = strftime($this->FORMAT,time());
fclose($this->HANDLER[$Handle]);
unset($this->HANDLER[$Handle]);
}
}
}

/***
* Metodo per settare la root dei file di log
*/
public function set_root ($root){
if(!$this->WIN32){
if(!ereg("\/$",$root)){
$root = "$root"."/";
}
if(is_dir($root)){
$this->ROOT = $root;
}else{
$this->ROOT = "";
if($this->DEBUG){
error_log("La root specificata: [$root] non è una directory",0);
}
}
}else{
// Macchine Winzozz
if(!ereg("\\$",$root)){
$root = "$root"."\\";
}
$this->ROOT = $root;
}
}
/***
* Metodo per loggare messaggi di errore
*
*/
public function error ($messaggio){
$this->log(ERRLOG,"[".pagina::getNomePagina()."] - ".$messaggio);
}
/***
* Metodo per loggare messaggi di debug
*
*/
public function debug ($messaggio){
$this->log(DEBUGLOG,"[".pagina::getNomePagina()."] - ".$messaggio);
}

/***
* Distruttore della classe che invoca la chiusura dei file di log
*/
public function __destruct() {
$this->close_logs();
}
}

Osserviamo come istanziare e inizializzare il ManageLogger :
$ManageLogger = new ManageLogger("./".LOGPATH);
$ManageLogger->initialize(array('ERRLOG' => ERRORFILE ,'DEBUGLOG' => DEBUGFILE));

E' chiaro che se si volessero utilizzare altri appender per i file di log, basterebbe aggiungerli all'array.
Queste sezioni di codice sarebbero sufficienti per poter avere un logger funzionante, richiamando i metodi opportuni (debug e error) sul manager, ma è sicuramente più comodo implementare una classe mediante cui interfacciarsi con il manager.
Il compito della classe Logger infatti è proprio quella di utilizzare i metodi del manageLogger da noi facilmente raggiungibile mediante i suoi metodi statici.
Ecco infine l'implementazione :
/**
* Depends : ManageLogger
* Classe di controllo per l'istanza dell'oggetto ManageLogger
*
* @author Alessandro Franzi
*
*/
class Logger{
/***
* Metodo statico per la stampa del log di debug
*/
public static function debug ($msg) {
global $ManageLogger;
$ManageLogger->debug($msg);
}
/***
* Metodo statico per la stampa del log di errore
*/
public static function error ($msg,$e=null) {
global $ManageLogger;
$ManageLogger->error($msg);
if ($e!=null){
$ManageLogger->error("Causa : ".$e->getMessage()."\nStackTrace : ".$e->getTraceAsString());
}
}
}

Vediamo quindi come poter utilizzare nel nostro codice i log:
// esempio di logging dell'errore in un blocco try/catch
try{
....
}catch(Exception $e){
logger::error("Eccezione",$e);
}
// esempio di logging per il debug
Logger::debug("Il valore della variabile a è ".$a);

Un buon utilizzo dei logger in fase di sviluppo certamente renderà molto più semplice eseguire manutenzioni correttive sullo stesso. Spero che questo codice sia semplice da comprendere, naturalmente sono ben accetti consigli o correzioni sullo stesso.

Nessun commento: