Sdílet prostřednictvím


Jednoduché protokolování

Návod

Ukázku tohoto článku si můžete stáhnout z GitHubu.

Jednoduché protokolování Entity Framework Core (EF Core) lze použít k snadnému získání protokolů při vývoji a ladění aplikací. Tato forma protokolování vyžaduje minimální konfiguraci a žádné další balíčky NuGet.

Návod

EF Core se také integruje s Microsoft.Extensions.Logging, která vyžaduje více konfigurace, ale často je vhodnější pro protokolování v produkčních aplikacích.

Konfigurace

Protokoly EF Core lze zpřístupnit z jakéhokoli typu aplikace pomocí LogTo při konfiguraci instance DbContext. Tato konfigurace se obvykle provádí pomocí přepsání DbContext.OnConfiguring. Například:

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

Alternativně lze LogTo volat jako součást AddDbContext nebo při vytváření instance DbContextOptions pro předání do konstruktoru DbContext.

Návod

OnConfiguring je stále volán, když je použit AddDbContext nebo je instance DbContextOptions předána konstruktoru DbContext. Díky tomu je ideálním místem pro použití konfigurace kontextu bez ohledu na to, jak je dbContext vytvořen.

Směrování protokolů

Protokolování do konzoly

LogTo vyžaduje delegáta, který přijímá řetězec Action<T>. EF Core zavolá tohoto delegáta s řetězcem pro každou vygenerovanou zprávu protokolu. Pak je na delegátu, aby něco udělal s danou zprávou.

Metoda Console.WriteLine se často používá pro tohoto delegáta, jak je znázorněno výše. Výsledkem je zápis každé zprávy protokolu do konzoly.

Protokolování do ladicího okna

Debug.WriteLine lze použít k odeslání výstupu do okna Ladění v sadě Visual Studio nebo v jiných prostředích IDE. V tomto případě je nutné použít syntaxi lambda, protože Debug třída je zkompilována z buildů vydaných verzí. Například:

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

Protokolování do souboru

Zápis do souboru vyžaduje vytvoření StreamWriter nebo podobného prvku pro soubor. Metodu WriteLine pak můžete použít jako v dalších příkladech výše. Nezapomeňte zajistit, aby se soubor zavřel čistě tak, že zapisovač vyřazuje při odstranění kontextu. Například:

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();
}

Návod

Zvažte použití microsoft.Extensions.Logging pro protokolování do souborů v produkčních aplikacích.

Získání podrobných zpráv

Citlivá data

Ef Core ve výchozím nastavení nebude obsahovat hodnoty žádných dat ve zprávách výjimek. Důvodem je to, že taková data mohou být důvěrná a mohou být odhalena v produkčním použití, pokud není zpracována výjimka.

Znalost hodnot dat, zejména pro klíče, ale může být při ladění velmi užitečná. To lze povolit v EF Core voláním EnableSensitiveDataLogging(). Například:

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

Podrobné výjimky dotazů

Z důvodů výkonu EF Core nezabalí každé volání pro čtení hodnoty od zprostředkovatele databáze v bloku try-catch. To ale někdy vede k výjimkám, které je obtížné diagnostikovat, zejména v případě, že databáze vrátí hodnotu NULL, pokud model nepovoluje.

Zapnutí EnableDetailedErrors způsobí, že EF zavede tyto bloky try-catch a tím poskytne podrobnější informace o chybách. Například:

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

Filtrování

Úrovně protokolování

Každá zpráva protokolu EF Core je přiřazena k úrovni definované výčtem LogLevel . Ve výchozím nastavení zahrnuje jednoduché protokolování EF Core všechny zprávy na Debug úrovni nebo vyšší. LogTo může být předána vyšší minimální úroveň, aby se vyfiltrily některé zprávy. Například předáním Information dojde k vytvoření minimální sady protokolů omezených na přístup k databázi a některé rutiní systémové zprávy.

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

Konkrétní zprávy

Každé zprávě protokolu je přiřazeno EventId. K těmto ID lze přistupovat z CoreEventId třídy nebo RelationalEventId třídy pro relační zprávy. Zprostředkovatel databáze může mít také ID specifická pro zprostředkovatele v podobné třídě. Například SqlServerEventId pro poskytovatele SQL Serveru.

LogTo lze nakonfigurovat tak, aby protokolovaly pouze zprávy přidružené k jednomu nebo více ID událostí. Pokud například chcete protokolovat pouze zprávy pro kontext, který je inicializován nebo odstraněn:

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

Kategorie zpráv

Každá zpráva protokolu se přiřadí k pojmenované hierarchické kategorii protokolovacího nástroje. Kategorie jsou:

