Jetzt könnte man sagen, dass man einfach ordentlich testen muss und solche Fehler durch automatische Tests nicht auftreten können. Das ist zum einen natürlich teilweise richtig und auch empfehlenswert, aber am Ende ist niemand perfekt. Einen Test zu entwickeln, welcher alle Eventualitäten abdeckt ist nahezu unmöglich und wird sicher von niemandem finanziert. Ein gewisses Restrisiko bleibt am Ende immer.
Dieses Problem kann man als Dienstleister nur lösen, indem man das Log-Management zentralisiert und ein automatisches Reporting einführt. Nur dann kann die Qualität der Shops auf Dauer gewährleistet werden. Im Idealfall löst man so sogar Probleme, noch bevor der Kunde davon etwas mitbekommt. Aber wie stellt man das Ganze nun an?
Die Idee
Jetzt gibt es zwei (oder sogar drei) Möglichkeiten.
- Entweder, man richtet einen Cron ein, welcher regelmäßig die Exception-Logs an eine zentrale Stelle liefert. Diese müsste man in einer Extension konfigurieren, wodurch man wiederum das meiste der Logik auf dem einzelnen System hätte. So werden Updates erschwert und man hat sehr schnell verschiedene Versionsstände im Einsatz. Möchte man das Zielsystem ändern, muss man alle Shops anfassen.
- Oder man holt die Informationen regelmäßig von den einzelnen Shops ab. Dies hätte auch den Vorteil, dass man Systeme einfach aus dem Logging ausklinken kann, sobald man nicht mehr für den Kunden arbeitet, oder ein Servicevertrag ausläuft. Hier bietet es sich außerdem an, die Magento-API zu erweitern, um so möglichst einfach (und sicher) an die Daten zu kommen.
- Theoretisch gibt es noch die Möglichkeit, diese direkt in ein anderes System zu Pushen, sobald eine Exception auftritt. Aber im Normalfall muss man nicht binnen Sekunden reagieren, sobald eine Exception aufgetreten ist. Aus Performance-Gründen würde ich diese Möglichkeit daher ignorieren.
In diesem Beitrag möchte ich mich auf die zweite Möglichkeit festlegen. Alle haben ihre Vor- und Nachteile. Es existieren natürlich schon etliche Lösungen am Markt, welche das Remote-Logging möglich machen. Auf diese möchte ich aber nicht weiter eingehen.
Reports
Reports werden bei unhandled Exceptions in Magento geschrieben. In der Standard-Konfiguration kann der Kunde die detaillierten Informationen nicht sehen. Geschrieben werden die Reports aber trotzdem. Tritt irgendwo eine unhandled Exception auf, wird diese in der run-Methode der Mage.php gefangen. Dort werden die Informationen an die Funktion printException weitergegeben.
Ist nun der Developer-Mode nicht eingeschaltet, wird ein Array mit Informationen für den Report aufgebaut. Enthalten sind:
- Die Meldung der Exception
- Der Exception-Trace als String
- Die Request-URL ($_SERVER['REQUEST_URI'])
- Der Script-Pfad ($_SERVER['SCRIPT_NAME'])
- Der aktuelle Store-Code (um das Design anpassen zu können)
Danach wird die report.php im errors-Verzeichnis eingebunden:
require_once(self::getBaseDir() . DS . 'errors' . DS . 'report.php');
Wie man sieht, hat man hier nicht sonderlich viele Möglichkeiten, in den Verlauf einzugreifen. Keine Events und keine genutzten Models, um irgendwo etwas zu überschreiben. Das ist natürlich ärgerlich, da man nicht viel weiter kommt, ohne Dateien vom Core anfassen zu müssen. Und das machen wir ja bekanntlich nicht.
Innerhalb der report.php wird dann der processor genutzt (siehe processor.php). Diese speichert zuerst den Report in folgenden Schritten:
- Zufälle Report-ID erzeugen
- Report-Verzeichnis erstellen (falls nötig)
- Report-Informationen serialisieren
- Datei schreiben
- Dateiberechtigungen anpassen (777)
Danach wird der Report noch angezeigt. Je nach Einstellungen werden dabei entweder alle Informationen ausgegeben, oder nur die Report-ID. Ich würde davon abraten, die Core-Files zu modifizieren um die Logik zu ändern. Viel schlauer wäre es, später auf die entsprechenden Dateien zuzugreifen. Schon allein aus Performance-Gründen.
Technik / Protokolle
Für die Umsetzung muss man daran denken, dass diese auch in der Minimalkonfiguration von Magento funktionieren muss. Daher muss man sich auf die "Basis-Protokolle" verlassen (z.B. Mail, FTP oder eben HTTP). Eine Konfiguration per SSH oder ähnliches ist natürlich generell denkbar, aber eben sehr aufwändig einzurichten. So könnte man theoretisch auch alle Dateien per SCP oder RSYNC zentral sammeln. Dann hat man nur wieder das Problem, dass man die Zugangsdaten bzw. Zertifikate/Keys dafür pflegen muss. Bei wenigen Kunden sicher eine tolle Lösung.
Ich wünsche mir aber ein System, welche mit wenigen Klicks eingerichtet ist und leicht zu warten bleibt.
Umsetzung
Genug Vorgeplänkel. Jetzt geht es an die Implementierung. Wie genau soll die API nun aussehen? Da es sich um eventuell sensible Informationen handelt, welche in den Reports gespeichert werden, sollte eine Authentifizierung implementiert werden. Diese kann dabei gerne einfach gehalten werden. Warum sollte man hier einen komplexen OAuth-Weg gehen, wenn es eine einfache HTTP-Authentifizierung auch tut. Natürlich ist es (wie immer) ratsam, dies über eine verschlüsselte HTTPS-Verbindung zu erledigen.
Zum Glück bietet hier ebenfalls das Framework bereits eine Möglichkeit, die Benutzerdaten aus dem Request zu prüfen.
/** @var $http Mage_Core_Helper_Http */
$http = Mage::helper('core/http');
list($user, $pass) = $http->authValidate();
Fehlen die Authentication-Informationen, wird der Request automatisch abgebrochen und dem Client mitgeteilt, dass die authentifizierung fehlgeschlagen ist. Hier noch ein Hinweis auf meinen letzten Beitrag - suchen im Framework lohnt sich eben immer. Falls Daten vorhanden sind, bekommt man diese als Array zurückgeliefert und kann diese ganz einfach auswerten. In diesem Fall möchte ich die Zugangsdaten über das Backend konfigurieren können, sodass man für jeden Shop eigene Daten verwenden kann.
Generell brauchen wir also nur drei ganz einfache Funktionen:
- list
- get
- delete
Kommuniziert wird per JSON. So spart man sich viel Overhead. Alles soll ja so einfach und performant wie möglich gehalten sein. Die List-Funktion soll ein Array mit allen Werten zurückliefern, welche die Report-ID (der Dateiname), den Zeitstempel und die Größe enthält. Danach kann man die komplette Liste durcharbeiten und die Einträge per get-Funktion abholen, indem man die entsprechende Report-ID übergibt. Hier sind dann alle Informationen enthalten, welche auch in der entsprechenden Datei stehen. Deserialisiert und als JSON wieder verpackt.
Wie man sieht ist alles kein Hexenwerk. Das Ganze hat man in wenigen Minuten runtergeschrieben. Meine Extension zum Artikel liegt auf GitHub für Euch bereit. Seht es mehr als Modulbasis, nicht als fertige Lösung für den Live-Betrieb.
Auf der Gegenseite wird dafür dann nur ein Cron implementiert, welcher die Liste aller Shops durcharbeitet. Dazu braucht man dann eben nur URL, Benutzername und Passwort. Darauf gehe ich nun aber nicht näher ein, dazu ist es zu simpel.
Fazit
Meiner Meinung nach ist dies eine super Lösung, um auf mehreren Systemen die Fehler einzusammeln. Gerade, wenn man mehrere Kunden betreut (als Agentur oder Freelancer), könnte einem diese Lösung eine Menge Stress und Zeit sparen.
Möchte man allerdings einen einzelnen Shop überwachen, bieten sich andere Möglichkeiten eher an. Und sei es nur eine Mail an den technischen Ansprechpartner. Dies geht am einfachsten, indem man einfach eine Mailadresse in der local.xml im errors-Verzeichnis hinterlegt und die action auf "email" stellt. Das Ganze sieht dann so aus:
<config>
<skin>default</skin>
<report>
<action>email</action>
<subject>Exception im Shop</subject>
<email_address>yourmail@shopdomain.com</email_address>
<trash>leave</trash>
</report>
</config>
Außerdem sollte man beachten, dass ein Report erst dann geschrieben wird, wenn alles vorige Handling nicht gegriffen hat oder nicht existent war. Alles was korrekt gefangen wird und über die normalen Log-Methoden geschrieben wird, ist hiervon unberührt. Für diese Fälle könnte man sich das exception.log auch einfach per Mail zusenden lassen. Zum Beispiel per Cron:
0 0 * * * /usr/lib/sendmail yourmail@shopdomain.com < /www/shop/var/log/exception.log;rm /www/shop/var/log/exception.log
Noch schöner ist es aber sicher, auf die Extension Firegento-Logger zurück zu greifen. Hier kann man mit mehreren Adaptern out of the Box bestehende Systeme anbinden (soweit ich weiß auch E-Mail). Außerdem ist es relativ einfach, neue Adapter zu schreiben. Der Umfang der Extension würde aber den (eh schon viel zu langen) Beitrag noch weiter aufblähen.
Auch schön wäre eine Lösung, welche MageMonitoring anbindet. Dann könnte man den kompletten Status des Shops über eine API erfragen und Probleme lokal sammeln. Wie man sieht, sind die Lösungen vielfältig und dieser Beitrag ist nur ein minimaler Denkanstoß für ein Thema, welches man sicher extrem komplex aufziehen könnte.
- Wie löst ihr das angesprochene Problem?
- Habt ihr Euch schon einmal Gedanken dazu gemacht?
- Welche Gründe sprechen für und gegen so ein System?