Quando si ha a che fare con progetti di dimensione medio/grandi i file di log sono sicuramente un’esigenza indispensabile per verificare malfunzionamenti, errori o semplicemente per tenere traccia delle operazione effettuate. CMS molto famosi forniscono tra le loro librerie ottimi strumenti di logging; altri invece, probabilmente perchè destinati alla realizzazione di progetti più piccoli, non hanno previsto alcun genere di strumento da utilizzare per il debug.
Per chi avesse la necessità di un semplice strumento per il log delle operazioni, voglio condividere una classe in PHP semplicissima da utilizzare e personalizzare.
Caratteristiche della classe
Le caratteristiche fondamentali della classe da me realizzata sono:
- Scelta della directory e del nome del file di log
- Scelta della dimensione massima del file e dell’operazione da compiere (eliminare o rinominare) sul file una volta raggiunta tale dimensione
- Personalizzazione del formato di datetime
- Scelta del timezone
- 5 tipologie dei messaggi di log: (debug, info, warn, error, profile)
- Misurazione del tempo di esecuzione di uno script attraverso il profile
Il codice PHP
Vediamo il codice completo della classe per poi analizzarlo nel dettaglio:
'[INFO] :',
'W' => '[WARNING]:',
'E' => '[ERROR] :',
'D' => '[DEBUG] :',
'P' => '[PROFILE]:'
);
/**
* Unix timestamp for profiling
*
* @access private
*/
private $start_profile = NULL;
/**
* Constructor
*
* @param string $directory
* @param string $filename
* @param string $datetime
* @param string $maxfilesize (in MB)
* @param string $exceeded_size
*/
function __construct( $directory = "log/", $filename = "app.log", $datetime = "c", $maxfilesize = "1", $exceeded_size = "rename") {
$this->directory = $directory;
$this->filename = $filename;
$this->datetime_format = $datetime;
$this->set_maxfilesize($maxfilesize);
$this->set_exceeded_size($exceeded_size);
$this->logpath = $directory.$filename;
$this->set_default_timezone();
$this->open();
}
/**
* Destructor
*
*/
function __destruct() {
$this->close();
unset($this->start_profile);
}
/**
* Open the log file
*
* If the filesize exceed the max file size the function provides to
* rename or remove the old file.
*
* @return handle to log file
*/
function open() {
if( file_exists($this->logpath) ) {
if($this->filesize() >= $this->maxfilesize) {
if($this->exceeded_size=='delete') {
$this->handle = fopen($this->logpath, "w+") or die('Error while trying to open file specified');
} else {
$newfilename = $this->logpath.time();
if( rename($this->logpath,$newfilename) )
$this->handle = fopen($this->logpath, "a+") or die('Error while trying to open file specified');
else die('Error while trying to rename old file');
}
}
else $this->handle = fopen($this->logpath, "a+") or die('Error while trying to open file specified');
} else $this->handle = fopen($this->logpath, "w+") or die('Error while trying to open file specified');
}
/**
* Close the log file
*
*/
function close() {
fclose($this->handle);
}
/**
* Return the file size (in byte)
*
* @return filesize
*/
function filesize() {
return filesize($this->logpath);
}
/**
* Return the date with the datetime_format
*
* @return string $datetime
*/
function datetime() {
return date($this->datetime_format);
}
/**
* Set max file size for log file
*
* @param int $size (in MB)
*
* @return int $maxfilesize (in bytes)
*/
function set_maxfilesize($size) {
if($size)
$this->maxfilesize = $size * 1024 * 1024;
}
/**
* Set action to do when the max file size exceeds
*
* @param string $action
*/
function set_exceeded_size($action) {
switch($action) {
case 'delete':
case 'remove': $this->exceededsize = 'delete';
break;
case 'rename':
default: $this->exceededsize = 'rename';
break;
}
}
/**
* Set default timezone
*
* @param string $timezone
*/
function set_default_timezone($timezone = NULL) {
if($timezone) $this->default_timezone = $timezone;
date_default_timezone_set($this->default_timezone);
}
/**
* Get the log filename
*
* @return string $filename
*/
function get_filename() {
return $this->filename;
}
/**
* Get the log directory
*
* @return string $directory
*/
function get_directory() {
return $this->directory;
}
/**
* Log method
*
* @param string $string
* @param string $log_type
*/
function log( $string, $log_type = "I" ) {
$datetime = $this->datetime();
$fwrite = $datetime . " " . $this->log_types[$log_type] . " ". $string . "\r\n";
fwrite($this->handle, $fwrite) or die('Error while trying to write on file specified.');
}
/**
* Alias for debugging log
*
* @param string $string
*/
function _($string) {
$this->log($string, 'D');
}
/**
* Debug method
*
* @param string $string
*/
function debug($string) {
$this->log($string, 'D');
}
/**
* Info method
*
* @param string $string
*/
function info($string) {
$this->log($string, 'I');
}
/**
* Warning method
*
* @param string $string
*/
function warn($string) {
$this->log($string, 'W');
}
/**
* Error method
*
* @param string $string
*/
function error($string) {
$this->log($string, 'E');
}
/**
* Profile method
*
* The method must be called two times. The first call saves the timestamp on
* $start_profile; the second call returns the execution time of script.
*
* @param string $string
*/
function profile($string) {
if(!$this->start_profile) {
$this->start_profile = microtime(true);
$this->log($string,'P');
} else {
$execution_time = microtime(true) - $this->start_profile;
$this->log($string.$execution_time,'P');
unset($this->start_profile);
unset($this->$execution_time);
}
}
}
?>
Come possiamo vedere i metodi utilizzati sono davvero pochissimi, solo gli essenziali. La classe può essere estesa per includere tutti quegli strumenti più avanzati che si necessitano in progetti più elaborati.
Vediamo innanzitutto quali sono e a cosa servono le singole proprietà della classe:
- $directory: percorso della cartella in cui è contenuto il file
- $filename: nome del file di log
- $logpath: percorso completo del file di log
- $handle: puntatore al file di log (per la scrittura e lettura sul file)
- $maxfilesize: dimensione massima (in MB) del file. Di default la dimensione è 1MB.
- $exceeded_size: operazione da compiere quando il file supera la dimensione massima. Può assumere il valore “remove” o “rename”.
- $datetime_format: formato della data utilizzato nel log. Accetta tutti i formati accettati dalla funzione date(). Di default utilizza il formato ISO 8601 “c”.
- $default_timezone: timezone utilizzato. Di default utilizza “Europe/London”.
- $log_types: array contenente le tipologie di log, utile per filtrare i risultati in base alla tipologia. Di default sono presenti i seguenti tipi: info, debug, profile, warn, error.
- $start_profile: variabile utilizzata per misurare la durata di uno script. La prima volta che viene chiamato il metodo profile(), viene salvato il timestamp in questa variabile.
Vediamo ora i metodi presenti nella classe:
- __construct(): il metodo più ovvio della classe. Prende in ingresso la directory e il nome del file di log, il datetime, la dimensione massima e l’azione da compiere una volta superata la dimensione massima. Provvede a richiamare il metodo open() per l’apertura del file.
- __destruct(): il distruttore della classe. Provvede a chiudere il file aperto attraverso la chiamata al metodo close()
- open(): metodo utilizzato per aprire il file. Verifica che il file esiste altrimenti lo crea. Nel caso che il file superi la dimensione massima consentita, provvede ad eseguire l’azione di default
- close(): chiude il file.
- filesize(): resituisce la dimensione del file di log.
- datetime(): restituisce la data nel formato definito.
- set_maxfilesize(): imposta la dimensione massima del file.
- set_exceeded_size(): imposta l’azione da compiere quando si supera la dimensione massima.
- set_default_timezone(): imposta il timezone.
- get_filename(): restituisce il nome del file.
- get_directory(): restituisce il path del file.
- log(): il metodo più importante. Prende in ingresso la stringa da stampare e la tipologia di log e scrive sul file la stringa.
- debug() - info() - warn() - error(): metodi che richiamano il metodo log con la tipologia già impostata
- _(): alias del metodo debug(). E’ utilizzato per velocizzare le operazione di debug
- profile(): il metodo utilizzato per misurare il tempo di esecuzione di uno script. Per funzionare correttamente deve essere richiamato due volte. La prima volta salva il timestamp e scrive la stringa sul file di log. La seconda volta restituisce la stringa passata e il tempo di esecuzione (in secondi).
Un esempio pratico
Vediamo ora alcuni esempi di utilizzo della classe e cosa viene scritto all’interno del file.
//istanziazione classe e apertura/creazione file di log
$log = new Log('log/', 'system.log');
$log->info('Log di info');
$log->debug('Log di debug');
$log->warn('Log di warning');
$log->error('Log di errore');
$log->_('Log di debug utilizzando il metodo alias');
//le istruzioni precedenti restituiscono le seguenti righe:
2010-11-02T10:00:35+00:00 [INFO] : Log di info
2010-11-02T10:00:35+00:00 [DEBUG] : Log di debug
2010-11-02T10:00:35+00:00 [WARN] : Log di warning
2010-11-02T10:00:35+00:00 [ERROR] : Log di errore
2010-11-02T10:00:35+00:00 [DEBUG] : Log di debug utilizzando il metodo alias
//esempio di utilizzo del metodo profile:
$log->profile('Inizio profiling');
sleep(5); //pausa di 5 secondi
$log->profile('Fine profiling. Durata script: ');
//lo script precedente restituisce le seguenti righe:
2010-11-02T10:00:35+00:00 [PROFILE] : Inizio profiling
2010-11-02T10:00:40+00:00 [PROFILE] : Fine profiling. Durata script: 5.000
Come avete visto dagli esempi precedenti, utilizzare la classe è davvero molto semplice. Non escludo che la classe verrà estesa in futuro ma, già così, garantisce buoni risultati. La classe è anche disponibile per il download al seguente link:
Download