Kategorie Zprávy
Microsoft.EntityFrameworkCore Všechny zprávy EF Core
Microsoft.EntityFrameworkCore.Database Všechny interakce s databází
Microsoft.EntityFrameworkCore.Database.Connection Použití připojení k databázi
Microsoft.EntityFrameworkCore.Database.Command Použití databázového příkazu
Microsoft.EntityFrameworkCore.Database.Transaction Použití databázové transakce
Microsoft.EntityFrameworkCore.Aktualizace Ukládání entit s výjimkou interakcí s databází
Microsoft.EntityFrameworkCore.Model Všechny interakce modelu a metadat
Microsoft.EntityFrameworkCore.Model.Validation Ověření modelu
Microsoft.EntityFrameworkCore.Query Dotazy s výjimkou interakcí s databází
Microsoft.EntityFrameworkCore.Infrastructure Obecné události, například vytváření kontextu
Microsoft.EntityFrameworkCore.Scaffolding Zpětná analýza databáze
Microsoft.EntityFrameworkCore.Migrations Migrace
Microsoft.EntityFrameworkCore.ChangeTracking Interakce při sledování změn

LogTo lze nakonfigurovat tak, aby protokolovaly pouze zprávy z jedné nebo více kategorií. Pokud například chcete protokolovat pouze databázové interakce:

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

Všimněte si, že DbLoggerCategory třída poskytuje hierarchické rozhraní API pro vyhledání kategorie a zabraňuje nutnosti pevně zakódovat řetězce.

Vzhledem k tomu, že kategorie jsou hierarchické, bude toto použití Database kategorie obsahovat všechny zprávy pro podkategorie Database.Connection, Database.Commanda Database.Transaction.

Vlastní filtry

LogTo umožňuje použít vlastní filtr pro případy, kdy žádná z výše uvedených možností filtrování nestačí. Pokud například chcete protokolovat jakoukoli zprávu na úrovni Information nebo vyšší, a také zprávy pro otevření a zavření připojení:

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

Návod

Filtrování pomocí vlastních filtrů nebo použití některé z dalších možností zobrazených zde je efektivnější než filtrování v delegátu LogTo . Důvodem je to, že pokud filtr určuje, že zpráva by neměla být protokolována, zpráva protokolu se ani nevytvořila.

Konfigurace pro konkrétní zprávy

Rozhraní EF Core ConfigureWarnings API umožňuje aplikacím změnit, co se stane, když dojde k určité události. Můžete ho použít k:

  • Změna úrovně protokolu, na které se událost protokoluje
  • Zcela přeskočit protokolování události
  • Vyvolání výjimky při výskytu události

Změna úrovně protokolu pro událost

Předchozí příklad použil vlastní filtr k protokolování každé zprávy LogLevel.Information a také dvě události definované pro LogLevel.Debug. Totéž lze dosáhnout změnou úrovně protokolu dvou Debug událostí na Information:

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

Potlačení protokolování události

Podobně může být jednotlivá událost vyloučena z protokolování. To je užitečné zejména pro ignorování upozornění, které bylo zkontrolováno a srozumitelné. Například:

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

Vyvolat událost

Nakonec je možné ef Core nakonfigurovat tak, aby se pro danou událost vyhodila. To je užitečné zejména při změně upozornění na chybu. (To byl původní účel ConfigureWarnings metody, tedy název.) Například:

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

Obsah a formátování zpráv

Výchozí obsah z LogTo je naformátovaný napříč více řádky. První řádek obsahuje metadata zpráv:

  • LogLevel jako předpona o čtyřech znacích
  • Místní časová známka formátovaná pro aktuální kulturní prostředí
  • Ve formuláři EventId, který lze zkopírovat a vložit, aby se získal člen z CoreEventId nebo jedné z dalších tříd EventId, plus nezpracovaná hodnota ID.
  • Kategorie události, jak je popsáno výše.

Například:

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.

Tento obsah lze přizpůsobit předáním hodnot z DbContextLoggerOptions, jak je znázorněno v následujících částech.

Návod

Zvažte použití microsoft.Extensions.Logging pro větší kontrolu nad formátováním protokolu.

Použití času UTC

Ve výchozím nastavení jsou časová razítka navržená pro lokální použití při ladění. Použijte místo toho časová razítka UTC, která nejsou závislá na kultuře, ale ponechejte všechno ostatní stejné. Například:

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

Výsledkem tohoto příkladu je následující formátování protokolu:

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.

Jednořádkové protokolování

Někdy je užitečné získat přesně jeden řádek pro každou zprávu protokolu. To může povolit DbContextLoggerOptions.SingleLine. Například:

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

Výsledkem tohoto příkladu je následující formátování protokolu:

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.

Další možnosti obsahu

Jiné volby ve DbContextLoggerOptions lze použít ke snížení množství metadat zahrnutých v protokolu. To může být užitečné ve spojení s jednořádkovým logováním. Například:

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

Výsledkem tohoto příkladu je následující formátování protokolu:

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.

Přechod z EF6

Jednoduché protokolování EF Core se liší od Database.Log v EF6 dvěma důležitými způsoby:

  • Zprávy protokolu nejsou omezeny pouze na interakce databáze.
  • Protokolování musí být nakonfigurované v době inicializace kontextu.

U prvního rozdílu lze filtrování popsané výše použít k omezení toho, které zprávy se protokolují.

Druhým rozdílem je úmyslná změna ke zlepšení výkonu tím, že negeneruje zprávy protokolu, pokud nejsou potřeba. Přesto je ale možné získat podobné chování jako EF6 vytvořením Log vlastnosti na vaší DbContext a použijete ji pouze tehdy, když je nastavena. Například:

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

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