Architecture multicouche: où dois-je implémenter la journalisation et la gestion des erreurs?

17

Je suis actuellement en train de refactoriser un grand sous-système avec une architecture multi-couches, et j'ai du mal à concevoir une stratégie efficace de journalisation et de gestion des erreurs.

Disons que mon architecture se compose des trois couches suivantes:

  • Interface publique (IE un contrôleur MVC)
  • Couche de domaine
  • Couche d'accès aux données

Ma source de confusion est l'endroit où je dois implémenter la journalisation des erreurs \ la gestion:

  1. La solution la plus simple serait d'implémenter la journalisation au niveau supérieur (IE l'interface publique \ contrôleur MVC). Cependant, cela ne semble pas correct, car cela signifie faire remonter l'exception à travers les différentes couches, puis la journaliser; plutôt que d'enregistrer l'exception à sa source.

  2. Consigner l'exception à sa source est évidemment la meilleure solution car j'ai le plus d'informations. Mon problème avec ceci est que je ne peux pas attraper toutes les exceptions à la source sans attraper TOUTES les exceptions, et dans la couche domaine / interface publique, cela conduira à intercepter les exceptions qui ont déjà été interceptées, enregistrées et relancées par la couche ci-dessous .

  3. Une autre stratégie possible est un mélange de # 1 et # 2; par lequel j'attrape des exceptions spécifiques au niveau de la couche qu'elles sont les plus susceptibles d'être levées (IE Catching, logging et re-throwing SqlExceptionsdans la couche d'accès aux données), puis j'enregistre toute autre exception non capturée au niveau supérieur. Cependant, cela nécessiterait également que j'attrape et reconnecte chaque exception au niveau supérieur, car je ne peux pas distinguer les erreurs qui ont déjà été enregistrées \ traitées de celles qui ne l'ont pas été.

Maintenant, évidemment, c'est un problème dans la plupart des applications logicielles, donc il doit y avoir une solution standard à ce problème qui se traduit par des exceptions interceptées à la source et enregistrées une fois; mais je ne vois pas comment faire cela moi-même.

Remarque, le titre de cette question est très similaire à " Journalisation des exceptions dans une application à plusieurs niveaux" ", mais les réponses dans ce message manquent de détails et ne sont pas suffisantes pour répondre à ma question.

KidCode
la source
1
Une approche que j'utilise parfois consiste à créer ma propre sous-classe d'exception (ou RuntimeException) et à la lever, y compris l'exception d'origine en tant que exception imbriquée. Bien sûr, cela signifie que lors de la capture d'exceptions à des niveaux supérieurs, je dois vérifier le type de l'exception (mes propres exceptions sont simplement renvoyées, d'autres exceptions sont enregistrées et incluses dans de nouvelles instances de mes exceptions). Mais je travaille en solo depuis longtemps donc je ne peux pas donner de conseil "officiel".
SJuan76
4
The easiest solution would be to implement the logging at the top level- fais ça. La journalisation des exceptions à leur source n'est pas une bonne idée et chaque application que j'ai rencontrée qui fait cela était un PITA à déboguer. Il devrait être de la responsabilité de l'appelant de gérer les exceptions.
Justin
Vous semblez avoir en tête une technique / un langage d'implémentation spécifique, sinon votre déclaration "les exceptions qui ont été enregistrées ne peuvent pas être distinguées de celles qui ne l'ont pas été" est difficile à comprendre. Pouvez-vous donner plus de contexte?
Vroomfondel
@Vroomfondel - Vous avez raison. J'utilise C #, et encapsuler le code dans chaque couche try{ ... } catch(Exception ex) { Log(ex); }entraînerait la même exception enregistrée dans chaque couche. (Cela semble également être une mauvaise pratique d'attraper chaque exception à chaque couche de la base de code.)
KidCode

Réponses:

18

A vos questions:

La solution la plus simple serait d'implémenter la journalisation au niveau supérieur

Avoir la bulle d'exception jusqu'au niveau supérieur est une approche absolument correcte et plausible. Aucune des méthodes de couche supérieure n'essaie de poursuivre un processus après l'échec, qui ne peut généralement pas réussir. Et une exception bien équipée contient toutes les informations nécessaires à la journalisation. Et ne rien faire des exceptions vous aide à garder votre code propre et concentré sur la tâche principale au lieu des échecs.

Consigner l'exception à sa source est évidemment la meilleure solution car j'ai le plus d'informations.

C'est à moitié correct. Oui, les informations les plus utiles y sont disponibles. Mais je recommanderais de mettre tout cela dans l'objet d'exception (s'il n'est pas déjà là) au lieu de le journaliser immédiatement. Si vous vous connectez à un bas niveau, vous devez toujours lever une exception pour dire à vos appelants que vous n'avez pas terminé votre travail. Cela se retrouve dans plusieurs journaux du même événement.

Des exceptions

Ma principale directive est de capturer et de consigner les exceptions au niveau supérieur uniquement. Et toutes les couches ci-dessous doivent s'assurer que toutes les informations de défaillance nécessaires sont transportées au niveau supérieur. Dans une application à un seul processus, par exemple en Java, cela signifie principalement de ne pas essayer / intercepter ou se connecter du tout en dehors du niveau supérieur.

Parfois, vous voulez que certaines informations de contexte soient incluses dans le journal des exceptions qui ne sont pas disponibles dans l'exception d'origine, par exemple l'instruction SQL et les paramètres qui ont été exécutés lorsque l'exception a été levée. Ensuite, vous pouvez intercepter l'exception d'origine et en renvoyer une nouvelle, contenant celle d'origine plus le contexte.

