.. _debugging: .. index:: single: Debugging single: Rollbar single: Debug Toolbar single: pdb single: Logging Debugging in Django ************************ die print-Funktion ===================================== Der einfachste und unkompliziertes Weg, Teile des Codes zu testen oder mal eine kleine Testausgabe zu machen, ist die print-Funktion. Führen wir print in einer View aus, sehen wir das Ergebnis in der Konsole, dh. es wird uns direkt in die Ausgabe des Runservers gespielt. Allerdings sollte man darauf achten, dass die prints nicht im Produktivsystem aktiv sind. Alles in allem also nicht unbedingt das ideale Werkzeug, um Django Code zu debuggen. Es ist auch zu beachten, dass ``print`` standardmäßig immer auf ``stdout`` druckt, dieses Verhalten aber nicht immer erwünscht ist. .. admonition:: File-Deskriptoren mit Bedeutung: stdout, stderr, stdin Unter Linux existieren drei Standard-Datenströme für die Ein- und Ausgabe. **stdin** ist die Standard-Eingabe, mit der Daten in ein Programm eingegeben werden können. Wenn wir auf der Kommandozeile ein Python-Programm starten und über ``input()`` Daten einlesen, ist das die Standardeigabe. **stdin** ist ein File-Deskriptor mit der **Nummer 0**. Die Standard-Ausgabe nennt sich **stdout** und ist mit der **Nummer 1** ein weiterer File-Deskriptor. Beim Aufruf der ``print()``-Funktion wird standardmäßig nach **stdout** gedruckt. Zuletzt gibt es noch den File-Deskriptor **stderr** mit der **Nummer 2**. Wer noch nie von diesen drei Standard-Datenströmen gehört hat, hat vielleicht an dieser Stelle die Frage, was das nun ist. In Python könnten wir eine ``print``-Funktion auf die folgende Weise dazu bewegen, nach stderr zu drucken: .. code-block:: python print("Fehler!", file=sys.stderr) Zuerst mal passiert nichts weiter, führen wir die Funktion so aus, wird uns auf der Konsole eben "Fehler!" ausgegeben. Also, was soll das dann? Wir könnten ein anderes Programm dazu bewegen, alles, was nach **stderr** geprintet wird, in eine Datei zu schreiben, während alles andere, was nach **stdout** gedruckt wird, auf der Konsole auszugeben. Im folgenden ein Beispiel-Skript, was gerne ausprobiert werden kann. .. code-block:: python # stderr_test.py import sys print("das hier wird nach stdout gedruckt") print("das hier nach stderr", file=sys.stderr) Ausführen kann man das Programm dann auf der Konsole: .. code-block:: bash python stderr_test.py 2> error_log python stderr_test.py 1> output_file 2> error_log python stderr_test.py 1>> output_file 2>> error_log ``2>`` leitet alle Ausgaben an **stderr** in eine Datei namens ``error_log`` um, während die andere print-Ausgabe auf der Konsole ausgegeben wird , bzw via ``1>`` in die ``output_file`` geschrieben wird. Im dritten Beispiel nutzen wir den Doppelpfeil ``>>``, um die Daten nur anzuhängen und nicht zu überschreiben. Allerdings sollten wir uns hüten, in Django diese Angabe ``sys.stderr`` manuell zu machen. Das ``Logging``-Modul von Django hat dieses Verhalten out-of-the-box. Mehr zu dem Thema: ``_ Logger ============================================== Wir können das Python-Logging Modul unkompliziert in Django nutzen. Dazu öffnen wir erstmal die Datei ``event_manager/event_manager/settings/base.py`` und definieren die ``LOGGING``-Konfiguration: .. rli:: https://raw.githubusercontent.com/realcaptainsolaris/event_manager_code/main/settings/base_settings_logs_1.py :language: python Wir haben in den ``settings/base.py`` also das Logging für das System festgelegt (nur ein Beispiel). Wir können das Logging natürlich in den Settings für Produktivsystem oder lokales System überschreiben. Formatters ------------ Wir haben einen einfachen Formatierer namens ``simple`` festgelegt. Unter ``formatters`` erstellt man die Formatierungen, die man in seinen Log Files benögtigt. Handler --------- Handler sind der Teil des Loggings, der mit den Debug-Infos konkret was macht, zum Beispiel die Fehler auf die Konsole streamen oder in ein rotierendes Log-File zu schreiben. Wir haben 2 Handler festgelegt: **django_log** Das django-Log ist für die Ausgaben von Django da und auch das schreiben wir in eine Datei. Wir wollen hier nur mindestens Warnings sehen. Es nutzt den simple-Formatter. **console** Auf der Console können wir alle Infos schreiben. Es nutzt den simple-Formatter. Logger --------- **django** Der Django-Logger ist der Haupt-Logger von Django. Wir könnten ihn hier auch deaktiveren, aber das wollen wir nicht. Wir wollen alles, was der Django-Logger von sich gibt, an den ``django_log``-Handler weiterreichen. Und der schreibt ja alles, was mindestens den Status ``WARNING`` hat, in eine Log-Datei. Auf der Konsole lassen wir auch Django-Infos zeigen, die sind ja manchmal auch ganz aufschlußreich. **eventmanager.events** Unsere Events-App soll Warnings in das debug_log schreiben. Wir brauche nicht für jede App einen Eintrag. Wenn wir keinen Eintrag angeben, wir der Django-Logger die Logging-Messages verarbeiten. Es macht nur Sinn, wenn man speziell für diese App Handler oder Levels nutzen will. Mehr zu den Django-Loggern hier: ``_ Root --------- Nicht alle Fehler, die auftreten, kommen direkt von Django oder einer Django-App. Auch Python oder eventuelle Abhängigkeiten senden Fehler und diese werden standardmäßig auf die Console weitergeleitet. Mit dem ``root`` Eintrag legen wir fest, dass alle diese Fehler ab dem ``WARNING``-Level auf der Konsole angzeigt werden sollen. Wenn wir die Logging-Ausgaben in der dev-Settings anders haben wollen, können wir diese dort nach Lust und Laune überschreiben .. rli:: https://raw.githubusercontent.com/realcaptainsolaris/event_manager_code/main/settings/dev_settings_logs_2.py :language: python Code debuggen ------------------- Wir können den Logger im Code so nutzen: .. code-block:: python import logging logger = logging.getLogger(__name__) def categories(request): """Funktion View, die alle Kategorien auflistet. http://127.0.0.1:8000/events/categories """ logger.info(f"verwendete HTTP-Methode: {request.method}") categories_list = Category.objects.all() logger.debug(f"Anzahl Kategorien: {categories_list.count()}") return render(request, "events/categories.html", {"categories": categories_list}) python Debugger ============================================== Für gezieltere Maßnahmen können wir auch den Python Debugger ``pdb`` nutzen. Die Vorgehensweise ist ziemlich klassisch. Wir setzen einen Breakpoint in einer View, rufen die Page auf und auf der Konsole wird der ``pdb``-Debugger aufgeführt und wartet auf Eingabe. Erst, wenn der Debugger mit ``quit`` wieder verlassen wird, wird auch die Page gerendert. Breakpoint -------------- breakpoint in Funktion setzen .. code-block:: python def categories(request): # breakpoint vor der Abfrage setzen um später categories zu untersuchen breakpoint() categories = Category.objects.all() return render( request, "events/categories.html", {"categories": categories}, ) View aufrufen -------------- http://127.0.0.1:8000/categories auf der Konsole öffnet sich der Debugger ``pdb`` .. rli:: https://raw.githubusercontent.com/realcaptainsolaris/event_manager_code/main/misc/pdg_output_1.txt :language: python .. admonition:: pdb - ein Klassiker Der Python-Debugger ``pdb`` ist fester Bestandteil der Python Standard-Bibliothek und ein mächtiges Werkzeug bei der interaktiven Inspektion von Python - Code. Nicht so komfortabel wie manche IDE, aber mit ein bisschen Übung leicht zu beherrschen. .. csv-table:: Die wichtigsten Kommandos: :widths: 20, 60 ``l``, liste Code oben und unterhalb aktueller Zeile (11 Zeilen) ``ll``, liste den Quellcode der akutellen Funktion (oder Frame) ``s``, Nächste Zeile und springe in Funktion (wenn verfügbar) ``n``, nächste Zeile und springe NICHT in Funktion (wenn verfügbar) ``c``, continue und springe zum nächsten Breakpoint ``var``, Inhalt der Variable var anzeigen ``q``, quit Logging mit Rollbar =================== Rollbar ist eine kommerzielle Error-Tracking Software, die in Django unkompliziert eingesetzt werden kann und alle Fehler/Exceptions an einen entfernten Server sendet. Rollbar bietet leichte Integration und ein komfortables Dashboard. Rollbar bietet überdies ein SDK für Django, es muss nur mit pip installiert und in die ``settings.py`` importiert werden. Rollbar ist nicht dafür da, falsche Formulareingaben oder ähnliches zu tracken, sondern Ausnahmen wie 404-Fehler und ähnliches. Rollbar Account erstellen ----------------------------- Für kleine Websites und Privatanwender bietet ``Rollbar`` einen Gratis-Account an. Mehr unter ``rollbar.com``. Rollbar installieren ----------------------------- Fügen wir die Rollbar der ``requirements.in`` hinzu, kompilieren und syncen: .. code-block:: shell ... rollbar .. code-block:: shell (eventenv) pip-compile requirements.in (eventenv) pip-sync requirements.txt requirements-dev.txt Settings -------------- Wenn ein Account angelegt wurde, kann man der entsprechende ``ROLLBAR_TOKEN`` in das ``.env``-File eintragen und in den Settings nutzen. Dazu in der ``event_manager/.env`` folgenden Eintrag hinzufügen: .. code-block:: bash ... ROLLBAR_TOKEN=a24ba3f5343a3234592cdstablee5e82b34g In den Settings muss die Middleware und die Konfigurationsvariablen eingetragen werden. .. code-block:: python import rollbar # anderer Code MIDDLEWARE = [ # andere Middleware "rollbar.contrib.django.middleware.RollbarNotifierMiddleware", ] # anderer Code ROLLBAR = { "access_token": env("ROLLBAR_TOKEN"), "environment": "development" if DEBUG else "production", "root": BASE_DIR, } rollbar.init(**ROLLBAR) Das war's, mehr muss man nicht machen, um ein professionelles Error-Tracking-System zu betreiben. Natürlich lassen sich weitere Umgebungen, Projekte und Kollaborationen einstellen. .. image:: /images/rollbar_3.png .. image:: /images/rollbar_4.png