Rejestrowanie proste
Napiwek
Przykład tego artykułu można pobrać z usługi GitHub.
Proste rejestrowanie programu Entity Framework Core (EF Core) umożliwia łatwe uzyskiwanie dzienników podczas opracowywania i debugowania aplikacji. Ta forma rejestrowania wymaga minimalnej konfiguracji i nie ma dodatkowych pakietów NuGet.
Napiwek
Program EF Core integruje się również z aplikacją Microsoft.Extensions.Logging, która wymaga większej konfiguracji, ale jest często bardziej odpowiednia do rejestrowania w aplikacjach produkcyjnych.
Konfigurowanie
Dostęp do dzienników programu EF Core można uzyskać z dowolnego typu aplikacji przy użyciu polecenia LogTo podczas konfigurowania wystąpienia obiektu DbContext. Ta konfiguracja jest często wykonywana w ramach przesłonięcia elementu DbContext.OnConfiguring. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Alternatywnie LogTo
można wywołać jako część AddDbContext lub podczas tworzenia DbContextOptions wystąpienia, które ma zostać przekazane do konstruktora DbContext
.
Napiwek
Funkcja OnConfiguring jest nadal wywoływana, gdy jest używany element AddDbContext lub wystąpienie DbContextOptions jest przekazywane do konstruktora DbContext. Dzięki temu idealnie nadaje się do zastosowania konfiguracji kontekstu niezależnie od sposobu konstruowania obiektu DbContext.
Kierowanie dzienników
Rejestrowanie w konsoli
LogTo
wymaga delegata Action<T> , który akceptuje ciąg. Program EF Core wywoła ten delegat z ciągiem dla każdego wygenerowanego komunikatu dziennika. Następnie do delegata należy wykonać coś z daną wiadomością.
Metoda Console.WriteLine jest często używana dla tego delegata, jak pokazano powyżej. Spowoduje to zapisanie każdego komunikatu dziennika w konsoli programu .
Rejestrowanie w oknie debugowania
Debug.WriteLine Może służyć do wysyłania danych wyjściowych do okna Debugowanie w programie Visual Studio lub innych środowiskach IDE. W tym przypadku należy użyć składni lambda, ponieważ Debug
klasa jest kompilowana poza kompilacjami wydania. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Rejestrowanie w pliku
Zapisywanie w pliku wymaga utworzenia StreamWriter pliku lub podobnego. WriteLine Następnie można użyć metody, jak w innych przykładach powyżej. Pamiętaj, aby upewnić się, że plik jest zamknięty w sposób czysty, dysponując składnik zapisywania po usunięciu kontekstu. Przykład:
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();
}
Napiwek
Rozważ użycie funkcji Microsoft.Extensions.Logging do rejestrowania plików w aplikacjach produkcyjnych.
Uzyskiwanie szczegółowych komunikatów
Dane poufne
Domyślnie program EF Core nie będzie zawierać wartości żadnych danych w komunikatach o wyjątkach. Wynika to z faktu, że takie dane mogą być poufne i mogą być ujawniane w użyciu produkcyjnym, jeśli wyjątek nie jest obsługiwany.
Jednak znajomość wartości danych, szczególnie w przypadku kluczy, może być bardzo przydatna podczas debugowania. Tę funkcję można włączyć w programie EF Core, wywołując polecenie EnableSensitiveDataLogging(). Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Szczegółowe wyjątki zapytań
Ze względu na wydajność program EF Core nie opakowuje każdego wywołania w celu odczytania wartości od dostawcy bazy danych w bloku try-catch. Jednak czasami skutkuje to wyjątkami, które są trudne do zdiagnozowania, zwłaszcza gdy baza danych zwraca wartość NULL, gdy model nie jest dozwolony.
EnableDetailedErrors Włączenie spowoduje, że program EF wprowadzi te bloki try-catch, a tym samym zapewni bardziej szczegółowe błędy. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtrowanie
Poziomy dziennika
Każdy komunikat dziennika programu EF Core jest przypisywany do poziomu zdefiniowanego przez wyliczenie LogLevel . Domyślnie proste rejestrowanie platformy EF Core zawiera każdy komunikat na Debug
poziomie lub wyższym. LogTo
Można przekazać wyższy minimalny poziom, aby odfiltrować niektóre komunikaty. Na przykład przekazywanie Information
wyników w minimalnym zestawie dzienników ograniczonych do dostępu do bazy danych i niektórych komunikatów dotyczących utrzymania domu.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Określone komunikaty
Każdy komunikat dziennika ma przypisaną wartość EventId. Dostęp do tych identyfikatorów można uzyskać z CoreEventId klasy lub RelationalEventId klasy dla komunikatów specyficznych dla relacyjnych. Dostawca bazy danych może również mieć identyfikatory specyficzne dla dostawcy w podobnej klasie. Na przykład SqlServerEventId dla dostawcy programu SQL Server.
LogTo
Można skonfigurować tak, aby rejestrować tylko komunikaty skojarzone z co najmniej jednym identyfikatorem zdarzeń. Aby na przykład rejestrować tylko komunikaty dla kontekstu zainicjowanego lub usuniętego:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Kategorie komunikatów
Każdy komunikat dziennika jest przypisywany do nazwanej kategorii rejestratora hierarchicznego. Kategorie to:
Kategoria | Wiadomości |
---|---|
Microsoft.EntityFrameworkCore | Wszystkie komunikaty programu EF Core |
Microsoft.EntityFrameworkCore.Database | Wszystkie interakcje z bazą danych |
Microsoft.EntityFrameworkCore.Database. Połączenie ion | Użycie połączenia z bazą danych |
Microsoft.EntityFrameworkCore.Database.Command | Użycie polecenia bazy danych |
Microsoft.EntityFrameworkCore.Database.Transaction | Użycie transakcji bazy danych |
Microsoft.EntityFrameworkCore.Update | Zapisywanie jednostek z wyłączeniem interakcji z bazą danych |
Microsoft.EntityFrameworkCore.Model | Wszystkie interakcje modelu i metadanych |
Microsoft.EntityFrameworkCore.Model.Validation | Walidacja modelu |
Microsoft.EntityFrameworkCore.Query | Zapytania, z wyłączeniem interakcji z bazą danych |
Microsoft.EntityFrameworkCore.Infrastructure | Zdarzenia ogólne, takie jak tworzenie kontekstu |
Microsoft.EntityFrameworkCore.Scaffolding | Inżynieria odwrotna bazy danych |
Microsoft.EntityFrameworkCore.Migrations | Migracje |
Microsoft.EntityFrameworkCore.ChangeTracking | Interakcje śledzenia zmian |
LogTo
można skonfigurować tak, aby rejestrować komunikaty tylko z co najmniej jednej kategorii. Na przykład aby rejestrować tylko interakcje z bazą danych:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Zwróć uwagę, że DbLoggerCategory klasa udostępnia hierarchiczny interfejs API do znajdowania kategorii i unika konieczności tworzenia ciągów zakodowanych w kodzie.
Ponieważ kategorie są hierarchiczne, ten przykład używający Database
kategorii będzie zawierać wszystkie komunikaty dla podkategorii Database.Connection
, Database.Command
i Database.Transaction
.
Filtry niestandardowe
LogTo
umożliwia użycie filtru niestandardowego w przypadkach, w których żadna z powyższych opcji filtrowania nie jest wystarczająca. Aby na przykład zarejestrować dowolny komunikat na poziomie Information
lub wyższym, a także komunikaty dotyczące otwierania i zamykania połączenia:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Napiwek
Filtrowanie przy użyciu filtrów niestandardowych lub użycie dowolnej z innych opcji przedstawionych tutaj jest bardziej wydajne niż filtrowanie w delegatu LogTo
. Dzieje się tak, ponieważ jeśli filtr określa, że komunikat nie powinien być rejestrowany, komunikat dziennika nie jest nawet tworzony.
Konfiguracja określonych komunikatów
Interfejs API platformy EF Core ConfigureWarnings umożliwia aplikacjom zmianę tego, co się stanie po napotkaniu określonego zdarzenia. Może to służyć do:
- Zmienianie poziomu dziennika, na którym rejestrowane jest zdarzenie
- Pomiń rejestrowanie zdarzenia całkowicie
- Zgłaszanie wyjątku w przypadku wystąpienia zdarzenia
Zmiana poziomu dziennika dla zdarzenia
W poprzednim przykładzie użyto filtru niestandardowego do rejestrowania każdego komunikatu pod LogLevel.Information
adresem, a także dwóch zdarzeń zdefiniowanych dla elementu LogLevel.Debug
. Można to osiągnąć, zmieniając poziom dziennika dwóch Debug
zdarzeń 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);
Pomijanie rejestrowania zdarzenia
W podobny sposób pojedyncze zdarzenie może zostać pominięte podczas rejestrowania. Jest to szczególnie przydatne w przypadku ignorowania ostrzeżenia, które zostało poddane przeglądowi i zrozumieniu. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Zgłaszanie zdarzenia
Na koniec program EF Core można skonfigurować do zgłaszania dla danego zdarzenia. Jest to szczególnie przydatne w przypadku zmiany ostrzeżenia na błąd. (Rzeczywiście, był to pierwotny cel ConfigureWarnings
metody, stąd nazwa). Na przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Zawartość i formatowanie wiadomości
Domyślna zawartość z LogTo
jest formatowana w wielu wierszach. Pierwszy wiersz zawiera metadane komunikatu:
- Prefiks LogLevel jako czteroznaczny
- Lokalny znacznik czasu sformatowany dla bieżącej kultury
- W EventId formularzu, który można skopiować/wkleić, aby pobrać element członkowski z CoreEventId lub jednej z innych
EventId
klas, oraz nieprzetworzonej wartości identyfikatora - Kategoria zdarzeń, zgodnie z powyższym opisem.
Przykład:
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.
Tę zawartość można dostosować, przekazując wartości z DbContextLoggerOptionselementu , jak pokazano w poniższych sekcjach.
Napiwek
Rozważ użycie funkcji Microsoft.Extensions.Logging , aby uzyskać większą kontrolę nad formatowaniem dziennika.
Korzystanie z czasu UTC
Domyślnie znaczniki czasu są przeznaczone do użycia lokalnego podczas debugowania. Użyj DbContextLoggerOptions.DefaultWithUtcTime polecenia , aby zamiast tego użyć znaczników czasu UTC niezależnego od kultury, ale zachować wszystkie inne elementy tak samo. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
Ten przykład powoduje następujące formatowanie dziennika:
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.
Rejestrowanie jednowierszowe
Czasami przydatne jest pobranie dokładnie jednego wiersza na komunikat dziennika. Można to włączyć za pomocą .DbContextLoggerOptions.SingleLine Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
Ten przykład powoduje następujące formatowanie dziennika:
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.
Inne opcje zawartości
Inne flagi w programie DbContextLoggerOptions mogą służyć do przycinania ilości metadanych zawartych w dzienniku. Może to być przydatne w połączeniu z rejestrowaniem jednowierszowym. Przykład:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
Ten przykład powoduje następujące formatowanie dziennika:
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.
Przechodzenie z platformy EF6
Proste rejestrowanie platformy EF Core różni się od Database.Log funkcji EF6 na dwa ważne sposoby:
- Komunikaty dziennika nie są ograniczone tylko do interakcji z bazą danych
- Rejestrowanie musi być skonfigurowane w czasie inicjowania kontekstu
Dla pierwszej różnicy filtrowanie opisane powyżej może służyć do ograniczania, które komunikaty są rejestrowane.
Druga różnica polega na zamierzonej zmianie w celu zwiększenia wydajności, nie generując komunikatów dziennika, gdy nie są one potrzebne. Jednak nadal istnieje możliwość uzyskania podobnego zachowania do programu EF6 przez utworzenie Log
właściwości na maszynie DbContext
, a następnie użycie jej tylko wtedy, gdy została ustawiona. Przykład:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));