Bien sûr, la vraie vie interfère parfois:

  • En Java, vous devez parfois intercepter une exception et l'encapsuler dans un type d'exception différent juste pour obéir à certaines signatures de méthode fixes. Mais si vous relancez une exception, assurez-vous que celle-ci contient toutes les informations nécessaires pour une journalisation ultérieure.

  • Si vous traversez une frontière inter-processus, vous ne pouvez souvent techniquement pas transférer l'objet d'exception complet, y compris la trace de pile. Et bien sûr, la connexion pourrait se perdre. Voici donc un point où un service doit consigner les exceptions, puis faire de son mieux pour transmettre autant d'informations que possible sur la ligne à son client. Le service doit s'assurer que le client reçoit un avis d'échec, soit en recevant une réponse d'échec, soit en exécutant un délai d'expiration en cas de connexion interrompue. Cela entraînera généralement le même échec à se connecter deux fois, une fois à l'intérieur du service (avec plus de détails) et une fois au niveau supérieur du client.

Enregistrement

J'ajoute quelques phrases sur la journalisation en général, pas seulement sur la journalisation des exceptions.

Outre les situations exceptionnelles, vous souhaitez que les activités importantes de votre application soient également enregistrées dans le journal. Utilisez donc un cadre de journalisation.

Faites attention aux niveaux de journalisation (la lecture de journaux où les informations de débogage et les erreurs graves ne sont pas signalées différemment est une douleur!). Les niveaux de journal typiques sont:

  • ERREUR: une fonction a échoué de manière irrémédiable. Cela ne signifie pas nécessairement que tout votre programme s'est écrasé, mais certaines tâches n'ont pas pu être terminées. En règle générale, vous disposez d'un objet d'exception décrivant l'échec.
  • AVERTISSEMENT: quelque chose d'étrange s'est produit, mais n'a pas provoqué d'échec de la tâche (configuration étrange détectée, panne de connexion temporaire provoquant des tentatives, etc.)
  • INFO: Vous souhaitez communiquer une action significative du programme à l'administrateur système local (démarrer un service avec sa configuration et sa version logicielle, importer des fichiers de données dans la base de données, les utilisateurs se connectant au système, vous avez l'idée ...).
  • DEBUG: ce que vous, en tant que développeur, souhaitez voir lorsque vous déboguez un problème (mais vous ne saurez jamais à l'avance ce dont vous avez vraiment besoin en cas de tel ou tel bogue spécifique - si vous pouvez le prévoir, vous le corrigerez ). Une chose qui est toujours utile est de consigner les activités sur des interfaces externes.

En production, définissez le niveau de journalisation sur INFO. Les résultats devraient être utiles à un administrateur système afin qu'il sache ce qui se passe. Attendez-vous à ce qu'il vous appelle pour obtenir de l'aide ou corriger des bogues pour chaque ERREUR dans le journal et la moitié des AVERTISSEMENTS.

Activez le niveau DEBUG uniquement pendant les sessions de débogage réelles.

Regroupez les entrées de journal dans les catégories appropriées (par exemple, par le nom de classe complet du code qui génère l'entrée), ce qui vous permet d'activer les journaux de débogage pour des parties spécifiques de votre programme.

Ralf Kleberhoff
la source
Merci pour cette réponse détaillée, j'apprécie vraiment la partie de journalisation aussi.
KidCode
-1

Je me prépare pour les downvotes, mais je vais sortir sur un membre et dire que je ne suis pas sûr d'être d'accord avec cela.

Faire bouillir les exceptions, et encore moins les enregistrer à nouveau, est un effort supplémentaire avec peu d'avantages. Attrapez l'exception à la source (oui, le plus simple), enregistrez-la, mais ne relancez pas l'exception, signalez simplement une "erreur" à l'appelant. "-1", chaîne vide, vide, une énumération, peu importe. L'appelant a seulement besoin de savoir que l'appel a échoué, presque jamais les détails horribles. Et ceux-ci seront dans votre journal, non? Dans les rares cas où l'appelant a besoin des détails, allez-y et faites des bulles, mais pas par défaut irréfléchi automatique.

InconditionallyReinstateMonica
la source
3
Le problème avec le rapport de l'erreur par les valeurs de retour est: 1. Si l'appelant se soucie des échecs, il doit vérifier la valeur spéciale. Mais attendez, est-ce nullou la chaîne vide? Est-ce -1 ou un nombre négatif? 2. Si l'appelant s'en fiche (c.-à-d. Ne vérifie pas), cela entraîne des erreurs de suivi sans rapport avec la cause d'origine, par exemple a NullPointerException. Ou pire: le calcul continue avec des valeurs erronées. 3. Si l'appelant s'en soucie mais que le programmeur ne pense pas que cette méthode échoue, le compilateur ne le lui rappelle pas. Les exceptions n'ont pas ce problème, soit vous attrapez, soit vous relancez.
siegi
1
Désolé, j'ai dû déprécier cela. L'approche que vous décrivez (remplacer les exceptions par des valeurs de retour spéciales) était à la pointe de la technologie dans les années 1970, lorsque le langage C est originaire, et il existe de nombreuses bonnes raisons pour lesquelles les langues modernes ont des exceptions, et pour moi la principale est que l'utilisation correcte des exceptions facilite grandement l'écriture de code robuste. Et votre "Bubbling exceptions up [...] is effort supplémentaire [...]" "est tout à fait faux: ne faites rien pour les exceptions, et elles se multiplieront toutes seules.
Ralf Kleberhoff