Ein allgemeines Muster zum Auffinden eines Fehlers folgt diesem Skript:
- Beachten Sie Verrücktheit, zum Beispiel keine Ausgabe oder ein hängendes Programm.
- Suchen Sie die relevante Meldung in der Protokoll- oder Programmausgabe, z. B. "Foo konnte nicht gefunden werden". (Das Folgende ist nur relevant, wenn dies der Pfad ist, auf dem der Fehler gefunden wurde. Wenn ein Stack-Trace oder andere Debugging-Informationen verfügbar sind, ist das eine andere Geschichte.)
- Suchen Sie den Code, in dem die Nachricht gedruckt wird.
- Debuggen Sie den Code zwischen der ersten Stelle, an der Foo das Bild eingibt (oder eingeben sollte), und der Stelle, an der die Nachricht gedruckt wird.
In diesem dritten Schritt kommt der Debugging-Prozess häufig zum Erliegen, da der Code an vielen Stellen "Foo nicht gefunden" (oder eine Zeichenfolge mit Vorlagen Could not find {name}
) enthält. Tatsächlich hat mir ein Rechtschreibfehler mehrmals geholfen, den tatsächlichen Standort viel schneller zu finden, als ich es sonst getan hätte. Dadurch wurde die Nachricht systemweit und häufig weltweit eindeutig, was zu einem sofortigen Treffer in einer relevanten Suchmaschine führte.
Die offensichtliche Schlussfolgerung daraus ist, dass wir global eindeutige Nachrichten-IDs im Code verwenden, diese als Teil der Nachrichtenzeichenfolge fest codieren und möglicherweise überprüfen sollten, dass nur ein Vorkommen jeder ID in der Codebasis vorhanden ist. Was hält diese Community im Hinblick auf die Wartbarkeit für die wichtigsten Vor- und Nachteile dieses Ansatzes und wie würden Sie dies implementieren oder auf andere Weise sicherstellen, dass die Implementierung niemals erforderlich wird (vorausgesetzt, die Software weist immer Fehler auf)?
Antworten:
Insgesamt ist dies eine gültige und wertvolle Strategie. Hier sind einige Gedanken.
Diese Strategie wird auch als "Telemetrie" bezeichnet. Wenn alle diese Informationen kombiniert werden, können sie die Ausführungsablaufverfolgung "triangulieren" und es einem Ratgeber ermöglichen, einen Eindruck davon zu gewinnen, was der Benutzer / die Anwendung zu erreichen versucht und was tatsächlich passiert ist .
Einige wesentliche Daten, die gesammelt werden müssen (die wir alle kennen), sind:
Häufig scheitern herkömmliche Protokollierungsansätze daran, dass eine Protokollnachricht auf niedriger Ebene nicht zum Befehl auf höchster Ebene zurückverfolgt werden kann, der sie auslöst. Ein Stack-Trace erfasst nur die Namen der übergeordneten Funktionen, mit denen der Befehl auf höchster Ebene ausgeführt wurde, und nicht die Details (Daten), die manchmal zur Charakterisierung dieses Befehls erforderlich sind.
Normalerweise wurde keine Software geschrieben, um diese Art von Rückverfolgbarkeitsanforderungen zu implementieren. Dies erschwert das Korrelieren der Nachricht auf niedriger Ebene mit dem Befehl auf hoher Ebene. Das Problem ist besonders schlimmer bei Systemen mit mehreren Threads, bei denen sich viele Anforderungen und Antworten überlappen können und die Verarbeitung möglicherweise auf einen anderen Thread als den ursprünglichen Thread für den Empfang von Anforderungen verlagert wird.
Um den größtmöglichen Nutzen aus der Telemetrie zu ziehen, müssen Änderungen an der gesamten Softwarearchitektur vorgenommen werden. Die meisten Schnittstellen und Funktionsaufrufe müssen geändert werden, um ein "Tracer" -Argument zu akzeptieren und weiterzugeben.
Sogar Dienstprogrammfunktionen müssen ein "Tracer" -Argument hinzufügen, damit die Protokollnachricht, falls dies fehlschlägt, mit einem bestimmten übergeordneten Befehl korreliert werden kann.
Ein weiterer Fehler, der die Telemetrieverfolgung erschwert, sind fehlende Objektreferenzen (Nullzeiger oder Referenzen). Wenn wichtige Daten fehlen, kann es unmöglich sein, irgendetwas Nützliches für den Fehler zu melden.
In Bezug auf das Schreiben der Protokollnachrichten:
quelle
Stellen Sie sich vor, Sie haben eine einfache Hilfsprogrammfunktion, die an Hunderten von Stellen in Ihrem Code verwendet wird:
Wenn wir tun würden, was Sie vorschlagen, könnten wir schreiben
Ein Fehler, der auftreten kann, liegt vor, wenn die Eingabe Null ist. Dies würde zu einer Division durch Null führen.
Nehmen wir also an, Sie sehen 27349262 in Ihrer Ausgabe oder Ihren Protokollen. Wo suchen Sie nach dem Code, der den Nullwert überschritten hat? Denken Sie daran, dass die Funktion mit ihrer eindeutigen ID an Hunderten von Stellen verwendet wird. Während Sie also wissen, dass eine Division durch Null stattgefunden hat, haben Sie keine Ahnung, wem
0
es gehört.Scheint mir, wenn Sie sich die Mühe machen, die Nachrichten-IDs zu protokollieren, können Sie auch den Stack-Trace protokollieren.
Wenn Sie die Ausführlichkeit der Stapelablaufverfolgung stört, müssen Sie sie nicht wie von der Laufzeit als Zeichenfolge ausgeben. Sie können es anpassen. Wenn Sie beispielsweise einen abgekürzten Stack-Trace nur für
n
Ebenen benötigen, können Sie Folgendes schreiben (wenn Sie c # verwenden):Und benutze es so:
Ausgabe:
Möglicherweise einfacher als die Verwaltung von Nachrichten-IDs und flexibler.
Stehlen Sie meinen Code von DotNetFiddle
quelle
SAP NetWeaver tut dies seit Jahrzehnten.
Es hat sich als wertvolles Werkzeug bei der Fehlerbehebung im gewaltigen Code-Ungetüm erwiesen, das das typische SAP-ERP-System ist.
Fehlermeldungen werden in einem zentralen Repository verwaltet, in dem jede Nachricht anhand ihrer Nachrichtenklasse und Nachrichtennummer identifiziert wird.
Wenn Sie eine Fehlermeldung ausgeben möchten, geben Sie nur Klassen-, Zahlen-, Schweregrad- und nachrichtenspezifische Variablen an. Die Textdarstellung der Nachricht wird zur Laufzeit erstellt. Normalerweise sehen Sie die Nachrichtenklasse und -nummer in jedem Kontext, in dem Nachrichten erscheinen. Dies hat mehrere nette Effekte:
In der ABAP-Codebasis finden Sie automatisch Codezeilen, die eine bestimmte Fehlermeldung erzeugen.
Sie können dynamische Debugger-Haltepunkte festlegen, die ausgelöst werden, wenn eine bestimmte Fehlermeldung generiert wird.
Sie können Fehler in den SAP-Knowledge Base-Artikeln nachschlagen und relevantere Suchergebnisse erhalten, als wenn Sie nach "Foo konnte nicht gefunden werden" suchen.
Die Textdarstellungen von Nachrichten sind übersetzbar. Wenn Sie also die Verwendung von Nachrichten anstelle von Zeichenfolgen fördern, erhalten Sie auch i18n-Funktionen.
Ein Beispiel für ein Fehler-Popup mit der Nachrichtennummer:
Suchen Sie diesen Fehler im Fehler-Repository:
Finden Sie es in der Codebasis:
Es gibt jedoch Nachteile. Wie Sie sehen, sind diese Codezeilen nicht mehr selbstdokumentierend. Wenn Sie den Quellcode lesen und eine
MESSAGE
Aussage wie die im obigen Screenshot sehen, können Sie nur aus dem Kontext schließen, was dies tatsächlich bedeutet. Außerdem implementieren Benutzer manchmal benutzerdefinierte Fehlerbehandlungsroutinen, die zur Laufzeit die Nachrichtenklasse und -nummer erhalten. In diesem Fall kann der Fehler nicht automatisch oder nicht an der Stelle gefunden werden, an der der Fehler tatsächlich aufgetreten ist. Die Problemumgehung für das erste Problem besteht darin, es sich zur Gewohnheit zu machen, dem Quellcode immer einen Kommentar hinzuzufügen, der dem Leser mitteilt, was die Nachricht bedeutet. Der zweite Fehler wird behoben, indem ein toter Code hinzugefügt wird, um sicherzustellen, dass die automatische Nachrichtensuche funktioniert. Beispiel:Es gibt jedoch Situationen, in denen dies nicht möglich ist. Es gibt beispielsweise einige UI-basierte Geschäftsprozessmodellierungstools, mit denen Sie Fehlermeldungen konfigurieren können, die bei Verstößen gegen Geschäftsregeln angezeigt werden. Die Implementierung dieser Tools erfolgt vollständig datengesteuert, sodass diese Fehler nicht im Verwendungsnachweis aufgeführt werden. Das bedeutet, dass es ein roter Hering sein kann, sich bei der Suche nach der Fehlerursache zu sehr auf den Verwendungsnachweis zu verlassen.
quelle
Das Problem bei diesem Ansatz ist, dass die Protokollierung immer detaillierter wird. 99,9999% davon werden Sie nie sehen.
Stattdessen empfehle ich, den Status zu Beginn Ihres Prozesses und den Erfolg / Misserfolg des Prozesses zu erfassen.
Auf diese Weise können Sie den Fehler lokal reproduzieren, den Code schrittweise durchlaufen und die Protokollierung auf zwei Stellen pro Prozess beschränken. z.B.
Jetzt kann ich genau denselben Status auf meinem Entwicklungscomputer verwenden, um den Fehler zu reproduzieren, den Code in meinem Debugger durchzugehen und einen neuen Komponententest zu schreiben, um die Fehlerbehebung zu bestätigen.
Außerdem kann ich bei Bedarf weitere Protokollierungen vermeiden, indem ich nur Fehler protokolliere oder den Status an anderer Stelle beibehalte (Datenbank? Nachrichtenwarteschlange?).
Natürlich müssen wir besonders vorsichtig sein, wenn es darum geht, sensible Daten zu protokollieren. Dies funktioniert also besonders gut, wenn Ihre Lösung Nachrichtenwarteschlangen oder das Ereignisspeichermuster verwendet. Da das Protokoll nur "Nachricht xyz fehlgeschlagen" sagen muss
quelle
Ich würde vorschlagen, dass die Protokollierung nicht der richtige Weg ist, sondern dass dieser Umstand als außergewöhnlich angesehen wird (Ihr Programm wird gesperrt) und eine Ausnahme ausgelöst werden sollte. Angenommen, Ihr Code war:
Es hört sich so an, als ob Ihr Anrufcode nicht dafür eingerichtet ist, mit der Tatsache umzugehen, dass Foo nicht existiert und Sie möglicherweise Folgendes tun könnten:
Und dies gibt einen Stack-Trace zurück, zusammen mit der Ausnahme, die zum Debuggen verwendet werden kann.
Wenn wir alternativ erwarten, dass Foo beim Empfang null sein kann und das in Ordnung ist, müssen wir die aufrufenden Sites reparieren:
Die Tatsache, dass Ihre Software unter unerwarteten Umständen "seltsam" hängt oder sich "seltsam" verhält, scheint mir falsch zu sein. Wenn Sie ein Foo brauchen und nicht damit umgehen können, dass es nicht da ist, dann ist es besser, wenn Sie abstürzen, als einen Pfad zu beschreiten, der möglicherweise verläuft beschädigen Sie Ihr System.
quelle
Ordnungsgemäße Protokollbibliotheken bieten Erweiterungsmechanismen. Wenn Sie also wissen möchten, auf welche Weise eine Protokollnachricht erstellt wurde, können sie dies sofort tun. Dies hat Auswirkungen auf die Ausführung, da für den Prozess ein Stack-Trace generiert und durchlaufen werden muss, bis Sie die Protokollbibliothek verlassen haben.
Das heißt, es hängt wirklich davon ab, was Ihre ID für Sie tun soll:
All diese Dinge können sofort mit der richtigen Protokollierungssoftware erledigt werden (dh nicht
Console.WriteLine()
oderDebug.WriteLine()
).Was persönlich wichtiger ist, ist die Fähigkeit, Ausführungspfade zu rekonstruieren. Dafür wurden Tools wie Zipkin entwickelt. Eine ID, um das Verhalten einer Benutzeraktion im gesamten System zu verfolgen. Indem Sie Ihre Protokolle in eine zentrale Suchmaschine stellen, können Sie nicht nur die Aktionen mit der längsten Laufzeit finden, sondern auch die Protokolle aufrufen, die für diese eine Aktion gelten (z. B. den ELK-Stapel ).
Undurchsichtige IDs, die sich mit jeder Nachricht ändern, sind nicht sehr nützlich. Eine konsistente ID, die verwendet wird, um das Verhalten einer ganzen Reihe von Mikrodiensten nachzuverfolgen ... immens nützlich.
quelle