Un modèle commun pour localiser un bogue suit ce script:
- Observez l'étrangeté, par exemple, pas de sortie ou un programme suspendu.
- Recherchez le message pertinent dans le journal ou la sortie du programme, par exemple, "Impossible de trouver Foo". (Ce qui suit n'est pertinent que s'il s'agit du chemin emprunté pour localiser le bogue. Si une trace de pile ou d'autres informations de débogage sont facilement disponibles, c'est une autre histoire.)
- Localisez le code où le message est imprimé.
- Déboguer le code entre le premier endroit où Foo entre (ou devrait entrer) l’image et où le message est imprimé.
Cette troisième étape est celle où le processus de débogage s'arrête souvent car il y a de nombreux endroits dans le code où "Impossible de trouver Foo" (ou une chaîne basée sur un modèle Could not find {name}
) est imprimé. En fait, plusieurs fois une faute d’orthographe m’a aidé à trouver l’emplacement réel beaucoup plus rapidement que je n’aurais pu le faire autrement: le message était unique dans l’ensemble du système et souvent dans le monde entier, ce qui entraînait immédiatement l’apparition d’un moteur de recherche pertinent.
La conclusion évidente est que nous devrions utiliser des identifiants de message uniques dans le code, en les codant en tant que partie intégrante de la chaîne de message et en vérifiant éventuellement qu'il n'y ait qu'une seule occurrence de chaque identifiant dans la base de code. En termes de facilité de maintenance, quels sont les avantages et les inconvénients les plus importants de cette approche pour cette communauté, et comment l’appliqueriez-vous ou garantiriez-vous qu’elle ne devienne jamais nécessaire (en supposant que le logiciel contiendra toujours des bogues)?
Réponses:
Globalement, cette stratégie est valable et valable. Voici quelques réflexions.
Cette stratégie est également appelée "télémétrie" dans le sens où, une fois combinées, ces informations permettent de "trianguler" la trace d'exécution et permettent à un dépanneur de comprendre ce que l'utilisateur / l'application tente d'accomplir et ce qui s'est réellement passé. .
Certaines données essentielles à collecter (que nous connaissons tous) sont:
Souvent, les approches de journalisation traditionnelles ne donnent pas les résultats escomptés en raison de l'impossibilité de retracer un message de journal de bas niveau dans la commande de niveau supérieur qui le déclenche. Une trace de pile ne capture que les noms des fonctions les plus récentes qui ont aidé à gérer la commande de niveau supérieur, pas les détails (données) parfois nécessaires pour caractériser cette commande.
Normalement, les logiciels n'étaient pas conçus pour implémenter ce type d'exigences de traçabilité. Cela rend plus difficile la corrélation du message de bas niveau avec la commande de haut niveau. Le problème est particulièrement grave dans les systèmes multi-threadés, où de nombreuses requêtes et réponses peuvent se chevaucher et où le traitement peut être déchargé sur un thread différent du thread d'origine.
Ainsi, pour tirer le meilleur parti de la télémétrie, il sera nécessaire de modifier l’architecture logicielle globale. La plupart des interfaces et des appels de fonction devront être modifiés pour accepter et propager un argument "traceur".
Même les fonctions utilitaires devront ajouter un argument "traqueur", de sorte que, en cas d'échec, le message de journal se permette d'être mis en corrélation avec une certaine commande de haut niveau.
Un autre échec rendant le traçage de télémétrie difficile est le manque de références d’objet (pointeurs ou références nuls). Lorsque certaines données cruciales sont manquantes, il peut être impossible de signaler quoi que ce soit d’utile pour l’échec.
En termes d'écriture des messages du journal:
la source
Imaginez que vous ayez une fonction utilitaire triviale utilisée à des centaines d’endroits de votre code:
Si nous devions faire ce que vous suggérez, nous pourrions écrire
Une erreur qui pourrait se produire est si l'entrée était zéro; cela donnerait lieu à une exception par division.
Supposons donc que vous voyez 27349262 dans votre sortie ou dans vos journaux. Où cherchez-vous le code qui a passé la valeur zéro? Rappelez-vous que la fonction - avec son ID unique - est utilisée dans des centaines d'endroits. Ainsi, bien que vous sachiez que la division par zéro a eu lieu, vous ne savez pas à qui
0
elle appartient.Il me semble que si vous ne voulez pas enregistrer les ID de message, vous pouvez également enregistrer la trace de la pile.
Si la verbosité de la trace de la pile vous dérange, vous n'avez pas besoin de la vider sous forme de chaîne de la façon dont le moteur d'exécution vous la donne. Vous pouvez le personnaliser. Par exemple, si vous voulez une trace de pile abrégée allant uniquement aux
n
niveaux, vous pouvez écrire quelque chose comme ceci (si vous utilisez c #):Et utilisez-le comme ceci:
Sortie:
Peut-être plus facile que de conserver les identifiants de message et plus flexible.
Voler mon code de DotNetFiddle
la source
SAP NetWeaver le fait depuis des décennies.
Il s’est révélé être un outil précieux lors du dépannage d’erreurs dans le gigantesque code énoncé qui constitue le système SAP ERP typique.
Les messages d'erreur sont gérés dans un référentiel central où chaque message est identifié par sa classe et son numéro.
Lorsque vous souhaitez émettre un message d'erreur, indiquez uniquement les variables de classe, de nombre, de gravité et spécifiques au message. La représentation textuelle du message est créée à l'exécution. Vous voyez généralement la classe et le numéro du message dans n’importe quel contexte dans lequel les messages apparaissent. Cela a plusieurs effets intéressants:
Vous pouvez rechercher automatiquement dans la base de code ABAP toutes les lignes de code créant un message d'erreur spécifique.
Vous pouvez définir des points d'arrêt de débogueur dynamiques qui se déclenchent lorsqu'un message d'erreur spécifique est généré.
Vous pouvez rechercher des erreurs dans les articles de la base de connaissances SAP et obtenir des résultats de recherche plus pertinents que si vous recherchiez "Impossible de trouver Foo".
Les représentations textuelles des messages sont traduisibles. Donc, en encourageant l'utilisation de messages plutôt que de chaînes, vous bénéficiez également des fonctionnalités i18n.
Un exemple de popup d'erreur avec le numéro du message:
Recherche de cette erreur dans le référentiel d'erreurs:
Trouvez-le dans la base de code:
Cependant, il y a des inconvénients. Comme vous pouvez le constater, ces lignes de code ne sont plus auto-documentées. Lorsque vous lisez le code source et voyez une
MESSAGE
déclaration telle que celles de la capture d'écran ci-dessus, vous ne pouvez déduire du contexte que ce que cela signifie réellement. De plus, les utilisateurs implémentent parfois des gestionnaires d’erreurs personnalisés qui reçoivent la classe et le numéro du message au moment de l’exécution. Dans ce cas, l'erreur ne peut pas être trouvée automatiquement ou ne peut pas être trouvée à l'emplacement où l'erreur s'est réellement produite. La solution de rechange au premier problème consiste à prendre l'habitude de toujours ajouter un commentaire dans le code source indiquant au lecteur la signification du message. Pour résoudre le second problème, ajoutez du code mort pour vous assurer que la recherche automatique de messages fonctionne. Exemple:Mais il y a des situations où cela n'est pas possible. Il existe par exemple certains outils de modélisation de processus métier basés sur l'interface utilisateur dans lesquels vous pouvez configurer les messages d'erreur pour qu'ils apparaissent lorsque les règles métier sont violées. L'implémentation de ces outils étant entièrement basée sur les données, ces erreurs ne figureront pas dans la liste où elles sont utilisées. Cela signifie qu’il faut s’appuyer trop sur la liste utilisée pour trouver la cause d’une erreur.
la source
Le problème avec cette approche est qu’elle conduit à une journalisation de plus en plus détaillée. 99,9999% de ce que vous ne regarderez jamais.
Au lieu de cela, je recommande de capturer l'état au début de votre processus et le succès / échec du processus.
Cela vous permet de reproduire le bogue localement, en parcourant le code et en limitant votre journalisation à deux emplacements par processus. par exemple.
Maintenant, je peux utiliser exactement le même état sur ma machine de développement pour reproduire l'erreur, en parcourant le code de mon débogueur et en écrivant un nouveau test unitaire pour confirmer le correctif.
De plus, je peux, si nécessaire, éviter davantage de journalisation en ne journalisant que les échecs ou en conservant l'état ailleurs (base de données? File d'attente de messages?)
Évidemment, nous devons faire très attention à la journalisation des données sensibles. Cela fonctionne donc particulièrement bien si votre solution utilise des files de messages ou le modèle de magasin d'événements. Comme le journal doit seulement dire "Message xyz a échoué"
la source
Je suggérerais que la journalisation n'est pas la solution, mais plutôt que cette circonstance est considérée comme exceptionnelle (elle verrouille votre programme) et qu'une exception devrait être levée. Dites que votre code était:
Il semble que votre code d'appel n'est pas configuré pour traiter le fait que Foo n'existe pas et que vous pourriez potentiellement l'être:
Et cela retournera une trace de pile avec l'exception qui peut être utilisée pour aider au débogage.
Alternativement, si nous nous attendons à ce que Foo puisse être NULL lorsqu’il est renvoyé et que c’est bien, nous devons réparer les sites d’appel:
Le fait que votre logiciel se bloque ou agisse "étrangement" dans des circonstances inattendues me semble mal - si vous avez besoin d'un Foo et que vous ne pouvez pas le supporter sans être présent, il semble préférable de s'effondrer que d'essayer de suivre un chemin corrompre votre système.
la source
Les bibliothèques de journalisation appropriées fournissent des mécanismes d'extension. Par conséquent, si vous souhaitez connaître la méthode d'où provient un message de journal, ils peuvent le faire immédiatement. Cela a un impact sur l'exécution car le processus nécessite de générer une trace de pile et de la parcourir jusqu'à ce que vous soyez sorti de la bibliothèque de journalisation.
Cela dit, cela dépend vraiment de ce que vous voulez que votre identifiant fasse pour vous:
Toutes ces choses peuvent être faites directement avec un logiciel de journalisation approprié (c'est-à-dire pas
Console.WriteLine()
ouDebug.WriteLine()
).Personnellement, le plus important est la capacité de reconstruire les chemins d'exécution. C'est ce que des outils comme Zipkin sont conçus pour accomplir. Un ID permettant de suivre le comportement d'une action d'un utilisateur sur l'ensemble du système. En mettant vos journaux dans un moteur de recherche central, vous pouvez non seulement rechercher les actions les plus longues, mais également appeler les journaux qui s'appliquent à cette action (comme la pile ELK ).
Les identifiants opaques qui changent avec chaque message ne sont pas très utiles. Un identifiant cohérent utilisé pour suivre le comportement à travers une suite complète de microservices ... extrêmement utile.
la source