Erweitertes Abfangen von Exceptions

Ist ein Projekt abgeschlossen und wird an den Kunden übergeben, beginnt die Zitter-Phase. Erst im Betrieb wird sich zeigen, ob das Programm seinen Aufgaben gewachsen ist. Keine Applikation ist fehlerlos/bugfrei und eigene Tests sind nicht unfehlbar. Was wir dagegen tun: Erweitertes Abfangen von Exceptions im productive Environment.

Symfony selbst baut auf viele hilfreiche Exceptions, die jedoch nur während der Entwicklung angezeigt werden. Wird das Projekt im productive Environment installiert, endet eine hilfreiche Exception nur in einer „Code 500 – Server unavailable“ Seite bzw. der eingerichteten Fehlermeldung. Das führt beim Kunden zu Frust und ist dem Entwickler keinerlei Hilfe, um dem Problem entgegenzuwirken.

Logs nutzen

Auf dem Live-System werden alle wichtigen Ereignisse – und natürlich auch Exceptions – in einer Log-Datei im Projektverzeichnis abgelegt. Unter var/logs/prod* findet man entsprechende Ausgaben. Hierbei muss natürlich erst einmal kommuniziert werden, dass ein Fehler aufgetreten ist. Oder man entwickelt einen serverseitigen Prozess, der den Entwickler über Einträge in der Log-Datei informiert.

Monolog

Symfony verwendet unter der Haube Monolog, um sämtliche Logs erstellen zu lassen. Das Paket ist äußert komfortabel und kann auch für unsere zwecke eingerichtet werden. Es bringt auch schon alles Notwendige mit, um Exceptions für die Entwickler sichtbar zu machen.

# config/packages/prod/monolog.yaml
monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: grouped
            excluded_http_codes: [403, 404]

        grouped:
            type: group
            members: [nested, mailer]

        nested:
            type: rotating_file
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            level: debug
            max_files: 30

        mailer:
            type: deduplication
            handler: swift_mail

        swift_mail:
            type: swift_mailer
            from_email: 'my-project@my-domain.com'
            to_email: 'receiver@example.com'
            subject: 'Exception in Project: %%message%%'
            level: debug
            formatter: monolog.formatter.html
            content_type: text/html

        console:
            type: console
            process_psr_3_messages: false
            channels: ["!event", "!doctrine"]

Diese Konfiguration erzeugt eine rotierende Log-Datei, also ein Log pro Tag und maximal 30 in einem Ordner. Zusätzlich wird eine E-Mail, die detaillierte Informationen zur Exception enthält, generiert und an receiver@example.com gesendet. Voraussetzung hierfür ist natürlich ein installierter SwiftMailer. Mit diesem Stück Text ist man also schon auf der sicheren Seite, um auf eventuelle Exceptions reagieren zu können.

Exceptions manuell nach Hause telefonieren lassen

Dies ist eine Symfony 3.* Konfiguration.

Sollte kein Monolog vorhanden sein, kann auch Twig eine Zusammenfassung der Exception für uns aufbereiten. Zusätzlich muss man sich jedoch selbst um die Zustellung der Informationen kümmern. Das Twig-Bundle verfügt über einen ExceptionController, der die Exception Seite im „dev“ Environment rendert. Die Methode showAction() im Controller kann für unsere Zwecke angepasst werden:

namespace AppBundle\Controller;

use ...;

/**
 * Class CustomExceptionController
 */
class CustomExceptionController extends ExceptionController
{
    /**
     * Override the existing handle
     * @param Request                   $request
     * @param FlattenException          $exception
     * @param DebugLoggerInterface|null $logger
     * @return Response
     */
    public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
    {
        // create the response from parent controller
        $response = parent::showAction($request, $exception, $logger);
        $code = $exception->getStatusCode();

        // do your own handling
        // ...

        // continue
        return $response;
    }
}

Unser Exception-Controller muss nun noch registriert werden.

# app/config/config.yml
twig:
    debug:                  "%kernel.debug%"
    strict_variables:       "%kernel.debug%"
    exception_controller:   AppBundle\Controller\CustomExceptionController::showAction

Die $exception enthält den gesamten „StackTrace“ des Fehlschlags. Hier wird einfach nur Platz geschaffen, um den eigenen Code auszuführen. An entsprechender Stelle könnte man z.B. eine E-Mail (mit der Exception) an seine eigene Adresse senden. Oder mit Guzzle an einen anderen Server oder Slack schicken.

Exceptions für den Admin

Dies ist eine Symfony 3.* Konfiguration.

Es kann ebenfalls hilfreich sein, für bestimmte Benutzergruppen Exceptions in prod Environment anzeigen zu lassen. Twig erlaubt das Überschreiben der Error-Seiten (Error-Codes 400, 404, 500, etc.). Hier also noch eine andere Möglichkeit für erweitertes Abfangen von Exceptions:

Wir müssen dafür unsere eigenen Templates in das Verzeichnis app/Resource/TwigBundle/views/Exception/ legen. Erstellt man hier eine error.html.twig, wird dieser Template für alle Exceptions genutzt.

{% extends '::base.html.twig' %}

{% block content %}
    <h1>Eine Exceptions wurde ausgelöst!</h1>
    <div>Status Code: {{ status_code }}</div>

    {% if isLoggedIn %}
        <div>{{ exception.message }}<br>Error Code: {{ exception.code }}</div>

        {% if isAdmin %}
            <div>
                <ol>
                    {% for trace in exception.trace %}
                        <li>
                            {% if trace.function %}
                                at
                                <strong>
                                    <abbr title="{{ trace.class }}">{{ trace.short_class }}</abbr>
                                    {{ trace.type ~ trace.function }}
                                </strong>
                                ({{ trace.args|format_args }})
                            {% endif %}

                            {% if trace.file is defined and trace.file and trace.line is defined and trace.line %}
                                {{ trace.function ? '<br>' : '' }}
                                in {{ trace.file|format_file(trace.line) }}
                            {% endif %}
                        </li>
                    {% endfor %}
                </ol>
            </div>
        {% endif %}
    {% endif %}
{% endblock %}

Eingeloggte Nutzer bekommen hier noch die Exception-Nachricht, Admins sogar den kompletten StackTrace angezeigt. Da dies nur hinter einer Firewall funktioniert, ist das Ergebnis natürlich beschränkt. Außerdem kann es auch unvorteilhaft sein, eine Exception absichtlich auszulösen / zu testen, um den StackTrace einzusehen.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.