Logging im Browser

Das Modul fmh-web-log aus dem Paket Basis protokolliert aus dem Browser heraus an den Endpunkt /siwflog. Es ist über das Layout bereits eingebunden — es gibt nichts zu registrieren.

Einbindung

_HeadIncludes.cshtml der Basis lädt das Modul, sobald das Layout BasisAssetKonstanten.HeadIncludes rendert:

<link rel="modulepreload" href="/_content/FMH.Komponente.Basis/fmh-web-log/index.js"/>

<script type="module" async src="/_content/FMH.Komponente.Basis/fmh-web-log/index.js" asp-append-version="true"></script>

Es ist ein natives ES-Modul ohne Build-Schritt und läuft deshalb bewusst nicht über den WebOptimizer: das Bundling versteht import/export nicht.

Daneben existiert wwwroot/js/fmh-web-log.js. Diese Datei ist nur ein Shim für Altprojekte, die noch ein klassisches <script src="…"> verwenden; sie lädt index.js per dynamischem import() nach. In neuen Projekten wird sie nicht gebraucht.

Verwenden

Beim Laden des Moduls entsteht window.log — eine Init-Funktion gibt es nicht:

window.log.info("Benutzer {BenutzerId} hat das Formular geöffnet", benutzerId);
window.log.warn("Für den Upload wurde keine Id zurückgegeben. Initiere Reload");
window.log.error("Fehler beim Abrufen der Einstellungen {@Exception}", e);

Die Meldungen sind Serilog-Vorlagen, keine fertigen Zeichenketten: Die benannten Platzhalter werden als eigene Eigenschaften übertragen und lassen sich in Seq einzeln durchsuchen. {@…} serialisiert ein Objekt.

Verfügbare Stufen — verbose, debug, info, warn, error, fatal.

Was automatisch protokolliert wird

Ohne eigenes Zutun meldet das Modul:

  • unbehandelte Fehler über window.onerror,
  • nicht behandelte Promise-Ablehnungen über window.onunhandledrejection,
  • jeden Aufruf von console.error (die Funktion wird dafür umschlossen).

Bekannte Infrastruktur-Fälle — etwa die anonyme Meldung "Script Error." eines Contentblockers — werden dabei als Warnung statt als Fehler eingestuft, damit sie die echten Fehler nicht überdecken.

Jedes Ereignis trägt zusätzlich ScreenSize, Url und einen Hash der Meldung.

Versand und Offline-Verhalten

Verhalten Standard
Stapelgrösse 10 Ereignisse
Wartezeit bis zum Senden 3 Sekunden
Zeitlimit je Anfrage 5 Sekunden
Erneuter Versuch nach Fehler 120 Sekunden
Zwischenspeicher localStorage, bis 2000 Einträge

Gesendet wird als POST mit Content-Type: application/vnd.serilog.clef, eine JSON-Zeile je Ereignis. Ist der Browser offline oder antwortet der Server nicht mit 2xx, wandern die Ereignisse zurück in den Zwischenspeicher und werden später erneut versucht — auch über einen Seitenwechsel hinweg. Gleiche Meldungen werden dabei zusammengefasst und gezählt, statt den Speicher zu fluten.

Eigene Einstellungen

Für den Regelfall ist nichts zu tun. Soll das Mindestlevel angehoben oder eine störende Meldung unterdrückt werden:

window.log.setOptions({
    level: 3000,                    // erst ab Information senden
    disallow: "^Heartbeat"          // regulärer Ausdruck auf die Meldungsvorlage
});

Die Stufen sind Verbose 1000, Debug 2000, Information 3000, Warning 4000, Error 5000, Fatal 6000; voreingestellt ist 1000, es wird also alles gesendet.

Für einen abweichenden Endpunkt oder andere Stapelgrössen wird der Appender selbst ausgetauscht:

import { SerilogAppender } from '/_content/FMH.Komponente.Basis/fmh-web-log/appender/SerilogAppender.js';

window.log.setOptions({
    appenders: [new SerilogAppender('Client', '/protokoll', {}, { batchSize: 25 })]
});

Zwischenspeicher des Browsers

Das Modul besteht aus mehreren Dateien, die index.js per import nachlädt. asp-append-version versioniert aber nur die Adresse der Einstiegsdatei — die importierten Dateien holt der Browser über ihren unveränderten Pfad und würde sie beliebig lange aus dem Zwischenspeicher bedienen.

Deshalb setzt Program.cs für alle Anfragen, deren Pfad fmh-web-log enthält, einen Revalidierungs-Header:

app.UseStaticFiles(
    new StaticFileOptions
    {
        OnPrepareResponse = context =>
        {
            if (context.Context.Request.Path.Value?.Contains(
                    "fmh-web-log",
                    StringComparison.OrdinalIgnoreCase) == true)
            {
                context.Context.Response.Headers[HeaderNames.CacheControl] = "no-cache, must-revalidate";
            }
        },
    });

Der Browser darf die Dateien weiterhin behalten, muss sie aber bei jedem Laden gegen den ETag prüfen: unverändert kostet das ein 304, geändert kommt sofort die neue Fassung an. Dieser Block gehört in jede Anwendung, die das Modul verwendet — sonst kommen Änderungen unvorhersehbar spät beim Benutzer an.

Zu beachten

  • log.enrichWithProperty(...) existiert, tut aber nichts — die Methode hat einen leeren Rumpf. Eigenschaften an jedem Ereignis setzt man über die Vorlage oder, für Microsoft Clarity, über die globale Variable clarityProperties.
  • Der Endpunkt ist im Client als /siwflog absolut verdrahtet. Wer EndpointPath auf dem Server ändert, muss den Appender wie oben austauschen.