Freigeben über


Einfache Protokollierung

Tipp

Sie können das Beispiel dieses Artikels von GitHub herunterladen.

Die einfache Protokollierung von Entity Framework Core (EF Core) kann zum einfachen Abrufen von Protokollen beim Entwickeln und Debuggen von Anwendungen verwendet werden. Diese Form der Protokollierung erfordert minimale Konfiguration und keine zusätzlichen NuGet-Pakete.

Tipp

EF Core ist auch in Microsoft.Extensions.Logging integriert, was eine größere Konfiguration erfordert, aber häufig für die Protokollierung in Produktionsanwendungen besser geeignet ist.

Konfiguration

Auf EF Core-Protokolle kann über jede Art von Anwendung mithilfe von LogTo zugegriffen werden, wenn eine DbContext-Instanz konfiguriert wird. Diese Konfiguration wird üblicherweise in einer Überschreibung von DbContext.OnConfiguring durchgeführt. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

Alternativ kann LogTo als Teil von AddDbContext aufgerufen werden oder beim Erstellen einer DbContextOptions Instanz, um an den DbContext Konstruktor übergeben zu werden.

Tipp

OnConfiguring wird weiterhin aufgerufen, wenn AddDbContext verwendet wird oder eine DbContextOptions-Instanz an den DbContext-Konstruktor übergeben wird. Dadurch ist es der ideale Ort, um die Kontextkonfiguration anzuwenden, unabhängig davon, wie der DbContext erstellt wird.

Leiten von Logdateien

Protokollierung an der Konsole

LogTo erfordert einen Action<T> Delegat, der einen String akzeptiert. EF Core ruft diesen Delegat mit einer Zeichenfolge für jede generierte Protokollnachricht auf. Es liegt dann an der Stellvertretung, etwas mit der angegebenen Nachricht zu tun.

Die Console.WriteLine Methode wird häufig für diesen Delegat verwendet, wie oben gezeigt. Dies führt dazu, dass jede Protokollnachricht in die Konsole geschrieben wird.

Protokollierung im Debugfenster

Debug.WriteLine kann verwendet werden, um die Ausgabe an das Debugfenster in Visual Studio oder andere IDEs zu senden. Die Lambda-Syntax muss in diesem Fall verwendet werden, da die Debug Klasse aus Releasebuilds kompiliert wird. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(message => Debug.WriteLine(message));

Anmelden bei einer Datei

Zum Schreiben in eine Datei muss ein StreamWriter oder ein ähnliches Objekt für die Datei erstellt werden. Die WriteLine Methode kann dann wie in den anderen Beispielen oben verwendet werden. Denken Sie daran, sicherzustellen, dass die Datei sauber geschlossen wird, indem Sie den Writer ordnungsgemäß entsorgen, wenn der Kontext geschlossen wird. Beispiel:

private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(_logStream.WriteLine);

public override void Dispose()
{
    base.Dispose();
    _logStream.Dispose();
}

public override async ValueTask DisposeAsync()
{
    await base.DisposeAsync();
    await _logStream.DisposeAsync();
}

Tipp

Erwägen Sie die Verwendung von Microsoft.Extensions.Logging für die Protokollierung bei Dateien in Produktionsanwendungen.

Abrufen detaillierter Nachrichten

Sensible Daten

Standardmäßig enthält EF Core nicht die Werte von Daten in Ausnahmemeldungen. Dies liegt daran, dass solche Daten vertraulich sein können und in der Produktionsverwendung offenbart werden können, wenn eine Ausnahme nicht behandelt wird.

Das Wissen von Datenwerten, insbesondere für Schlüssel, kann jedoch beim Debuggen sehr hilfreich sein. Dies kann in EF Core durch Aufrufen von EnableSensitiveDataLogging() aktiviert werden. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableSensitiveDataLogging();

Detaillierte Abfrageausnahmen

Aus Leistungsgründen umschließt EF Core nicht jeden Aufruf, um einen Wert vom Datenbankanbieter in einem Try-Catch-Block zu lesen. Dies führt jedoch manchmal zu Ausnahmen, die schwer zu diagnostizieren sind, insbesondere, wenn die Datenbank einen NULL-Wert zurückgibt, wenn das Modell nicht zulässig ist.

Das Aktivieren von EnableDetailedErrors führt dazu, dass EF dann die Try-Catch-Blöcke einführt und dadurch detailliertere Fehler bereitstellt. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();

Filterung

Protokollebenen

