1. Wprowadzenie
W programowaniu logowanie — to nie tylko "wypisać coś na konsolę". Logowanie to twój czarny skrzynka, GPS-tracker i wskaźnik w jednym. Bez logów duży program staje się czarną skrzynką-zagadką: "dlaczego serwis się zawiesił?", "dlaczego użytkownik nie otrzymał maila?", "czy w ogóle ktoś uruchamiał ten moduł w ciągu ostatnich 3 miesięcy?" — na wszystkie te pytania często można odpowiedzieć tylko jeśli logi są przechowywane i dostępne.
Po co logowanie w praktyce?
Wyszukiwanie i diagnostyka błędów
Jeśli program się zwiesił — logi opowiedzą, gdzie i dlaczego. Jeśli nie zwiesił się, ale działa dziwnie — logi wskażą kroki, które do tego doprowadziły.
Monitoring stanu
Z logów można dowiedzieć się, czy system działa teraz, ile jest zapytań do serwera, czy pojawiają się błędy, kto i co robi.
Bezpieczeństwo
Logowanie prób nieautoryzowanego dostępu, podejrzanej aktywności, błędów uwierzytelniania.
Audit
Kto, co i kiedy zrobił. Jeśli zwolnił się zły admin i usunął dane, po logach można wszystko odtworzyć (albo przynajmniej zrozumieć, co dokładnie zrobił).
Wsparcie w development i operacjach
Logi to nie tylko dla programisty, ale też dla testera, operatora, admina. Za pół roku powiesz do siebie: 'Dzięki, że dodałeś logi!'
Od Console.WriteLine do produkcyjnego logowania
Pierwsza reakcja początkującego: "A co, po prostu Console.WriteLine nie wystarczy?". Dla małych programów edukacyjnych to faktycznie bywa ok. Ale kiedy aplikacja działa na serwerze, pracuje równolegle, albo korzysta z niej dziesiątki i setki użytkowników, konsola już nie pomoże. Trzeba:
- Oddzielić ważne informacje od debugowych.
- Wysyłać logi nie tylko na konsolę, ale też do plików, baz danych, scentralizowanych systemów.
- Zmieniac poziom szczegółowości logów (minimum — tylko błędy, maksimum — wszystko).
- Automatycznie uzupełniać wpisy datą, czasem i dodatkowymi informacjami.
- Elastycznie konfigurować.
- A przede wszystkim — nie zmieniać kodu, żeby przekierować logi w inne miejsce.
Właśnie wtedy zaczyna się era "prawdziwych" loggerów.
2. Podstawy nowoczesnego systemu logowania w .NET
W ekosystemie .NET istnieje standardowy, potężny i elastyczny framework do logowania — Microsoft.Extensions.Logging. To część całej "nowej fali" bibliotek .NET, które pojawiły się razem z ASP.NET Core, ale teraz używa się ich wszędzie: na serwerze, na desktopie, a nawet w aplikacjach mobilnych.
Co jest w nim dobrego?
- Abstrakcja — nie wiąże cię z konkretną implementacją (logger może pisać do pliku, konsoli, do chmury, albo jednocześnie wszędzie).
- Wsparcie poziomów logowania (Trace, Debug, Information, Warning, Error, Critical).
- Integracja z DI-containerem i wsparcie dla nowoczesnych aplikacji .NET.
- Bogaty ekosystem rozszerzeń: wsparcie strukturacji logów, zaawansowanych formatterów i integracji.
Podstawowe pojęcia i klasy
Poznajmy kluczowe obiekty, które będą potrzebne do pracy z Microsoft.Extensions.Logging:
| Klasa/Interfejs | Przeznaczenie |
|---|---|
|
Interfejs do logowania, typowany względem klasy |
|
Ogólny interfejs loggera |
|
Fabryka tworzenia instancji loggerów |
|
Pozwala konfigurować logowanie w aplikacji |
|
Enum poziomów logów (Trace, Debug, Information, ...) |
Poziomy logowania
Podział wiadomości według ważności pozwala filtrować "szum" i znaleźć to, co potrzebne:
| Poziom (LogLevel) | Do czego używać? |
|---|---|
|
Najbardziej szczegółowe debugowanie, "szum", dane czasowe |
|
Podstawowe informacje debugowe |
|
Kluczowe zdarzenia dla normalnej pracy |
|
Ostrzeżenia o potencjalnych problemach, ale system działa dalej |
|
Błędy wymagające uwagi, ale aplikacja nadal działa |
|
Krytyczne awarie zagrażające całemu systemowi |
3. Praktyka: dodajemy logowanie do naszej aplikacji
Nie będziemy pisać abstrakcyjnego kodu — rozbudujemy nasze demo. Załóżmy, że mamy prostą klasę kalkulatora, którą rozszerzamy w trakcie kursu.
Przykład: podstawowy kalkulator
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
// Pozostałe metody...
}
Teraz wbudujemy w niego logowanie. Do tego potrzebujemy interfejsu ILogger<Calculator>, który będziemy otrzymywać z zewnątrz (np. przez Dependency Injection, DI).
using Microsoft.Extensions.Logging;
public class Calculator
{
private readonly ILogger<Calculator> _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Add(int a, int b)
{
int result = a + b;
_logger.LogInformation("Wykonano dodawanie: {A} + {B} = {Result}", a, b, result);
return result;
}
}
Ciekawostka:
Zamiast konkatenacji stringów loggerzy wspierają szablony i nazwane parametry ({A}, {B}, {Result}), co pozwala robić logi strukturalnymi i wygodnymi do automatycznej analizy i wyszukiwania.
4. Jak stworzyć i skonfigurować logger w aplikacji konsolowej
1. Dodajemy pakiety NuGet
W projekcie będziesz potrzebować:
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Logging.Console (jeśli chcesz pisać na konsolę)
- (opcjonalnie) inne providery, jeśli potrzebujesz
2. Konfigurujemy logger
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
// Tworzymy DI-container
var serviceProvider = new ServiceCollection()
.AddLogging(builder => {
builder.AddConsole(); // Wyjście na konsolę
builder.SetMinimumLevel(LogLevel.Debug); // Minimalny poziom logów
})
.BuildServiceProvider();
// Pobieramy instancję loggera dla potrzebnego typu
var logger = serviceProvider.GetRequiredService<ILogger<Calculator>>();
var calculator = new Calculator(logger);
calculator.Add(5, 3); // W logach pojawi się wiadomość
Typowy błąd początkujących
"Dlaczego log nie pojawia się w konsoli?" — sprawdź, czy dodany jest odpowiedni provider (.AddConsole()) i czy poprawnie ustawiony jest minimalny poziom logowania (SetMinimumLevel). Jeśli poziom jest wyższy, wiadomości mogą być po prostu "odfiltrowane"!
5. Przydatne niuanse
Logujemy błędy i nietypowe sytuacje
Załóżmy, że chcemy zalogować dzielenie przez zero. Dodajmy odpowiednią metodę:
public int Divide(int a, int b)
{
if (b == 0)
{
_logger.LogError("Próba dzielenia przez zero! a={A}", a);
throw new DivideByZeroException();
}
int result = a / b;
_logger.LogInformation("Wykonano dzielenie: {A} / {B} = {Result}", a, b, result);
return result;
}
Po co to robić?
W realnej aplikacji, kiedy coś idzie nie tak, logi z poziomem Error zwykle przyciągają specjalną uwagę: są automatycznie wysyłane do adminów, podświetlane w monitoringu i używane do alertów/powiadomień.
Użycie kategorii i znaczników (scopes)
Logger w .NET wspiera tzw. scopes — to dodatkowe metadata, które automatycznie dodawane są do wszystkich logów w obrębie określonego bloku kodu. Na przykład, gdy obsługujesz request webowy lub sesję użytkownika, możesz dodać identyfikator do scope.
using (_logger.BeginScope("UserId: {UserId}", 42))
{
_logger.LogInformation("Rozpoczęto przetwarzanie danych użytkownika");
// ...
}
Wszystkie wiadomości w bloku otrzymają dodatkową etykietę UserId: 42, co później ułatwi znajdowanie logów po użytkownikach lub operacjach.
Przykład: poziomy logowania w praktyce
_logger.LogTrace("To jest Trace — prawie nikt tego nie zobaczy");
_logger.LogDebug("To jest Debug — dla deweloperów");
_logger.LogInformation("To jest Information — dla zdarzeń normalnej pracy");
_logger.LogWarning("To jest Warning — ostrzeżenie o możliwym problemie");
_logger.LogError("To jest Error — błąd wymagający uwagi");
_logger.LogCritical("To jest Critical — System płonie, potrzebny strażak!");
Jeśli ustawiłeś SetMinimumLevel(LogLevel.Information), zobaczysz tylko wiadomości poziomów Information, Warning, Error, Critical.
Tip:
Zostawiaj logi Trace i Debug do szczegółowej diagnostyki podczas developmentu, a na produkcji zwykle włączaj tylko Information i wyżej, żeby nie rozdęć rozmiaru logów i nie zgubić ważnych informacji w "szumie".
Schemat wizualny: architektura nowoczesnego logowania
graph TD
A[Kod aplikacji] --ILogger<YourClass>--> B[Microsoft.Extensions.Logging]
B --> C1[Console Provider]
B --> C2[File Provider]
B --> C3[Cloud/Database Provider]
C1 -.-> D1[Logi w konsoli]
C2 -.-> D2[Logi w pliku]
C3 -.-> D3[Logi w systemie monitoringu]
subgraph Providers
C1
C2
C3
end
6. Dodatkowe możliwości i rozszerzenia
Strukturalne logowanie:
Wartości parametrów można przechowywać nie tylko w stringu, ale też jako klucz-wartość, co umożliwia wyszukiwanie i agregację po tych parametrach (np. przez Seq, ELK/ElasticSearch czy Application Insights).
Providery logowania:
Można dodać dziesiątki różnych providerów: plikowy, dla Windows EventLog, Azure itd.
Konfigurowanie logowania przez appsettings.json
W ASP.NET Core logi można elastycznie konfigurować z pliku konfiguracyjnego, bez rekompilacji aplikacji.
Przykład ustawienia minimalnego poziomu logowania przez config (appsettings.json):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MyApp.Calculator": "Debug",
"Microsoft": "Warning"
}
}
}
7. Typowe błędy przy pracy z Microsoft.Extensions.Logging
Błąd nr 1: Nieprawidłowy wybór poziomu logowania.
Używanie LogInformation dla błędów zamiast LogError lub LogCritical utrudnia znajdowanie problemów na produkcji.
Błąd nr 2: Ignorowanie strukturalnego logowania.
Konkatenacja stringów zamiast szablonów z placeholderami ({Parameter}) odbiera korzyści strukturalnego logowania, takie jak wyszukiwanie po parametrach.
Błąd nr 3: Nieprawidłowa konfiguracja poziomów logowania.
Jeśli minimalny poziom logów jest ustawiony zbyt wysoko (np. Warning zamiast Debug), ważne wiadomości mogą zostać odfiltrowane.
Błąd nr 4: Nadmiarowe lub zbyt skąpe logowanie.
Zbyt szczegółowe logi (np. Trace na produkcji) tworzą "szum", a brak logów utrudnia diagnostykę.
GO TO FULL VERSION