Autor: Jarosław Kowalski <jaak@jkowalski.net>
Zapewne każdy z nas napisał kiedyś kod podobny do poniższego:
Instrukcje Console.WriteLine() pełnią tu rolę instrukcji śledzących, które informują o działaniu programu. Na ich podstawie możemy się zorientować, czy program działa poprawnie lub też czy się nie zawiesił. Zwykle po jako-takim przetestowaniu instrukcje Console.WriteLine() są wyłączane, aby nie spowalniać działania programu. Często jednak, po pewnym czasie przychodzi potrzeba ponownego włączenia śledzenia w aplikacji, dlatego też instrukcje śledzące w kodzie zwykle zamieniamy na komentarze zamiast fizycznie je usuwać.
Po kilku iteracjach dochodzimy zwykle do wniosku, że obsługa śledzenia naszej aplikacji, byłaby dużo bardziej użyteczna, gdyby możliwe było:
Wydaje się, że w dobie graficznych narzędzi do debugowania użyteczność rozwiązań opartych na śledzeniu jest mała. Często jednak są to jedyne dostępne narzędzia, które muszą nam wystarczyć np. do zlokalizowania przyczyny błędu w krytycznym systemie działającym na serwerze, którego nie można wyłączyć nawet na chwilę.
NLog (http://www.nlog-project.org) jest biblioteką na platformie .NET, która umożliwia łatwe wzbogacenie naszej aplikacji o obsługę śledzenia, realizującą wymagania opisane powyżej, a także dużo więcej.
Najprościej rzecz ujmując NLog umożliwia tworzenie reguł sterujących przepływem komunikatów diagnostycznych od źródła do celu, którym może być:
Dodatkowo, każdy komunikat diagnostyczny może być wzbogacony o informacje kontekstowe, które razem z nim zostaną przesłane do celu. Mogą to być:
Każdy komunikat diagnostyczny ma swój poziom (log level). Wspierane są następujące poziomy
NLog jest rozwiązaniem open source dostępnym za darmo na licencji BSD. Najnowszą wersję biblioteki można zawsze pobrać pod adresem http://www.nlog-project.org/download.html. Dostępny jest graficzny instalator, dzięki któremu można szybko i łatwo zainstalować NLoga w wybranym miejscu, jak również (w najnowszej wersji), integracja z Visual Studio 2005 udostępniająca:
Add Reference...Na początek przyjrzyjmy się, jak wygląda tworzenie aplikacji wykorzystującej NLog przy użyciu Visual Studio 2005. Przeanalizujemy 2 przykłady - jeden najprostszy, drugi bardziej skomplikowany, które pokazują jak łatwo jest sterować konfiguracją logowania.
Zaczynamy od utworzenia projektu w Visual Studio i dodania w nim pliku konfiguracyjnego NLoga. Będziemy się posługiwać językiem C#, ale NLog równie dobrze obsługuje VB.NET i inne języki na platformie .NET. Instalator NLoga dodaje do okna "Add New Item..." kilka nowych opcji pozwalających nam na "szybki start". Dodajmy więc do projektu pusty plik konfiguracyjny NLoga (Empty NLog Configuration File).
Zauważmy, że automatycznie została dodana referencja do biblioteki NLog i w projekcie pojawił się plik o nazwie NLog.config o poniższej treści:
]]>
W oknie właściwości dla tego pliku należy zaznaczyć opcję "Copy To Output Directory":
W sekcji
]]>
Zauważmy, że po Visual Studio podpowie nam dostępne nazwy elementów i ich atrybutów, a po wpisaniu xsi: uzyskamy listę dostępnych celów logowania.
W sekcji
Aby wysłać komunikat diagnostyczny posługujemy się obiektem klasy Logger, który udostępnia metody o nazwach odpowiadających poziomom ważności komunikatu. Obiekty Logger tworzymy za pośrednictwem klasy LogManager, najwygodniej jest w tym celu wywołać metodę LogManager.GetCurrentClassLogger(), która tworzy źródło o takiej samej nazwie jak bieżąca klasa.
Zmodyfikujmy wygenerowany plik C# dodając na początku polecenie using NLog, a w treści klasy instrukcję tworzenia obiektu Logger i wypisanie przykładowego komunikatu:
Wynikiem działania programu jest wypisana na konsoli tekstowej bieżąca data, poziom logowania (Debug) i komunikat Hello World.
Spróbujmy prześledzić teraz poszczególne etapy przetwarzania powyższego komunikatu diagnostycznego:
Załóżmy, że oprócz wypisywania na konsolę, chcemy jednocześnie zapisywać nasze komunikaty do pliku testowego i
rejestrować informacje o bieżącym stosie wywołań, czyli nazwy metod. Zdefiniujmy drugi cel logowania typu plikowego i skierujmy wszystkie komunikaty do niego. Włączmy też poziom Trace.
Wymaga to jedynie dodania nowego elementu
]]>
Wypiszmy także nieco więcej komunikatów na różnych poziomach logowania. Wprowadźmy też kilka dodatkowych metod, tak aby zaobserwować ślad stosu:
Uruchomienie programu spowoduje wypisanie na do pliku file.txt następujących informacji:
Program.A Trace AAA
Program.Main => Program.A Debug AAA
Program.Main => Program.A Info AAA
Program.Main => Program.A => Program.B Trace BBB
Program.Main => Program.A => Program.B Debug BBB
Program.Main => Program.A => Program.B Info BBB
Program.A => Program.B => Program.C Info CCC
Program.Main => Program.A => Program.B Warn BBB
Program.Main => Program.A => Program.B Error BBB
Program.Main => Program.A => Program.B Fatal BBB
Program.Main => Program.A Warn AAA
Program.Main => Program.A Error AAA
Program.Main => Program.A Fatal AAA
Program.Main Komunikat-ostrzeżenie na poziomie Warn
Program.Main Komunikat o błędzie na poziomie Error
Program.Main Komunikat o błędzie krytycznym na poziomie Fatal
]]>
W tym samym czasie konsolę wypisana zostanie wiadomość w innym formacie:
Załóżmy, że chcemy do pliku zapisywać wszystkie informacje, a na konsolę jedynie te najistotniejsze (na poziomie Info lub wyższym). Nic prostszego, wystarczy jedynie zmienić sekcję reguł:
]]>
Po uruchomieniu programu okaże się, że komunikaty na poziomie Trace i Debug są zawarte wyłącznie w pliku, podczas gdy nie widzimy ich na konsoli.
Czas teraz, aby odpowiedzieć na pytanie: jak to się stało, że NLog automatycznie odczytał poprawną konfigurację? Otóż, w odróżnieniu od innych narzędzi tego typu, NLog stara się maksymalnie ułatwić konfigurację, stosując rozmaite zachowania domyślne. NLog szuka pliku konfiguracyjnego w następujących standardowych lokalizacjach:
W przypadku aplikacji ASP.NET przeszukiwane są:
W środowisku .NET Compact Framework nie występuje pojęcie pliku konfiguracyjnego aplikacji (*.exe.config) ani zmienne środowiskowe, dlatego sprawdzane są tylko trzy lokalizacje:
Przeszukiwany zestaw plików został tak dobrany, aby umożliwić automatyczną konfigurację we wszystkich typowych trybach działania aplikacji bez konieczności wykonywania żadnych dodatkowych czynności.
Dostępne są 2 formaty konfiguracji NLoga:
W pierwszym przypadku posługujemy się standardowym mechanizmem sekcji konfiguracyjnych, dzięki którym nasz plik wygląda jak poniżej:
]]>
Format uproszczony występuje w przypadku plików z rozszerzeniem *.nlog oraz NLog.config i
składa się bezpośrednio z pliku XML zawierającego element
]]>
Wewnątrz elementu
Wewnątrz sekcji
Dodatkowo cele zwykle akceptują inne parametry - wpływające na sposób zapisywania informacji diagnostycznej. Dla każdego celu zestaw parametrów jest inny - są one szczegółowo opisane na stronie projektu jak również dostępne kontekstowo w trakcie edycji pliku konfiguracyjnego dzięki Intellisense.
Przykładowo - cel typu "File" (plik) akceptuje parametr "fileName" definiujący nazwę pliku a cel typu "Console" akceptuje parametr "error" określający czy komunikaty diagnostyczne mają być wypisywane na standardowe wyjście (stdout) czy na standardowe wyjście dla błędów (stderr).
NLog udostępnia wiele predefiniowanych celów. Są one opisane na stronie projektu. Bardzo łatwo jest także utworzyć swój własny cel - wymaga to zaledwie kilkunastu wierszy kodu i jest opisane w dokumentacji projektu.
Reguły sterowania logowaniem definiujemy w sekcji
Każdy wpis w tabeli routingu ma formę elementu
Poniżej kilka przykładów:
W najprostszych aplikacjach konfiguracja logowania wygląda zwykle tak, że mamy jeden cel i jedną regułę kierującą komunikaty do niego w zależności od poziomu. W miarę jak aplikacja rośnie, pojawia się potrzeba dodawania kolejnych celów i reguł. W przypadku NLoga jest to wyjątkowo proste.
Największą siłą NLoga w porównaniu z innymi narzędziami tego typu jest mechanizm formatów (ang. layouts). Są to małe fragmenty tekstu otoczone parą nawiasów "${" (dolar + lewy nawias klamrowy) oraz "}" (prawy nawias klamrowy), które potrafią wstawiać do tekstu elementy informacji kontekstowej. Można ich używać w bardzo wielu miejscach, przykładowo do sterowania formatem informacji wyświetlanych na ekran lub zapisywanych do pliku, ale także do sterowania nazwami plików!
Po co? Zobaczmy to na przykładzie. Załóżmy, że chcemy wypisywać na konsolę tekstową następujące informacje:
Jak to zrobić? Nic prostszego:
]]>
Załóżmy teraz, że chcemy aby komunikaty z każdego źródła trafiały do osobnego pliku. To jest równie łatwe:
]]>
Jak widzimy, format ${logger} został tutaj użyty w atrybucie fileName, co sprawia, że każdy komunikat diagnostyczny trafia do pliku o nazwie takiej jak nazwa źródła. Powyższy przykład utworzy nam pliki:
Częstym wymaganiem jest przechowywanie logów z każdego dnia w osobnym pliku. To również jest trywialne:
]]>
Gdybyśmy chcieli dla każdego użytkownika naszej aplikacji zrobić osobny plik, wystarczy poniższy fragment kodu:
]]>
Dzięki tej prostej operacji NLog utworzy nam pliki o nazwach takich jak loginy naszych użytkowników:
Oczywiście możliwe są też bardziej złożone przypadki. Np. dla każdego użytkownika i daty tworzymy osobny plik w katalogu takim jak data:
]]>
Powstałe pliki to:
NLog udostępnia bardzo wiele predefiniowanych formatów. Są one opisane pod adresem http://www.nlog-project.org/layoutrenderers.html. Bardzo łatwo jest także utworzyć swój własny format - wymaga to zaledwie kilkunastu wierszy kodu i jest opisane w dokumentacji projektu.
Czasami celowe może być rozbicie pliku konfiguracyjnego na kilka mniejszcych.
NLog udostępnia w tym celu mechanizm plików dołączanych. Aby włączyć je do konfiguracji wystarczy użyć
...
...
]]>
Zmienne pozwalają nam w skrócony sposób zapisywać złożone lub powtarzalne wyrażenia, takie jak nazwy plików.
Aby zdefiniować zmienną posługujemy się składnią
]]>
Pliki konfiguracyjne są standardowo wczytywane na początku pracy programu. W przypadku długo działających
procesów (np. usług systemowych) często zachodzi potrzeba zwiększenia poziomu logowania w trakcie działania.
Aby umożliwić taką rekonfigurację, NLog potrafi monitorować plik konfiguracyjny aplikacji
i wczytywać go ponownie po wykryciu jakiejkolwiej zmiany. Aby włączyć ten mechanizm, wystarczy dodać w
pliku konfiguracyjnym atrybut
Czasami zdarza się tak, że pomimo skonfigurowania, aplikacja nie zapisuje logów. Przyczyn może być wiele, najczęstszym problemem są uprawnienia, szczególnie w aplikacji webowej. Zwykle okazuje się, że serwer aplikacji WWW nie ma prawa zapisu do katalogu, gdzie chcielibyśmy gromadzić logi. Ponieważ NLog "zjada" wyjątki występujące podczas logowania, aplikacja może się o tym nie dowiedzieć. Jest kilka sposobów, aby stwierdzić występowanie tego typu problemów.
Oprócz standardowych, NLog udostępnia także tzw. wrapper targets i compound targets, czyli cele opakowujące i złożone, które modyfikują działanie innych celów. Mogą dodawać tym samym takie funkcje jak:
Aby zdefiniować cel złożony bądź opakowujący w pliku konfiguracjnym, zagnieżdżamy cel opakowany wewnątrz odpowiedniego celu opakowującego. Zagnieżdżanie może być wielopoziomowe, więc możemy modyfikować działanie celu na więcej niż jeden sposób. Przykładowo, asynchroniczne logowanie do pliku z ponawianiem próby zapisu w przypadku błędu definiujemy w następujący sposób.:
]]>
Ze względu na to, że przetwarzanie asynchroniczne jest dość często stosowane,
NLog udostępnia skróconą składnię pozwalającą włączyć to zachowanie dla wszystkich celów. Wystarczy w elemencie
Zamiast pliku konfiguracyjnego NLog może być konfigurowany przy pomocy API. Pełny opis tej funkcji wykracza poza zakres artykułu. W skrócie można powiedzieć, że polega to na:
Poniżej krótki przykład definiujący jeden cel typu "konsola tekstowa z kolorowaniem" i drugi plikowy oraz regułę kierującą do niego komunikaty na poziomie Debug lub wyższym:
Wynikiem działania powyższej aplikacji jest wypisany tekst na konsoli, gdzie każdy wiersz jest oznaczony kolorem wynikającym z poziomu logowania i dodatkowo plik zawierający te same informacje w nieco innym formacie.
NLog udostępnia jeszcze wiele funkcji, które ze względu na objętość nie mogły zostać omówione w niniejszym artykule. Każda z tych funkcji mogłaby być tematem na osobny artykuł.