Jede EF Core-Protokollnachricht wird einer durch die LogLevel Enumeration definierten Ebene zugewiesen. Standardmäßig enthält die einfache Protokollierung in EF Core jede Nachricht ab der Ebene Debug oder höher. LogTo kann eine höhere Mindeststufe übergeben werden, um einige Nachrichten herauszufiltern. Beispiel: Das Übergeben von Information führt zu einem minimalen Set von Logs, die auf den Datenbankzugriff beschränkt sind, und einigen Verwaltungsmeldungen.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

Bestimmte Nachrichten

Jeder Protokollnachrichten wird ein EventId zugewiesen. Auf diese IDs kann über die CoreEventId Klasse oder die RelationalEventId Klasse für relationale Nachrichten zugegriffen werden. Ein Datenbankanbieter verfügt möglicherweise auch über anbieterspezifische IDs in einer ähnlichen Klasse. Beispiel: SqlServerEventId für den SQL Server-Anbieter.

LogTo kann so konfiguriert werden, dass nur die Nachrichten protokolliert werden, die einer oder mehreren Ereignis-IDs zugeordnet sind. So protokollieren Sie beispielsweise nur Nachrichten für den Kontext, der initialisiert oder verworfen wird:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });

Nachrichtenkategorien

Jede Protokollnachricht wird einer benannten hierarchischen Loggerkategorie zugewiesen. Die Kategorien sind:

Kategorie Meldungen
Microsoft.EntityFrameworkCore Alle EF Core-Nachrichten
Microsoft.EntityFrameworkCore.Database Alle Datenbankinteraktionen
Microsoft.EntityFrameworkCore.Database.Connection Verwendung einer Datenbankverbindung
Microsoft.EntityFrameworkCore.Database.Command Verwendung eines Datenbankbefehls
Microsoft.EntityFrameworkCore.Database.Transaction Verwendung einer Datenbanktransaktion
Microsoft.EntityFrameworkCore.Update Speichern von Entitäten mit Ausnahme von Datenbankinteraktionen
Microsoft.EntityFrameworkCore.Model Alle Modell- und Metadateninteraktionen
Microsoft.EntityFrameworkCore.Model.Validation Modellvalidierung
Microsoft.EntityFrameworkCore.Query Abfragen mit Ausnahme von Datenbankinteraktionen
Microsoft.EntityFrameworkCore.Infrastructure Allgemeine Ereignisse, wie z. B. Kontextgestaltung
Microsoft.EntityFrameworkCore.Scaffolding Reverse Engineering der Datenbank
Microsoft.EntityFrameworkCore.Migrationen Migrationen
Microsoft.EntityFrameworkCore.ChangeTracking Interaktionen zur Änderungsnachverfolgung

LogTo kann so konfiguriert werden, dass nur die Nachrichten aus einer oder mehreren Kategorien protokolliert werden. Um beispielsweise nur Datenbankinteraktionen zu protokollieren:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });

Beachten Sie, dass die DbLoggerCategory Klasse eine hierarchische API zum Suchen einer Kategorie bereitstellt und die Notwendigkeit von hartcodierten Zeichenfolgen vermeidet.

Da Kategorien hierarchisch sind, enthält dieses Beispiel, in dem die Database Kategorie verwendet wird, alle Nachrichten für die Unterkategorien Database.Connection, Database.Commandund Database.Transaction.

Benutzerdefinierte Filter

LogTo ermöglicht die Verwendung eines benutzerdefinierten Filters für Fälle, in denen keine der oben genannten Filteroptionen ausreichend ist. Zum Protokollieren von Nachrichten auf Ebene Information oder höher, sowie von Nachrichten zum Öffnen und Schließen einer Verbindung:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(
            Console.WriteLine,
            (eventId, logLevel) => logLevel >= LogLevel.Information
                                   || eventId == RelationalEventId.ConnectionOpened
                                   || eventId == RelationalEventId.ConnectionClosed);

Tipp

Das Filtern mit benutzerdefinierten Filtern oder mit einer der hier gezeigten Optionen ist effizienter als das Filtern im LogTo Delegate. Dies liegt daran, dass, wenn der Filter bestimmt, dass die Nachricht nicht protokolliert werden soll, die Protokollnachricht nicht einmal erstellt wird.

Konfiguration für bestimmte Nachrichten

Mit der EF Core-API ConfigureWarnings können Anwendungen ändern, was passiert, wenn ein bestimmtes Ereignis auftritt. Dies kann verwendet werden, um:

  • Ändern der Protokollebene, auf der das Ereignis protokolliert wird
  • Protokollierung des Ereignisses vollständig überspringen
  • Auslösen einer Ausnahme, wenn das Ereignis auftritt

