Je lisais en particulier sur «l'enregistrement des erreurs». Et j'ai mis au point la fonction «error_log», qui semble être un bon outil à utiliser pour gérer l'enregistrement des erreurs. Mais comment est la meilleure et la plus douce façon de l’utiliser?
Si j'ai un
try {
//try a database connection...
} catch (PDOException $e) {
error_log($e->getMessage(), 3, "/var/tmp/my-errors.log");
}
Cela consignerait l'erreur dans le fichier my-errors.log. Mais que se passe-t-il si j’ai parfois besoin de changer la position du fichier, un nouveau dossier, ou quelque chose du genre? Si j'ai des tonnes de fichiers, je dois tous les changer.
Maintenant, j'ai commencé à penser à utiliser une variable pour définir le chemin d'accès au journal des erreurs. Bien sûr, cela pourrait fonctionner, mais que faire si je veux utiliser error_log dans une méthode ou une méthode de classe? Ensuite, je devrais définir la variable comme globale, mais cela est considéré comme une mauvaise pratique! Mais que faire si je ne devais pas utiliser la fonction en profondeur dans une classe, ne serait-ce pas aussi considéré comme une mauvaise pratique? Quelle est une bonne solution ici?
<?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
}
Ceci est un exemple de ce dont je parlais ci-dessus (ne pas avoir la journalisation en profondeur dans une classe/fonction ... Est-ce un bon moyen?)
Plus loin:
Je ne sais pas trop comment utiliser les exceptions en général. Supposons que je souhaite effectuer une insertion dans une base de données avec SQL à l'intérieur d'une méthode. Est-ce que j'utiliserais un essai/un accrochage, puis une nouvelle exception si elle échoue? Est-ce considéré comme une bonne pratique? Des exemples s'il vous plaît.
Il a été demandé de rendre cette réponse plus applicable à un public plus large, alors allez-y.
Préambule
La gestion des erreurs n’est généralement pas la première chose à laquelle vous voudrez penser lors de l’écriture d’une application; il en résulte indirectement que le besoin s'en fait sentir. Cependant, exploiter les mécanismes existants dans PHP ne coûte pas cher non plus.
C'est un article assez long, je l'ai donc divisé en ensembles logiques de texte.
Erreurs de déclenchement
Dans PHP, les erreurs peuvent être déclenchées de deux manières différentes:
imagecreatefromjpeg
n'a pas pu ouvrir un fichier),trigger_error
,Celles-ci sont généralement imprimées sur votre page (sauf si display_errors
est désactivé ou error_reporting
vaut zéro), ce qui devrait être standard pour les machines de production, à moins que vous n'écriviez un code parfait comme moi ... à l'avenir. ces erreurs peuvent également être capturées, vous donnant un aperçu de tout accroc dans le code, en utilisant set_error_handler
expliqué plus tard.
Lancer des exceptions
Les exceptions sont différentes des erreurs de trois manières principales:
Exception
; Cela vous permet de capturer et de gérer des exceptions spécifiques tout en laissant les autres se disperser dans la pile jusqu'à ce qu'ils soient capturés par un autre code. Voir aussi: http://www.php.net/manual/fr/language.exceptions.phpUn exemple d'exceptions de lancement est donné plus tard.
Gestion des erreurs
La capture et la gestion des erreurs sont assez simples en enregistrant un gestionnaire d’erreurs, par exemple:
function my_error_handler($errno, $errstr, $errfile = 'unknown', $errline = 0, array $errcontext = array())
{
// $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 performed
}
set_error_handler('my_error_handler');
Pour démarrer, vous pouvez transformer toutes les erreurs en ErrorException
en enregistrant le gestionnaire d'erreurs suivant (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");
Gestion des exceptions
Dans la plupart des cas, vous gérez les exceptions le plus près possible du code qui l'a amené à autoriser les plans de sauvegarde. Par exemple, vous essayez d'insérer un enregistrement de base de données et une exception de contrainte de clé primaire est levée; vous pouvez récupérer en mettant à jour l’enregistrement (ce qui est conçu comme la plupart des bases de données peuvent gérer cela tout seul). Certaines exceptions ne peuvent tout simplement pas être gérées localement, vous voulez donc que celles-ci soient transférées en cascade. Exemple:
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', array('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 glissante
Alors que se passe-t-il lorsque vous ne détectez pas d'exception nulle part? Vous pouvez aussi comprendre cela en utilisant 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');
Cela n'est pas recommandé, à moins que vous n'ayez aucun moyen significatif de gérer l'exception dans votre code.
Consignation de l'erreur/exception
Maintenant que vous gérez l'erreur, vous devez la consigner quelque part. Pour mon exemple, j'utilise un projet porté par Apache de Java à PHP, appelé LOG4PHP . Il y en a d'autres, mais cela illustre l'importance d'une installation de journalisation flexible.
Il utilise les concepts suivants:
Utilisation de base pour illustrer différents niveaux de message:
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');
En utilisant ces concepts, vous pouvez modéliser une installation de journalisation assez puissante. Par exemple, sans changer le code ci-dessus, vous pouvez implémenter la configuration suivante:
En outre, pour la journalisation des erreurs (et en fait pour toutes les journalisations), j'utiliserais le répartiteur d'événements, de la même manière que le framework Symfony.
Jetez un coup d'oeil à ce composant sf (sa dépendance très légère, toute la structure n'est pas nécessaire, il y a peut-être 3 classes php pertinentes et 2 interfaces)
https://github.com/symfony/EventDispatcher
de cette façon, vous pouvez créer un répartiteur quelque part dans votre application bootstrap:
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
});
Ensuite, vous pouvez soulever un événement à n’importe quel endroit de votre code, par exemple:
$dispatcher->dispatch(new GenericEvent('application.log', array('message' => 'some log', 'priority' => 'high'));
Bien entendu, vous pouvez sous-classer une classe d'événements avec vos propres événements:
class LogEvent extends GenericEvent {
public function __construct($message, $priority = 'INFO') {
parent::__construct('application.log', array('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'));
Cela vous permettra également de créer des événements plus personnalisés, tels que ExceptionEvent.
class ExceptionEvent extends GenericEvent {
public function __construct(Exception $cause) {
parent::__construct('exception.event', array('cause' => $cause));
}
}
Et manipulez-les en conséquence.
Comme quelqu'un l'a déjà dit, je conseillerais d'utiliser une bibliothèque prête à l'emploi, comme Monolog, Zend_Log ou log4php, il n'y a probablement aucune raison de coder ces choses à la main (et la dernière chose que vous voulez est un enregistreur d'erreurs cassé !)
PS: Traitez les extraits de code comme des pseudo-codes, je ne les ai pas testés. Les détails peuvent être trouvés dans la documentation des bibliothèques mentionnées.
Définissez-le, puis utilisez-le :)
define('ERRORLOG_PATH', '/var/tmp/my-errors.log');
error_log($e->getMessage(), 3, ERRORLOG_PATH);
Alternativement, il suffit de rendre le troisième paramètre de error_log
facultatif, en le configurant par défaut sur le chemin souhaité.
Si vous avez toujours besoin d'une méthode personnalisée de gestion des journaux (par exemple, vous ne souhaitez pas utiliser la fonction trigger_error()
standard), je vous conseillerais de consulter Zend_Log ( http://framework.zend.com/manual/en/zend.log .overview.html ) pour ces raisons:
ceci peut être utilisé en tant que composant autonome, ZF n'est pas une infrastructure complète. Vous ne pouvez copier que les espaces de noms Zend_Loader et Zend_Log, instancier Zend_Loader et l'utiliser. Voir ci-dessous:
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);
De nombreuses bibliothèques de journalisation vous ont été proposées, mais je pense que l'équipe de Zend (fondateurs de PHP lang) sait ce qu'elle fait.
2012-05-07T23: 57: 23 + 03: 00 INFO (6): Message d'information
Comme le dit KNL, ce qui est tout à fait correct, mais malheureusement non documenté, avoir des erreurs lors du lancement d’exceptions n’est pas recommandé par les développeurs de PHP et que quelqu'un ait commis une erreur dans la documentation. Cela peut en effet causer des bugs avec de nombreuses extensions, alors ne le faites pas.
Cela a déjà été débattu sur #PHP sur irc.
Le "Cependant, les erreurs peuvent être simplement traduites en exceptions avec ErrorException." sur http://php.net/manual/en/language.exceptions.php va être supprimé.
Je choisirais la solution de journalisation Tom vand der Woerdt, la plus simple et la plus efficace pour vos besoins.
Quant à l'autre question:
Vous n'avez pas besoin d'attraper/de rediffuser l'exception à l'intérieur de la fonction, sauf s'il existe un type d'exception spécifique pour lequel vous avez une solution.
Exemple quelque peu simpliste:
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 :(');
}
Ici, vous ne récupéreriez que la variable NoDataException
car toutes les autres erreurs relèvent de la première et restaient traitées par la logique supérieure, car toutes les exceptions levées devaient hériter de la variable Exception
.
Évidemment, si vous lancez à nouveau une Exception
(en dehors du try {}
initial ou du catch {}
supérieur), votre script se terminera avec une erreur d'exception non capturée et la journalisation des erreurs sera perdue.
Si vous voulez aller jusqu'au bout, vous pouvez également implémenter une fonction de gestion des erreurs personnalisée à l'aide de set_error_handler()
et y mettre également votre connexion.
J'utilise ma propre fonction qui me permet d'écrire plusieurs types de fichiers journaux en définissant ou en modifiant le deuxième paramètre.
Je dépasse les questions conceptuelles que vous posez sur "quelle est la bonne manière" de le faire, en incluant la fonction log dans une bibliothèque de fonctions que je considère "natives" dans mes projets de développement ... De cette façon, je peux envisager ces fonctions font simplement partie de "MON" noyau php, comme date()
ou time()
Dans cette version de base de dlog, je gère également les tableaux. alors que je l’utilisais à l’origine pour consigner les erreurs, j’ai fini par l’utiliser pour d’autres suivis à court terme «rapides et sales», tels que la consignation des heures de saisie du code dans une section donnée, la connexion des utilisateurs, etc.
function dlog($message,$type="php-dlog")
{
if(!is_array($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");
}
Si le moyen de gérer les erreurs PHP n'est pas assez souple pour vous (par exemple, vous souhaitez parfois vous connecter à une base de données, parfois à un fichier, parfois autre chose), vous devez utiliser/créer une journalisation PHP personnalisée. cadre.
Vous pouvez parcourir la discussion dans https://stackoverflow.com/questions/341154/php-logging-framework ou tout simplement et donner le premier choix, KLogger , essayez. Je ne suis toutefois pas sûr s'il prend en charge les destinations personnalisées pour la journalisation. Mais à tout le moins, il s’agit d’un petit groupe facile à lire et vous devriez pouvoir l’étendre davantage pour répondre à vos propres besoins.
Il y a deux défis à relever. La première consiste à faire preuve de souplesse lors de la connexion à différents canaux. Dans ce cas, vous devriez par exemple regarder Monolog .
Le deuxième défi consiste à intégrer cette connexion à votre application. À mon avis, le mieux est de ne pas utiliser explicitement la journalisation. Ici, par exemple l'orientation de l'aspect est très pratique. Un bon échantillon est flow3 .
Mais il s’agit plus d’une vue à vol d'oiseau sur le problème ...
La plupart des enregistreurs d'erreur et des enregistreurs d'exceptions sont inutiles pour la plupart des utilisateurs car ils n'ont pas accès aux fichiers journaux.
Je préfère utiliser un gestionnaire d'erreurs personnalisé et un gestionnaire d'exceptions personnalisé et que ceux-ci, lors de la production, enregistrent les erreurs directement dans la base de données si le système s'exécute sur une base de données.
Pendant le développement, lorsque display_errors est défini, il ne consigne rien car toutes les erreurs sont soulevées dans le navigateur.
Et en guise de remarque: ne faites pas de votre gestionnaire d'erreurs personnalisé une exception! C'est une très mauvaise idée. Cela peut causer des bugs dans le gestionnaire de mémoire tampon et dans certaines extensions. De plus, certaines fonctions PHP principales telles que fopen () provoquent un avertissement ou un avis d'échec; elles doivent être traitées en conséquence et ne doivent pas arrêter l'application si une exception le ferait.
La mention d'avoir le gestionnaire d'erreurs levant des exceptions dans la documentation de PHP est un bogue de note.