Paweł Łukasiewicz
2021-08-20
Paweł Łukasiewicz
2021-08-20
Udostępnij Udostępnij Kontakt
Wprowadzenie

Logowanie informacji w naszych aplikacjach może być kluczowe przy rozwiązywaniu różnych problemów. Niejednokrotnie wszystko działa w naszym środowisku deweloperskim...kiedy jednak oddajemy aplikacje w ręce testerów okazuje się, że nie wszystko spełnia swoje oczekiwania. W takim wypadku dobrze jest podejrzeć log naszej aplikacji, spojrzeć jakie zapytania zostały wykonane i jaką formę przyjęły. Dotychczas do prezentacji wyników używałem głównie narzędzia SQL Profiler - nie jest to jednak rozwiązanie optymalne.

W przypadku .NET Core oraz EF Core mamy zapewniony mechanizm automatycznej integracji dzięki czemu rejestrowanie kodu SQL i konfiguracja w zależności od środowiska wyjściowego naszego projektu jest niezwykle ułatwiona. Zanim jednak przejdziemy do instalacji odpowiedniej paczki spójrzmy na poniższy diagram pokazujący połączenie pomiędzy konktekstem naszej aplikacji konsolowej a API logowania: EF Core: mechanizm logowania

Przechodzimy do konkretów i instalacji pakietu nuget dla naszego projektu konsolowego: Microsoft.Extenstions.Logging.Console.

Po zainstalowaniu paczki musimy utworzyć statyczną instancję LoggerFactory a następnie dokonać powiązania z DbContext. Jeżeli korzystacie z wcześniejszych wersji EF Core poniższe rozwiązanie nie będzie u Was działać:

public class ApplicationDbContext : DbContext
{
	public static readonly ILoggerFactory factory
		= LoggerFactory.Create(builder => { builder.AddConsole(); });

    // Rozwiązanie dla starszych wersji EF Core
    // public static readonly ILoggerFactory loggerFactory = new LoggerFactory(new[] {
    //            new ConsoleLoggerProvider((_, __) => true, true)
    //    });

	public DbSet<Customer> Customer { get; set; }

	public ApplicationDbContext()
	{

	}

	protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
	{
		if (!optionBuilder.IsConfigured)
		{
			optionBuilder
				.UseLoggerFactory(factory) // połączenie DBContext z mechanizem logowania
				.UseSqlServer(@"Server=PAWEL;database=EFCoreDisconnected;Integrated Security=true;MultipleActiveResultSets=true").EnableSensitiveDataLogging(true);
		}
	}
W powyższym przykładzie utworzyliśmy obiekt klasy LoggerFactory i przypisaliśmy go do statycznej zmiennej typu ILoggerFactory. Jeżeli korzystacie ze starszej wersji EF Core zakomentowane rozwiązanie powinno u Was działać (do wersji 3.0). Utworzony obiekt przekazaliśmy do optionsBuilder.UserLoggerFactory() w metodzie OnConfiguring(). Podejście takie pozwoli na udostępnienie obiektowi loggerFactory informacji pochodzących z kontekstu i wyświetlenie ich w konsoli.

Osoby bardziej dociekliwe mogły zobaczyć, że w niektórych wpisach korzystam z EnableSensitiveDataLogging(true) – wszystko dlatego, że od początku przygotowania całego cyklu korzystałem z mechanizmu logowania i rejestrowania poufnych wartości takich jak wartości parametrów poleceń wysyłanych do bazy danych. Chciałem mieć pewność, że przygotowane polecenia są poprawne a śledzenie tych informacji jest domyślnie wyłączone w EF Core.

Zrozumieć działanie

Przeanalizujmy poniższy kod:

public static readonly ILoggerFactory factory
	= LoggerFactory.Create(builder => { builder.AddConsole();
LoggerFactory może w swojej definicji zawierać więcej niż jednego dostawcę rejestrowania dzięki czemu jednocześnie możemy publikować dane logowania do wielu miejscu. W naszym przypadku tworzymy prostą aplikację konsolową dlatego w konfiguracji wykorzystaliśmy AddConsole().

Nieco niżej w naszej konfiguracji dokonaliśmy powiązania utworzonego obiektu ILoggerFactory z kontekstem wykorzystując DbContextOptionsBuilder:

optionBuilder
	.UseLoggerFactory(factory)
Dzięki temu dokonaliśmy powiązania kontekstu z LoggerFactory, który zawiera w definicji dostawcę logowania informacji, tj. konsolę. Za każdym razem gdy DbContext wykona jakąkolwiek akcję zobaczymy rezultat w konsoli.

Wykonajmy zatem parametryzowane polecenie w celu prześledzenia wyników operacji w konsoli:

using(var context = new ApplicationDbContext())
{
	string name = "UpdatedPaweł";

	var updatedRows = context.Database
		.ExecuteSqlInterpolated($"UPDATE Customer SET FullName = {name} where Id = 1");
}
Spójrzcie na rezultat widoczny w konsoli:
warn: Microsoft.EntityFrameworkCore.Model.Validation[10400]
      Sensitive data logging is enabled. Log entries and exception messages may
include sensitive application data; this mode should only be enabled during deve
lopment.
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 5.0.0 initialized 'ApplicationDbContext' using provi
der 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLogging
Enabled
Security Warning: The negotiated TLS 1.0 is an insecure protocol and is supporte
d for backward compatibility only. The recommended protocol version is TLS 1.2 a
nd later.
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (36ms) [Parameters=[@p0='UpdatedPaweł' (Size = 4000)],
CommandType='Text', CommandTimeout='30']
      UPDATE Customer SET FullName = @p0 where Id = 1

Filtrowanie informacji

Potrzebujemy wszystkich informacji zawartych powyżej? Raczej nie...

Czasami zależmy nam tylko na filtrowaniu określonych kategorii zdarzeń. W Entity Framework Core możemy dokonać konfiguracji określając kategorię i poziom rejestrowanych zdarzeń. W tym celu posłużmy się klasą DbLoggerCategory w której znajdują się poniższe kategorie rejestrowania informacji:

Kategoria Opis
Database.Command Informacje dotyczące wykonywanych poleceń.
Database.Connection Informacje dotyczące operacji związanych z połączeniem z bazą danych.
Database.Transaction Informacje dotyczące transakcji wykonywanych na bazie danych.
Infrastructure Informacje dotyczące komunikatów związanych z infrastrukturą Entity Framework.
Migration Informacje dotyczące procesu migracji.
Model Informacje dotyczące tworzenia modelu i metadanych.
Query Informacje dotyczące zapytań (z wyłączeniem wygenerowanego kodu SQL).
Scaffolding Informacje dotyczące inżynierii wstycznej i operacji tworzenia rusztowań (scaffolding).
Update Informacje związane z komunikatami metody DbContext.SaveChanges().

W celu implementacji powyższych założeń musimy nieznacznie zmodyfikować konfigurację LoggerFactory w poniższy sposób określając kategorię i poziom filtrowania informacji:

public static readonly ILoggerFactory factory
	= LoggerFactory.Create(builder => { builder.AddConsole()
		.AddFilter((category, level) =>
		    category == DbLoggerCategory.Database.Command.Name &&
		    level == LogLevel.Information); });
Tym razem dostajemy informacje dotyczącą jedynie wykonywanych poleceń:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
    Executed DbCommand (36ms) [Parameters=[@p0='UpdatedPaweł' (Size = 4000)],
    CommandType='Text', CommandTimeout='30']
    UPDATE Customer SET FullName = @p0 where Id = 1