Jednoduché protokolování
Tip
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.
Tip
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
K protokolům EF Core je možné přistupovat z libovolného typu aplikace prostřednictvím LogTo při konfiguraci instance DbContextu. Tato konfigurace se běžně provádí přepsáním DbContext.OnConfiguring. Příklad:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Alternativně LogTo
lze volat jako součást AddDbContext nebo při vytváření DbContextOptions instance pro předání konstruktoru DbContext
.
Tip
OnConfiguring je stále volána, když AddDbContext je použit nebo DbContextOptions instance je předán DbContext konstruktoru. 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 Action<T> , který přijímá řetězec. 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 okna ladění
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í. Pří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í nebo podobného StreamWriter souboru. 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. Pří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();
}
Tip
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(). Pří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 poskytne tak podrobnější chyby. Příklad:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtrování
Úrovně protokolů
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. Předáním výsledků je například Information
minimální sada protokolů omezených na přístup k databázi a některé zprávy o úklidu.
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 inicializaci nebo vyřazení kontextu:
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:
Kategorie | Zprávy |
---|---|
Microsoft.EntityFrameworkCore | Všechny zprávy EF Core |
Microsoft.EntityFrameworkCore.Database | Všechny interakce databáze |
Microsoft.EntityFrameworkCore.Database. Připojení ion | 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.Update | 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 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.Command
a 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);
Tip
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
- Přeskočení protokolování události celkem
- 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 potlačena z protokolování. To je užitečné zejména pro ignorování upozornění, které bylo zkontrolováno a srozumitelné. Příklad:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Vyvolání události
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.) Pří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:
- Jako LogLevel předpona čtyř znaků
- Místní časové razítko formátované pro aktuální jazykovou verzi
- Ve EventId formuláři, který lze zkopírovat nebo vložit, aby získal člena z CoreEventId nebo jedné z dalších
EventId
tříd, plus nezpracovaná hodnota ID - Kategorie události, jak je popsáno výše.
Pří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.
Tip
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 místní spotřebu při ladění. Místo toho používejte DbContextLoggerOptions.DefaultWithUtcTime časová razítka UTC nezávislá na jazykové verzi, ale ponechejte všechno ostatní stejné. Pří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.
Protokolování s jedním řádkem
Někdy je užitečné získat přesně jeden řádek pro každou zprávu protokolu. To může povolit DbContextLoggerOptions.SingleLine. Pří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
Další příznaky lze DbContextLoggerOptions použít k oříznutí množství metadat zahrnutých v protokolu. To může být užitečné ve spojení s jednořádkovým protokolováním. Pří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 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 ve vaší DbContext
a jeho následném použití pouze v případě, že byla nastavena. Příklad:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));