Ho letto in particolare 'error logging' e ho trovato la function 'error_log' che sembra essere un buon strumento da usare per gestire la logging degli errori. Ma come è il modo più fluido e migliore per usarlo?
Se ho un
try { //try a database connection... } catch (PDOException $e) { error_log($e->getMessage(), 3, "/var/tmp/my-errors.log"); }
Ciò registrerebbe l'errore nel file my-errors.log. Ma cosa succede se a volte ho bisogno di cambiare la posizione di where si trova il file, una nuova cartella o qualcosa del genere. Se ho un sacco di file ho bisogno di cambiarli tutti.
Ora ho iniziato a pensare di usare una variabile per impostare il path del log degli errori. Certo che potrebbe funzionare, ma cosa succede se voglio usare error_log in un metodo di function o di class? Quindi avrei bisogno di impostare la variabile come globale, ma questa è considerata una ctriggers pratica! Ma cosa succede se non wheressi usare la function in profondità in una class, non sarebbe considerata una ctriggers pratica? Qual è una buona soluzione qui?
<?php function legit() { try { if (1 == 1) { throw new Exception('There was an error here'); } } catch (Exception $e) { throw new Exception('throw the error to the try-catch outside the function...'); } } try { legit(); } catch (Exception $e) { echo 'error here' . $e->getMessage(); //log it }
Questo è un esempio di ciò di cui stavo parlando sopra (non avendo il logging in profondità in una class / function … è un buon modo?)
Più avanti:
Non sono abbastanza sicuro di come dovrei usare le eccezioni in generale. Diciamo che voglio fare un INSERT in un database con SQL all'interno di un metodo, dovrei usare un try / catch e quindi rilanciare l'exception se fallisce? È considerata una buona pratica? Esempi per favore.
Innanzitutto, vorrei raccomandarti per aver guardato i methods di errore standard all'interno di PHP. Sfortunatamente error_log
ha alcune limitazioni come hai scoperto.
Questa è una lunga risposta, continua a leggere per scoprire:
trigger_error
e trigger_error
TL; DR Usa trigger_error
per generare errori e set_error_handler
per registrarli.
Quando le cose non vanno come previsto nel tuo programma, spesso vuoi segnalare un errore in modo che qualcuno o qualcosa venga notificato. Un errore riguarda una situazione in cui il programma potrebbe continuare, ma si è verificato qualcosa di notevole, probabilmente dannoso o errato. A questo punto molte persone vogliono registrare immediatamente l'errore con il loro pacchetto di logging preferito. Credo che questa sia esattamente la cosa sbagliata da fare. Raccommand di utilizzare trigger_error
per generare l'errore in modo che possa essere gestito con un callback impostato da set_error_handler
. Consente di confrontare queste opzioni:
Registrazione dell'errore direttamente
Quindi, hai scelto il tuo pacchetto di logging. Ora sei pronto per diffondere le chiamate al tuo registratore ovunque si verifichi un errore nel tuo codice. Vediamo una singola chiamata che potresti fare (userò un logger simile a quello nella risposta di Jack):
Logger::getLogger('standard')->error('Ouch, this hurts');
Cosa ti serve per eseguire questo codice?
Classe : Logger Metodo : getLogger Ritorno : object con metodo 'errore'
Queste sono le dependencies necessarie per utilizzare questo codice. Chiunque voglia riutilizzare questo codice dovrà fornire queste dependencies. Ciò significa che una configuration PHP standard non sarà più sufficiente per riutilizzare il tuo codice. Con il migliore dei casi, utilizzando Dependency Injection è ancora necessario che un object logger venga passato in tutto il codice in grado di emettere un errore.
Inoltre, oltre a qualunque sia il codice responsabile, ha anche la responsabilità di registrare l'errore. Ciò va contro il principio di responsabilità unica .
Possiamo vedere che registrare l'errore direttamente è sbagliato .
trigger_error per il salvataggio
PHP ha una function chiamata trigger_error
che può essere utilizzata per generare un errore proprio come fanno le funzioni standard. I livelli di errore che si utilizzano con esso sono definiti nelle costanti del livello di errore . Come utente è necessario utilizzare uno degli errori utente: E_USER_ERROR
, E_USER_WARNING
o il valore predefinito E_USER_NOTICE
(altri livelli di errore sono riservati per le funzioni standard ecc.). L'utilizzo di una function PHP standard per aumentare l'errore consente di riutilizzare il codice con qualsiasi installazione PHP standard! Il nostro codice non è più responsabile per la logging dell'errore (solo assicurandosi che sia stato generato).
Usando trigger_error
eseguiamo solo metà del process di logging degli errori (innalzando l'errore) e salviamo la responsabilità di rispondere all'errore per il gestore degli errori che verrà trattato successivamente.
Gestore degli errori
Impostiamo un gestore di errori personalizzato con la function set_error_handler
(vedi l'impostazione del codice). Questo gestore di errori personalizzato sostituisce il gestore di errori PHP standard che normalmente registra i messaggi nel log degli errori del server Web in base alle impostazioni di configuration di PHP. Possiamo ancora usare questo gestore di errori standard restituendo false
all'interno del nostro gestore di errori personalizzato.
Il gestore di errori personalizzato ha una sola responsabilità: rispondere all'errore (incluso qualsiasi logging che si desidera eseguire). All'interno del gestore di errori personalizzato hai pieno accesso al sistema e puoi eseguire qualsiasi tipo di logging che desideri. Praticamente qualsiasi logger che utilizza il model di progettazione di Observer sarà ok (non ho intenzione di entrare in quello che ritengo sia di secondaria importnza). Questo dovrebbe consentire di colbind nuovi osservatori di log per submit l'output where necessario.
Hai il controllo completo di fare ciò che ti piace con gli errori in un'unica parte del tuo codice gestibile. La logging degli errori può ora essere modificata rapidamente e facilmente da un progetto all'altro o all'interno di un singolo progetto da una pagina all'altra. È interessante notare che anche gli errori @
soppressi lo rendono al gestore di errori personalizzato con un errno
di 0 che, se la maschera error_reporting
viene rispettata, non dovrebbe essere segnalata.
Quando i buoni errori vanno male – Errori fatali
Non è ansible continuare da determinati errori. I seguenti livelli di errore non possono essere gestiti da un gestore di errori personalizzato: E_ERROR
, E_PARSE
, E_CORE_ERROR
, E_CORE_WARNING
, E_COMPILE_ERROR
, E_COMPILE_WARNING
. Quando questi tipi di errori vengono triggersti da una chiamata di function standard, il gestore di errori personalizzato viene saltato e il sistema si arresta. Questo può essere generato da:
call_this_function_that_obviously_does_not_exist_or_was_misspelt();
Questo è un grave errore! È imansible da recuperare e il sistema sta per spegnersi. La nostra unica scelta è avere un accordo register_shutdown_function
con l'arresto. Tuttavia questa function viene eseguita each volta che uno script viene completato (con esito positivo, oltre che non riuscito). Usando questo e error_get_last
possono essere registrate alcune informazioni di base (il sistema è quasi inattivo a questo punto) quando l'ultimo errore è stato un errore fatale. Può anche essere utile submit il codice di stato corretto e mostrare una pagina di tipo Errore interno del server di tua scelta.
Le eccezioni possono essere trattate in modo molto simile agli errori di base. Invece di trigger_error
verrà generata un'exception dal codice (manualmente con la throw new Exception
o da una chiamata di function standard). Utilizzare set_exception_handler
per definire il callback che si desidera utilizzare per gestire l'exception con.
SPL
La libreria PHP standard (SPL) fornisce eccezioni . Sono il mio modo preferito di sollevare eccezioni perché come trigger_error
sono una parte standard di PHP che non introduce dependencies extra al tuo codice.
cosa fare con loro?
Quando viene lanciata un'exception, ci sono tre scelte che possono essere fatte:
Ad each livello dello stack vengono fatte queste scelte. Alla fine, una volta raggiunto il livello più alto, verrà eseguita la richiamata impostata con set_exception_handler
. Questo è il punto in cui il tuo codice di logging appartiene (per gli stessi motivi della gestione degli errori) piuttosto che diffuso in tutte le dichiarazioni catch
del tuo codice.
Gestore degli errori
function errorHandler($errno , $errstr, $errfile, $errline, $errcontext) { // Perform your error handling here, respecting error_reporting() and // $errno. This is where you can log the errors. The choice of logger // that you use is based on your preference. So long as it implements // the observer pattern you will be able to easily add logging for any // type of output you desire. } $previousErrorHandler = set_error_handler('errorHandler');
Gestore di eccezioni
function exceptionHandler($e) { // Perform your exception handling here. } $previousExceptionHandler = set_exception_handler('exceptionHandler');
Funzione di spegnimento
function shutdownFunction() { $err = error_get_last(); if (!isset($err)) { return; } $handledErrorTypes = arrays( E_USER_ERROR => 'USER ERROR', E_ERROR => 'ERROR', E_PARSE => 'PARSE', E_CORE_ERROR => 'CORE_ERROR', E_CORE_WARNING => 'CORE_WARNING', E_COMPILE_ERROR => 'COMPILE_ERROR', E_COMPILE_WARNING => 'COMPILE_WARNING'); // If our last error wasn't fatal then this must be a normal shutdown. if (!isset($handledErrorTypes[$err['type']])) { return; } if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } // Perform simple logging here. } register_shutdown_function('shutdownFunction');
Errori
// Notices. trigger_error('Disk space is below 20%.', E_USER_NOTICE); trigger_error('Disk space is below 20%.'); // Defaults to E_USER_NOTICE // Warnings. fopen('BAD_ARGS'); // E_WARNING fopen() expects at least 2 parameters, 1 given trigger_error('Warning, this mode could be dangerous', E_USER_WARNING); // Fatal Errors. // This function has not been defined and so a fatal error is generated that // does not reach the custom error handler. this_function_has_not_been_defined(); // Execution does not reach this point. // The following will be received by the custom error handler but is fatal. trigger_error('Error in the code, cannot continue.', E_USER_ERROR); // Execution does not reach this point.
eccezioni
Ognuna delle tre scelte precedenti è elencata qui in modo generico, risolvendola, aggiungendola e facendola esplodere.
1 riparabile:
try { $value = code_that_can_generate_exception(); } catch (Exception $e) { // We decide to emit a notice here (a warning could also be used). trigger_error('We had to use the default value instead of ' . 'code_that_can_generate_exception\'s', E_USER_NOTICE); // Fix the exception. $value = DEFAULT_VALUE; } // Code continues executing happily here.
2 Aggiungi:
Osserva di seguito come il code_that_can_generate_exception()
non conosce il $context
. Il block catch a questo livello ha più informazioni che può aggiungere all'exception se è utile rilanciandolo.
try { $context = 'foo'; $value = code_that_can_generate_exception(); } catch (Exception $e) { // Raise another exception, with extra information and the existing // exception set as the previous exception. throw new Exception('Context: ' . $context, 0, $e); }
3 Lascia che bolla:
// Don't catch it.
È stato chiesto di rendere questa risposta più applicabile a un pubblico più ampio, quindi ecco qui.
Preambolo
La gestione degli errori di solito non è la prima cosa a cui vornetworking pensare quando scrivete un'applicazione; come risultato indiretto si imballa a seconda delle necessità. Tuttavia, non deve costare molto per sfruttare i meccanismi esistenti in PHP.
È un articolo abbastanza lungo, quindi l'ho suddiviso in serie logiche di text.
Errori di trigger
All'interno di PHP ci sono due modi distinti per l'triggerszione degli errori:
imagecreatefromjpeg
non può aprire un file), trigger_error
, Questi sono solitamente printingti sulla tua pagina (a less che display_errors
sia spento o error_reporting
sia zero), che dovrebbe essere standard per le macchine di produzione a less che tu non scriva un codice perfetto come me … in movimento); questi errori possono anche essere catturati, dando uno sguardo a qualsiasi intoppo nel codice, usando set_error_handler
spiegato in seguito.
Eccezioni gettanti
Le eccezioni sono diverse dagli errori in tre modi principali:
Exception
; questo ti permette di catturare e gestire eccezioni specifiche, ma lascia che altri si riempiano lo stack fino a quando non vengono catturati da un altro codice. Vedi anche: http://www.php.net/manual/en/language.exceptions.php Un esempio di lancio di eccezioni viene fornito in seguito.
Gestione degli errori
Catturare e gestire gli errori è piuttosto semplice registrando un gestore di errori, ad esempio:
function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, arrays $errcontext = arrays()) { // $errcontext is very powerful, it gives you the variable state at the point of error; this can be a pretty big variable in certain cases, but it may be extremely valuable for debugging // if error_reporting() returns 0, it means the error control operator was used (@) printf("%s [%d] occurred in %s:%d\n%s\n", $errstr, $errno, $errfile, $errline, print_r($errcontext, true)); // if necessary, you can retrieve the stack trace that led up to the error by calling debug_backtrace() // if you return false here, the standard PHP error reporting is performsd } set_error_handler('my_error_handler');
Per i calci, puoi trasformare tutti gli errori in ErrorException
e registrando il seguente gestore degli errori (PHP> = 5.1):
function exception_error_handler($errno, $errstr, $errfile, $errline) { throw new ErrorException($errstr, $errno, 0, $errfile, $errline); } set_error_handler("exception_error_handler");
Gestire le eccezioni
Nella maggior parte dei casi, gestisci le eccezioni il più vicino ansible al codice che ha causato la concessione di piani di backup. Ad esempio, si tenta di inserire un record del database e viene generata un'exception del vincolo della chiave primaria; è ansible ripristinare aggiornando il record (progettato come la maggior parte dei database in grado di gestirlo da soli). Alcune eccezioni non possono essere gestite localmente, quindi devi farle cadere a cascata. Esempio:
function insertRecord($user, $name) { try { if (true) { throw new Exception('This exception should not be handled here'); } // this code is not executed $this->db->insert('users', arrays('uid' => $user, 'name' => $name)); } catch (PDOException $e) { // attempt to fix; an exception thrown here will cascade down throw $e; // rethrow exception // since PHP 5.3.0 you can also nest exceptions throw new Exception("Could not insert '$name'", -1, $e); } catch (WhatEverException $e) { // guess what, we can handle whatever too } }
L'exception scivolosa
Quindi cosa succede quando non si rileva un'exception da nessuna parte? Puoi prenderlo anche usando set_exception_handler
.
function my_exception_handler(Exception $exception) { // do your stuff here, just don't throw another exception here } set_exception_handler('my_exception_handler');
Questo non è incoraggiato a less che tu non abbia modo significativo di gestire l'exception in qualsiasi parte del tuo codice.
Registrazione dell'errore / exception
Ora che stai gestendo l'errore devi registrarlo da qualche parte. Per il mio esempio, utilizzo un progetto che Apache ha portto da Java a PHP, chiamato LOG4PHP . Ce ne sono altri, ma illustra l'importnza di una struttura di logging flessibile.
Usa i seguenti concetti:
Utilizzo di base per illustrare diversi livelli di messaggi:
Logger::getLogger('main')->info('We have lift off'); Logger::getLogger('main')->warn('Rocket is a bit hot'); Logger::getLogger('main')->error('Houston, we have a problem');
Usando questi concetti è ansible modellare una struttura di logging piuttosto potente; ad esempio, senza modificare il codice precedente, è ansible implementare la seguente configuration:
Definirlo, quindi usarlo 🙂
define('ERRORLOG_PATH', '/var/tmp/my-errors.log'); error_log($e->getMessage(), 3, ERRORLOG_PATH);
In alternativa, basta rendere opzionale il terzo parametro di error_log
, error_log
default sul path desiderato.
Se hai ancora bisogno di un modo personalizzato di gestire i registri (es. Non vuoi usare il trigger_error standard ()), ti consiglio di consultare Zend_Log (http://framework.zend.com/manual/en/zend.log .overview.html) per questi motivi:
questo può essere usato come componente indipendente, ZF non è un framework full-stack. Puoi copiare solo Zend_Loader e Zend_Log namespace, istanziare Zend_Loader e usarlo. Vedi sotto:
require_once('Zend/Loader/Autoloader.php'); $loader = Zend_Loader_Autoloader::getInstance(); $logger = new Zend_Log(); $writer = new Zend_Log_Writer_Stream('php://output'); $logger->addWriter($writer); $logger->log('Informational message', Zend_Log::INFO);
Vi sono state offerte molte librerie di logging, ma credo che il team di Zend (fondatori di PHP lang) sappia cosa fanno
potrebbe cambiare il formato del registro (ma quello che è out-of-box è fantastico per la mia mente). L'esempio sopra con formattatore standard produrrà qualcosa di simile a questo:
2012-05-07T23: 57: 23 + 03: 00 INFO (6): messaggio informativo
Come aggiunta, per la logging degli errori (e di fatto per tutti i log) userei il dispatcher di events, come fa il framework symfony.
Dai un'occhiata a questo componente sf (la sua dipendenza molto leggera, l'integer framework non è richiesto, ci sono forse 3 classi php rilevanti e 2 interfacce)
https://github.com/symfony/EventDispatcher
in questo modo puoi creare il dispatcher da qualche parte nel bootstrap dell'applicazione:
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\Event; $dispatcher = new EventDispatcher(); //register listeners $dispatcher->addListener('application.log', function (Event $event) { //do anything you want });
Quindi puoi generare un evento in qualsiasi posto del tuo codice da qualcosa di simile
$dispatcher->dispatch(new GenericEvent('application.log', arrays('message' => 'some log', 'priority' => 'high'));
Ovviamente è ansible suddividere la class di events con i propri events:
class LogEvent extends GenericEvent { public function __construct($message, $priority = 'INFO') { parent::__construct('application.log', arrays('message'=>$message,'priority'=>$priority)); } public function getMessage() { return $this->getArgument('message'); } public function getPriority() { return $this->getArgument('priority'); } } // now raising LogEvent is much cleaner: $dispatcher->dispatch(new LogEvent('some log'));
Ciò consentirà anche di creare events personalizzati come ExceptionEvent
class ExceptionEvent extends GenericEvent { public function __construct(Exception $cause) { parent::__construct('exception.event', arrays('cause' => $cause)); } }
E gestirli di conseguenza.
Come già affermato da qualcuno, consiglierei di andare con la libreria pronta all'uso, come menzionato Monolog, Zend_Log o log4php, non c'è probabilmente alcun motivo per codificare queste cose a mano (e l'ultima cosa che vuoi è un errore logger !)
PS: Tratta gli snippet di codice come pseudo-codice, non li ho testati. I dettagli possono essere trovati nei documenti delle biblioteche menzionate.
Se la modalità di gestione degli errori di PHP non è abbastanza flessibile per te (ad esempio a volte vuoi accedere al database, a volte al file, a volte qualsiasi altra cosa), devi usare / creare un framework di logging PHP personalizzato.
Puoi sfogliare la discussione in https://stackoverflow.com/questions/341154/php-logging-framework o semplicemente andare e dare la scelta migliore, KLogger , una prova. Non sono sicuro, tuttavia, se support le destinazioni personalizzate per la logging. Ma per lo less, è una lezione piccola e di facile lettura e dovresti essere in grado di estenderla ulteriormente per le tue esigenze.
Vado con la soluzione di logging di Tom vand der Woerdt, la più semplice e la più efficace per le tue esigenze.
Per quanto riguarda l'altra domanda:
Non è necessario catturare / rilanciare l'exception all'interno della function a less che non ci sia un tipo specifico di exception per cui si ha una soluzione.
Un esempio un po 'semplicistico:
define('ERRORLOG_PATH', '/var/tmp/my-errors.log'); function do_something($in) { if (is_good($in)) { try { return get_data($in); } catch (NoDataException $e) { // Since it's not too big a deal that nothing // was found, we just return false. return false; } } else { throw new InvalidArguementException('$in is not good'); } } function get_data($data) { if (!is_int($data)) { InvalidArguementException('No'); } $get = //do some getting. if (!$get) { throw new NoDataException('No data was found.'); } else { return $get; } } try { do_something('value'); } catch (Exception $e) { error_log($e->getMessage(), 3, ERRORLOG_PATH); die ('Something went wrong :('); }
Qui prendi solo NoDataException
perché hai qualche altra logica per risolverlo, tutti gli altri errori ricadono sul primo catch e vengono gestiti dal catch superiore perché tutte le eccezioni generate devono a un certo punto della loro gerarchia ereditare da Exception
.
Ovviamente se si lancia nuovamente Exception
(al di fuori della try {}
iniziale try {}
o nella parte superiore della catch {}
), lo script verrà chiuso con un errore Eccezione non rilevata e la logging degli errori viene persa.
Se si voleva andare fino in fondo, si potrebbe anche implementare una function di gestione degli errori personalizzata usando set_error_handler()
e inserire anche la logging.
Ci sono due sfide da affrontare. Il primo è quello di essere flessibili nel logging su diversi canali. In questo caso dovresti dare un'occhiata ad esempio a Monolog .
La seconda sfida consiste nell'intrecciare il log nella tua applicazione. Imho il caso migliore è no per usare la logging in modo esplicito. Qui per esempio l' orientamento dell'aspetto è utile. Un buon campione è flow3 .
Ma questa è più una visione a volo d'uccello sul problema …
Uso la mia function che mi consente di scrivere più tipi di file di registro impostando o modificando il secondo parametro.
Supero le domande concettuali che mi stai ponendo riguardo a "qual è la strada giusta" per farlo, includendo la function di registro in una libreria di funzioni che considero "native" per i miei progetti di sviluppo. In questo modo posso considerare queste funzioni come parte del php "MY", come date()
o time()
In questa versione di base di dlog, gestisco anche gli arrays. mentre inizialmente lo usavo per registrare gli errori, ho finito per usarlo per altri tracciati a breve termine "veloci e sporchi", come la logging delle volte in cui il codice è entrato in una determinata sezione e gli accessi degli utenti, ecc.
function dlog($message,$type="php-dlog") { if(!is_arrays($message) ) $message=trim($message); error_log(date("m/d/Y h:i:s").":".print_r($message,true)."\n",3, "/data/web/logs/$_SERVER[HTTP_HOST]-$type.log"); }
La maggior parte dei logger di errore e dei logger di eccezioni sono inutili per la maggior parte delle persone perché non hanno accesso ai file di registro.
Preferisco usare un gestore di errori personalizzato e un gestore di eccezioni personalizzato e avere, durante la produzione, errori di registro direttamente nel database se il sistema è in esecuzione su un database.
Durante lo sviluppo, quando vengono impostati display_errors, non registrano nulla poiché tutti gli errori vengono generati nel browser.
E come nota a margine: non fare in modo che il tuo gestore di errori personalizzato generi eccezioni! È una pessima idea Può causare errori nel gestore buffer e in alcune estensioni. Inoltre alcune funzioni PHP di base come fopen () causano un avviso o un avviso in caso di errore, queste dovrebbero essere gestite di conseguenza e non dovrebbero arrestare l'applicazione con un'exception.
La menzione di avere il gestore degli errori che genera eccezioni nella documentazione di PHP è un bug di note.
Come afferma KNL, che è giusto, ma sfortunatamente non ancora documentato, l'errore di lanciare eccezioni non è raccomandato dagli sviluppatori PHP e qualcuno ha fatto un errore nella documentazione. Può infatti causare bug con molte estensioni quindi non farlo.
Questo è già stato discusso su #PHP su irc.
"Tuttavia, gli errori possono essere semplicemente tradotti in eccezioni con ErrorException." su http://php.net/manual/en/language.exceptions.php verrà rimosso.