JavaScript-Fehler loggen und so unbekannte Probleme aufdecken

Head of Technology @ Chrono24

Serverseitiges Loggen über Apache/nginx oder andere Logfiles, die von der jeweiligen Backend-Applikation erzeugt werden, ist weit verbreitet und sehr nützlich um Probleme zu erkennen, die während der Entwicklung oder QA nicht aufgetreten sind.

Fast immer können durch diese Logfiles clientseitige Probleme, wie z.B. Fehler beim Ausführen von JavaScript, nicht erkannt werden. Das kann fatale Folgen haben, da heutzutage zahlreiche Funktionen auf JavaScript angewiesen sind.

Im Idealfall werden die clientseitigen Fehler erfasst, über eine Schnittstelle an das Backend gemeldet und später ausgewertet. Da bereits ein Fehler im JavaScript auftrat, kann es sein, dass ein Versenden der Daten per AJAX und z.B. jQuery nicht mehr oder nur sehr kompliziert möglich ist, da Vanilla-JS eingesetzt werden muss.

Erfassen von JavaScript-Fehlern

Die meisten Browser stellen das Event window.onerror zur Verfügung, das mit einer beliebigen Funktion überschrieben werden kann:

window.onerror = function(message, source, line, column, error) {
   // Error handling
};

Das Hinzufügen eines Event-Listeners ist ebenfalls möglich:

window.addEventListener('error', function(event) {
   // Error handling
});

Es muss beachtet werden, dass aus historischen Gründen nicht immer die gleichen Parameter bzw. Inhalte an die Funktionen übergeben werden, weshalb diese Daten gegebenenfalls bereinigt werden müssen.

Ein mögliches Beispiel ist:

function errorHandler(message, file, line, column, error) {
   if (typeof file !== 'string') {
      file = '';
   }

   line = line || '';
   column = column || '';

   // Log the first error only and also not every error
   if (window.doNotTrackErrors || isIgnoredError(message, file, error)) {
      return;
   }

   // Log the first error only
   window.doNotTrackErrors = true;

   if (jQuery == null) {
      const s = document.createElement('script');
      s.onload = function () {
         logError(message, file, line, column, error);
      };
      s.src = '/lib/generated/js/vendor/jquery/codeorigin.jquery.com/cdn/jquery-1.11.3.min.js';
      document.getElementsByTagName('head')[0].appendChild(s);
   } else {
      logError(message, file, line, column, error);
   }
}

Mit der Statusvariable window.doNotTrackErrors sorgen wir auf einfachem Wege dafür, dass nur der erste auftretende Fehler gelogt wird. Oft entstehen durch JavaScript-Fehler zahlreiche Folgefehler, die jedoch nicht interessant sind.

Da wir uns im Client befinden, der einerseits nicht vertrauenswürdig ist und andererseits fehlerhafte Browserplugins einsetzt, ist es ratsam bestimmte Fehler auszufiltern. Das ist auch dann wichtig, wenn man Third-Party-Banner einsetzt, die JavaScript-Fehler erzeugen, die man selbst nicht loggen möchte.

Loggen von JavaScript-Fehlern im Backend

Sobald wir mit obigem Beispiel einen Fehler erfasst haben, müssen die Daten gesammelt und an das Backend gesendet werden. Dazu bietet sich ein AJAX-Request an, der z.B. mit jQuery ausgeführt wird:

function logError(type, message, file, line, column, error) {
   // Log error to browser console   
   console.error(message, error);

   try {
      const data = {
         url: window.location.href,
         file: file,
         line: line,
         column: column,
         message: typeof message === 'string' ? message : '',
         stacktrace: error != null ? error.stack : '',
         userAgent: navigator.userAgent,
         clientWidth: document.documentElement.clientWidth,
         referrer: document.referrer
      };

      jQuery.ajax({
         url: '/log-js-error',
         method: 'POST',
         data: {
            data: JSON.stringify(data)
         }
      });
   } catch (e) {
      // Something went wrong
   }
}

Wie man schnell sieht, werden die gesammelten Informationen mit dem POST-Parameter data an das Backend übermittelt, das die Daten dann in Logfiles schreiben kann, die in regelmäßigen Abständen geprüft werden müssen.

Achtung: Falls der Endpoint nicht geschützt wird, kann dieser missbraucht werden, um beliebige Daten in die Logfiles schreiben zu lassen. Es ist also unabdingbar ein Rate-Limiting oder andere Sicherheitsmechanismen zu integrieren.

Auswertung der Daten

Die Auswertung der Logfiles kann sehr mühsam sein, da die übermittelten Daten vom jeweiligen Browser und der vom Benutzer eingestellten Sprache abhängen. Es ist deshalb erforderlich, bestimmte Muster zu erkennen. Ein ELK-Stack kann diese Arbeit deutlich vereinfachen:

  • Logstash: Konvertieren und übermitteln der Daten an die Datenhaltung
  • Elasticsearch: Datenhaltung
  • Kibana: Oberfläche, um die Daten zu visualisieren und besser filtern zu können

Mit Kibana steht eine komfortable Oberfläche zur Verfügung, um die Logdaten auszuwerten. Clientseitige Fehler sind teilweise schwierig zu reproduzieren, weshalb Detailinformationen zu Browser, Gerätetyp, Fensterbreite, Loginzustand, usw. sehr hilfreich sein können.

JavaScript Fehler in Kibana

Fazit

Viele unterschiedliche Browser und deren Versionen können Probleme erzeugen, die man nicht unmittelbar erkennen kann, da sie im Client auftreten. Das Loggen solcher Fehler im Backend sorgt für deutlich mehr Transparenz, einfachere Frontend-Entwicklung und bessere User Experience.

Bildquellen

  • javascript-errors-kibana: Owned by the author | All Rights Reserved
  • javascript-logo: pixabay