Server einrichten

So ist Serilog in dieser Anwendung (FMH.Komponente.WebUI) konfiguriert — die Ausschnitte stammen unverändert aus Program.cs, appsettings.json und der Projektdatei.

Pakete

<ItemGroup>
  <PackageReference Include="Serilog.AspNetCore" />
  <PackageReference Include="Serilog.Enrichers.ClientInfo" />
  <PackageReference Include="Serilog.Settings.Configuration" />
  <PackageReference Include="Serilog.Sinks.Seq" />
</ItemGroup>

<ItemGroup>
  <ProjectReference Include="..\FMH.Komponente.Serilog.Ingestion\FMH.Komponente.Serilog.Ingestion.csproj" />
</ItemGroup>
Paket Wofür
Serilog.AspNetCore UseSerilog am Host, Request-Logging, Console-Sink
Serilog.Settings.Configuration ReadFrom.Configuration(...) — der Serilog-Abschnitt der appsettings
Serilog.Sinks.Seq Zielsystem Seq
Serilog.Enrichers.ClientInfo WithClientIp() und WithRequestHeader(...)
FMH.Komponente.Serilog.Ingestion der Endpunkt /siwflog für die Browser-Meldungen

Die Versionen stehen zentral in Directory.Packages.props; im Projekt wird nur der Paketname angegeben.

Schritt 1: Serilog als Logging-Pipeline

builder.Host.UseSerilog((context, services, config) => config.Enrich.WithRequestHeader("User-Agent")
    .Enrich.WithClientIp()
    .ReadFrom.Configuration(configuration)
    .WriteTo.Console(theme: ConsoleTheme.None));

ReadFrom.Configuration holt Mindestlevel, feste Eigenschaften und Sinks aus den appsettings; WriteTo.Console kommt zusätzlich im Code dazu, damit auch ohne Seq etwas sichtbar ist.

Schritt 2: den Endpunkt für den Browser öffnen

Das ist der Schritt, der am leichtesten vergessen wird:

using FMH.Komponente.Serilog.Ingestion;

var app = builder.Build();
app.UseSiwfLogIngestion();

Ohne app.UseSiwfLogIngestion() protokolliert der Server unverändert weiter — aber POST /siwflog läuft ins Leere und jede Meldung aus dem Browser geht verloren. Der Fehler ist still: es gibt keine Ausnahme, nur fehlende Einträge.

Die Erweiterungsmethode liegt im Namespace FMH.Komponente.Serilog.Ingestion.

Wo in der Pipeline

In dieser Anwendung steht der Aufruf direkt nach builder.Build() und damit vor UseHeaderSecurity, UseHttpsRedirection, UseRouting und UseAuthorization. Zwei Folgen, die man kennen sollte:

  • Der Endpunkt ist anonym erreichbar. Wer das nicht will, verschiebt den Aufruf hinter UseAuthorization — dann muss der Browser allerdings authentifiziert sein, bevor er etwas melden darf.
  • Er ist nicht geroutet. Die Middleware vergleicht den Pfad selbst und reicht alles Übrige weiter.

Schritt 3: appsettings

"Serilog": {
  "MinimumLevel": {
    "Default": "Debug",
    "Override": {
      "Microsoft": "Error"
    }
  },
  "Properties": {
    "Release": "0.0.0.0-development",
    "App": "FMH.Komponente.WebUI"
  },
  "WriteTo": [
    {
      "Name": "Seq",
      "Args": {
        "ServerUrl": "http://127.0.0.1:5341",
        "ApiKey": ""
      }
    }
  ]
}

Properties landet an jedem Ereignis — so lassen sich in Seq mehrere Anwendungen und Releases auseinanderhalten. Der Sink wird über seinen Namen gefunden; ein Using-Abschnitt ist nicht nötig, solange das Sink-Paket referenziert ist.

Der klassische Logging:LogLevel-Abschnitt steht in dieser Anwendung noch daneben, wirkt neben Serilog aber praktisch nicht mehr.

Optionen der Ingestion-Middleware

UseSiwfLogIngestion() kommt hier ohne Argumente aus. Alle Werte lassen sich über ein Lambda setzen:

app.UseSiwfLogIngestion(optionen =>
{
    optionen.EndpointPath = "/protokoll";
    optionen.EventBodyLimitBytes = 256 * 1024;
    optionen.ClientLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Warning);
});
Option Standard Wirkung
EndpointPath /siwflog Der Pfad, auf den die Middleware hört. Exakter Vergleich — /siwflog/ oder /siwflog/mehr treffen nicht. Wird er geändert, muss auch der Client umgestellt werden.
Logger Log.Logger Ziel-Logger. Ohne Angabe der globale Logger; deshalb muss UseSerilog vorher laufen.
OriginPropertyName Origin Jedes Ereignis wird mit Origin = "Client" angereichert. Schickt der Browser die Eigenschaft selbst mit, wird sie verworfen — die Herkunft lässt sich nicht fälschen. null schaltet die Markierung ab.
EventBodyLimitBytes null Obergrenze je Zeile. Standard ist unbegrenzt — für einen öffentlich erreichbaren Endpunkt einen Wert setzen. Zu lange Zeilen werden einzeln verworfen und über SelfLog gemeldet.
ClientLevelSwitch null Filtert eingehende Ereignisse und schickt das akzeptierte Mindestlevel in der Antwort zurück. Nur der WASM-Sink wertet diesen Rückkanal aus.

Was der Endpunkt antwortet

  • Erfolg: 201 Created, ohne Inhalt.
  • Mit ClientLevelSwitch: 201 und im Rumpf {"MinimumLevelAccepted":"Warning"}.
  • Eine defekte Zeile im Stapel wird übersprungen und über SelfLog gemeldet; die übrigen Ereignisse des Stapels kommen trotzdem an, die Antwort bleibt 201.

Fehler beim Verarbeiten werden also bewusst nicht an den Browser zurückgemeldet — Logging darf die Anwendung nicht stören. Wer beim Einrichten prüfen will, ob wirklich etwas ankommt, schaltet Serilog.Debugging.SelfLog ein:

Serilog.Debugging.SelfLog.Enable(Console.Error);