paolo@bimodesign.com | +34 608 61 64 10

Framework

        

Una classe php (e 3 tabelle) per generare file di log (Nuova Versione)

L'obiettivo di questo articolo e' quello di definire una classe PHP che permetta di gestire i file di log, configurando nel DB, i messaggi, il modulo che lo genera, la linea del codice ed il livello di gravita' dell'errore.
Il risultato, chiaramente configurabile ed ampliabile secondo le proprie necessita', sara' un file, uno per ogni modulo, il cui contenuto sara' di questo tipo

[20-10-2012 08:30:45] NomeModulo Classe::Metodo Linea Livello Mess
ovvero
[01-06-2012 13:40:43] oferta::getData 182 2 Prueba

Le tabelle

Innanzitutto definiamo queste tre tabelle ed i relativi campi (uso il db Mysql).
Nota: Non vengono definiti i popolamenti delle relative tabelle.


//
// Tabella Error_Level

CREATE TABLE  `error_level` (
  `iderrorlevel` int(11) NOT NULL,
  `description` varchar(50) NOT NULL,
  PRIMARY KEY (`iderrorlevel`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

//
// Tabella Moduli

CREATE TABLE  `module` (
  `idmodule` int(11) NOT NULL,
  `idmodule_father` int(11) NOT NULL,
  `description` varchar(50) NOT NULL,
  `name_file` varchar(50) NOT NULL,
  PRIMARY KEY (`idmodule`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

//
// Tabella testo Errori

CREATE TABLE  `log_message` (
  `idlogmessage` int(11) NOT NULL AUTO_INCREMENT,
  `idmodule` int(11) NOT NULL,
  `iderrorlevel` int(11) NOT NULL,
  `description` varchar(50) NOT NULL,
  `show_log` char(1) NOT NULL,
  PRIMARY KEY (`idlogmessage`,`idmodule`,`iderrorlevel`),
  KEY `fk_logmessage_module` (`idmodule`),
  KEY `fk_logmessage_errorlevel` (`iderrorlevel`),
  CONSTRAINT `fk_logmessage_errorlevel` FOREIGN KEY (`iderrorlevel`) REFERENCES `error_level` (`iderrorlevel`),
  CONSTRAINT `fk_logmessage_module` FOREIGN KEY (`idmodule`) REFERENCES `module` (`idmodule`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

il cui disegno lo potete trovare nel modello (in formato .pdf)

Nota: il valore del module.description deve essere quello del nome del file, comprensivo dell'estensione .php
Nel caso in cui un modulo e' formato da piu' file .php, per far confluire in un unico log tutti i messaggi, ho introdotto una semplice struttura padre-figlio, ovvero il campo module.idmodule_father che conterra' il riferimento al file .php padre, ovvero il primo modulo della "catena". Quest'ultimo verra' identificato perche' i valori dell'idmodule e idmodule_father saranno uguali. Quindi, sia per i messaggi da inserire nel modulo padre, sia per quelli da inserire nel modulo figlio, il valore del log_message.idmodule sara' quello del padre module.idmodule.
Il valore log_message.description puo' contere o meno, anche variabili, che vanno indicate con il carattere %s. In seguito, descrivo come passare questi valori.

Config e define

Non entro in merito su come configurare la connessione alla base dati, ma in questa versione la connessione e' incapsulata direttamente nella classe.
Nel vostro file di configurazione php, occorre includere queste direttive:

// Db connection
define('__DB_HOSTNAME__','localhost');
define('__DB_USERNAME__','paolo');
define('__DB_PASSWORD__','bindi');
define('__DB_DATABASE__','log');


define('__TABLE_MODULE__','module');
define('__TABLE_LOG_MESSAGE__','log_message');
define('__PATH_LOG_FILE__',__WWW_ROOT__."log/");


La classe php

Come noterete, il codice e' molto semplice e non mi addentro troppo nel descriverlo, eccetto per alcuni punti.
Inoltre, per comprendere il codice e' necessario conoscere le basi della programmazione ad oggetti (OOP), sulle quali non mi dilunghero' in questo articolo

/**
  * @Title: Class Log_Message
  * @Author: Paolo Bindi
  * @Date: 12-06-2012
  * @Version: 2
  */

class log_message {
    
    /**
     * List Class Parameters
     */
    protected $IdDbConexion;
    protected $sql;


    protected $IdModule;
    protected $aPathFileName;
    protected $FileName;
    protected $FileLogName;
    protected $ClassAndMethodName;
    protected $RowNumber;
    protected $IdErrorLevel;
    protected $TextError;
    protected $fp;
    protected $datos;

    public function __construct() {
        $this->IdDbConexion = mysql_connect(__DB_HOSTNAME__, __DB_USERNAME__, __DB_PASSWORD__);
        mysql_select_db(__DB_DATABASE__);
    }

    /**
     * Open, Write and close into a file lof
     * @param nothing
     * @return nothing
     */
    
    public function openWriteCloseFile() {
        $this->fp = fopen(__PATH_LOG_FILE__.$this->FileLogName, "a+");

        fwrite($this->fp, "[".date( 'd-m-Y H:i:s')."] ".$this->ClassAndMethodName." ".$this->RowNumber." ".$this->IdErrorLevel." ".$this->TextError."\n");
        fclose($this->fp);
    }

Utilizzo una sola funzione per l'apertura, la scrittura e la chiusura del file, visto che non e' richiesto di leggere il file di log e, sopratutto per rendere il codice piu' snello. In realta' sarebbe preferibile creare un metodo per l'apertura, uno per la chiusura, e quindi uno per la scrittura.

    /**
     * Set the name file.php (for select from Module Table) and file.log (to write the log)
     * @param PHP Magic constant __FILE__ 
     * @return nothing
     */
    
    public function setFileName($value) {
        // explode path file name
        $this->aPathFileName = explode('/', $value);

        // Get the last element of the array
        $this->FileName = end($this->aPathFileName);

        $this->sql = "SELECT name_file 
                  FROM ".__TABLE_MODULE__." 
                 WHERE idmodule= (SELECT idmodule_father 
			          FROM ".__TABLE_MODULE__." 
			         WHERE name_file='" . $this->FileName . "')";
        $res = mysql_query($this->sql);

	while($arr=mysql_fetch_assoc($res)){
        	$this->FileLogName=substr_replace($arr['name_file'] , 'log', strrpos($arr['name_file'] , '.') +1);
	}
        
        $this->setIdModule();
    }

Visti i commenti al codice, non ci sarebbe bisogno di dettagliare ulteriormente. L'unica cosa che voglio far presente e' che il nome del file, inviato in input, come vedremo alla fine dell'articolo, attraverso la direttiva __FILE__, presenta il percorso completo, ovvero qualcosa del tipo /path/paolo/application/manageTabelle.php. Quindi attraverso la funzione end() che mette a disposizione php, dopo aver creato l'array di tutti gli elementi attraverso la funzione explode(), posso recuperare il nome del file .php.
Notare inoltre come viene estratto il nome del file dalla tabella Module.

    /**
     * Set id Module
     * @param nothing 
     * @return nothing
     */    
    
    protected function setIdModule() {
        $this->sql = "SELECT idmodule_father 
                        FROM ".__TABLE_MODULE__." 
                       WHERE name_file='" . $this->FileName . "'";

        $res = mysql_query($this->sql);

	while($arr=mysql_fetch_assoc($res)){
		$this->IdModule=$arr['idmodule_father'];			 
	}

	mysql_free_result($res);

    }   

    /**
     * Set the Class and Method name (class::method)
     * @param PHP Magic constant __METHOD__ 
     * @return nothing
     */   
    public function setClassAndMethodName($value) {
        $this->ClassAndMethodName = $value;
    }

    /**
     * Set Row Number Code
     * @param PHP Magic constant __LINE__ 
     * @return nothing
     */       
    public function setRowNumber($value) {
        $this->RowNumber = $value;
    }
    
    /**
     * Set the array with all the message of module instead of select from table each time
     * (For performance)
     * @param Nothing
     * @return nothing
     */         


    public function setListMessageByIdModule() {
        
        $this->sql = "SELECT idlogmessage,
                             iderrorlevel,
                             description
                        FROM ".__TABLE_LOG_MESSAGE__." 
                       WHERE idmodule='" . $this->IdModule."'
      		         AND show_log = 'S'";

        $res = mysql_query($this->sql);

	while($arr=mysql_fetch_assoc($res)){
			$this->datos[$arr['idlogmessage']] = $arr;			 
	}

	mysql_free_result($res);
    }  
    
   
    /**
     * Set idErrorLevel and TextError from Array Datos
     * @param Log_Message.idLogMessage
     * @return nothing
     */   
     
      public function getDataMessagebyModuleId($value,$aListParam=NULL) {
        $this->IdErrorLevel = $this->datos[$value]['iderrorlevel'];

        if(!empty($aListParam)){
            $this->TextError = vsprintf($this->datos[$value]['description'],$aListParam);
        }else{
            $this->TextError = $this->datos[$value]['description'];   
        }
    }        
         
}		

Notare la funzione vsprintf che stampa in output un intero array "numerico".
Come potete constatare la classe e' molto semplice. Ora manca solo l'ultimo punto, ovvero come richiamare i suoi metodi.

Call della classe

 $oLogMessage=new log_message($this->new_db);
 
 $oLogMessage->setFileName(__FILE__); //Il nome del file .php deve essere configurato in module.namefile
 $oLogMessage->setClassAndMethodName(__METHOD__);
 $oLogMessage->setRowNumber(__LINE__);
 $oLogMessage->setListMessageByIdModule(); 

Per ogni messaggio da scrivere nel log, occorre chiamare questo due metodi - Il valore 3 rappresenta il valore del campo Log_Message.idLogMessage
L'array puo' essere nullo ho avere valori (da 1 a N) ed in quest'ultimo caso questi corrispondono a quanti parametri (indicato con %s) prevede il testo del messaggio.
Nel testo del messaggio tutti i parametri devono essere dichiarati come %s e devono essere indicati in ordine di visualizzazione.

$aListParameter=array("parametro1","parametro2","parametro3");

(Caso1 - Sin parametros) $oLogMessage->getDataMessagebyModuleId(3);

(Caso2 - Con parametros) $oLogMessage->getDataMessagebyModuleId(3,$aListParameter);

$oLogMessage->openWriteCloseFile();
 

Qualche considerazione.
Innanzitutto nel codice appena indicato, ho generato un solo messaggio di log, corrispondente al codice "3" della tabella log_message che, chiaramente, va pre-popolata secondo le proprie esigenze. Per inserire uno o piu' messaggi, occorre ripetere piu' volte il blocco sopra indicato, eccetto per l'instanza della classe.
Inoltre, tra le Magic Constants che mette a disposizione il php (qui il link ufficiale), esiste quella chiamata __METHOD__ che restituisce il nome del metodo comprensivo della classe a cui appartiene, nel seguente formato class::method. Quindi non e' necessario utilizzare anche la __CLASS__.

Qui infine trovate il pacchetto completo, comprensivo del file .php della classe e del modello del db (in formato .pdf)

Spero vi possa essere utile.