Ändern der Protokollebene für ein Ereignis

Im vorherigen Beispiel wurde ein benutzerdefinierter Filter verwendet, um jede Nachricht zu LogLevel.Information zu protokollieren sowie zwei Ereignisse für LogLevel.Debug zu definieren. Dasselbe kann erreicht werden, indem die Protokollierungsstufe bei den beiden Debug-Ereignissen auf Information geändert wird.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(
            b => b.Log(
                (RelationalEventId.ConnectionOpened, LogLevel.Information),
                (RelationalEventId.ConnectionClosed, LogLevel.Information)))
        .LogTo(Console.WriteLine, LogLevel.Information);

Unterdrücken der Protokollierung eines Ereignisses

Auf ähnliche Weise kann ein einzelnes Ereignis aus der Protokollierung unterdrückt werden. Dies ist besonders nützlich, um eine Warnung zu ignorieren, die überprüft und verstanden wurde. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
        .LogTo(Console.WriteLine);

Werfen für eine Veranstaltung

Schließlich kann EF Core so konfiguriert werden, dass es für ein bestimmtes Ereignis ausgelöst wird. Dies ist besonders nützlich, um eine Warnung in einen Fehler zu ändern. (Tatsächlich war dies der ursprüngliche Zweck der ConfigureWarnings Methode, daher der Name.) Zum Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
        .LogTo(Console.WriteLine);

Nachrichteninhalte und -formatierung

Der Standardinhalt von LogTo wird über mehrere Zeilen formatiert. Die erste Zeile enthält Nachrichtenmetadaten:

  • Das LogLevel Präfix als vierstelliges Zeichen
  • Ein lokaler Zeitstempel, formatiert für die aktuelle Kultur
  • Das EventId in der Form, das kopiert und eingefügt werden kann, um das Element aus CoreEventId oder einer der anderen EventId Klassen abzurufen, plus den rohen ID-Wert.
  • Die Ereigniskategorie, wie oben beschrieben.

Beispiel:

info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

Dieser Inhalt kann angepasst werden, indem Werte von DbContextLoggerOptions weitergegeben werden, wie in den folgenden Abschnitten gezeigt.

Tipp

Erwägen Sie die Verwendung von Microsoft.Extensions.Logging , um mehr Kontrolle über die Protokollformatierung zu haben.

Verwenden der UTC-Zeit

Standardmäßig sind Zeitstempel für den lokalen Verbrauch beim Debuggen ausgelegt. Verwenden Sie DbContextLoggerOptions.DefaultWithUtcTime stattdessen kulturunabhängige UTC-Zeitstempel, aber behalten Sie alles andere gleich. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithUtcTime);

In diesem Beispiel wird die folgende Protokollformatierung angezeigt:

info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

Protokollierung mit einer einzelnen Zeile

Manchmal ist es nützlich, genau eine Zeile pro Protokollnachricht abzurufen. Dies kann durch DbContextLoggerOptions.SingleLineaktiviert werden. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);

In diesem Beispiel wird die folgende Protokollformatierung angezeigt:

info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.

Weitere Inhaltsoptionen

Andere Flags in DbContextLoggerOptions können verwendet werden, um die Menge der im Protokoll enthaltenen Metadaten zu kürzen. Dies kann in Verbindung mit der Einzelzeilenprotokollierung hilfreich sein. Beispiel:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);

In diesem Beispiel wird die folgende Protokollformatierung angezeigt:

2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.

Wechsel von EF6

Die einfache Protokollierung in EF Core unterscheidet sich in zwei wichtigen Punkten von EF6: Database.Log

  • Protokollmeldungen sind nicht nur auf Datenbankinteraktionen beschränkt
  • Die Protokollierung muss während der Initialisierung des Kontexts konfiguriert werden.

Für den ersten Unterschied kann die oben beschriebene Filterung verwendet werden, um zu begrenzen, welche Nachrichten protokolliert werden.

Der zweite Unterschied ist eine beabsichtigte Änderung, um die Leistung zu verbessern, indem keine Protokollmeldungen generiert werden, wenn sie nicht benötigt werden. Es ist jedoch weiterhin möglich, ein ähnliches Verhalten wie EF6 zu erhalten, indem eine Log-Eigenschaft in Ihrem DbContext erstellt und nur verwendet wird, wenn sie gesetzt ist. Beispiel:

public Action<string> Log { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(s => Log?.Invoke(s));