Last updated: 2006-09-18

This website is based on NLog v20060918. Click here to view the documentation for other versions.

Wprowadzenie do śledzenia aplikacji przy pomocy bibliteki NLog

Autor: Jarosław Kowalski <jaak@jkowalski.net>

Dawno, dawno temu, kiedy na świecie nie było jeszcze debugerów, a aplikacje były w większości oparte o tekstową konsolę, programiści umieszczali w nich instrukcje printf() wypisujące komunikaty diagnostyczne na ekran. Kilka lat później świat poszedł znacznie do przodu i instrukcje printf() zostały zastąpione przez Console.WriteLine()...

Zapewne każdy z nas napisał kiedyś kod podobny do poniższego:

static void Main() 
{ 
    Console.WriteLine("Program SuperApp został uruchomiony..."); 
    DoSomething(0); 
    Console.WriteLine("Program SuperApp kończy działanie"); 
}

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:

  • sterowanie poziomem szczegółowości informacji diagnostycznych (np. wyświetlanie wyłącznie błędów i ostrzeżeń lub bardzo dokładne informacje przydatne do debugowania)
  • włączanie i wyłączanie śledzenia dla poszczególnych klas i bibliotek w trakcie działania programu, bez jego zatrzymywania
  • zapisywanie komunikatów do pliku, systemowego dziennika zdarzeń, dołączonego debugera, itp.
  • wysyłanie szczególnie ważnych komunikatów mailem lub zapisywanie ich w bazie danych
  • i wiele innych

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ę.

Czym jest NLog?

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ć:

  • plik
  • konsola tekstowa
  • wiadomość email
  • baza danych
  • inny komputer w sieci (protokół TCP/UDP)
  • kolejka komunikatów MSMQ
  • systemowy dziennik zdarzeń (Event Log)
  • i inne opisane na stronie http://www.nlog-project.org/targets.html

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ć:

  • bieżąca data i godzina (w różnych formatach)
  • poziom ważności
  • nazwa źródła
  • informacje o metodzie, która wyemitowała komunikat diagnostyczny
  • zawartości zmiennych środowiskowych
  • informacje o wyjątku
  • nazwa maszyny, procesu i wątku, który emituje komunikat
  • i wiele innych opisanych na stronie http://www.nlog-project.org/layoutrenderers.html

Każdy komunikat diagnostyczny ma swój poziom (log level). Wspierane są następujące poziomy

  • Trace - Szczegółowe komunikaty diagnostyczne o potencjalnie dużej częstotliwości i objętości
  • Debug -Komunikaty diagnostyczne o niewielkiej częstotliwości i małej objętości
  • Info - Komunikaty informacyjne, zwykle niezbyt częste
  • Warn - Ostrzeżenia nie powodujące błędnego działania programu
  • Error - Błędy, które objawiają się widocznym dla użytkownika komunikatem
  • Fatal - Błędy krytyczne, po których aplikacja zwykle kończy swoje działanie

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:

  • wybór biblioteki NLog.dll z okna Add Reference...
  • wsparcie dla kontekstowego podpowiadania składni - Intellisense podczas edycji plików konfiguracyjnych
  • szablony plików konfiguracyjnych
  • code snippet

Pierwsza aplikacja wykorzystująca NLoga

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:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
  <targets> 
  </targets> 
  <rules> 
  </rules> 
</nlog> 

W oknie właściwości dla tego pliku należy zaznaczyć opcję "Copy To Output Directory":

W sekcji <targets /> dodajmy następujący wpis definiujący wyjście na konsolę i format tego wyjścia:

<targets> 
  <target name="console" xsi:type="Console" 
          layout="${longdate}|${level}|${message}"/> 
</targets> 

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 <rules /> dodajmy regułę kierującą komunikaty na poziomie Debug lub wyższym na konsolę.

<rules> 
  <logger name="*" minlevel="Debug" writeTo="console"/> 
</rules> 

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:

using System; 
using System.Collections.Generic; 
using System.Text; 
using NLog; 
 
namespace NLogExample 
{ 
    class Program 
    { 
        private static Logger logger = LogManager.GetCurrentClassLogger(); 
         
        static void Main(string[] args) 
        { 
            logger.Debug("Hello World!"); 
        } 
    } 
}

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:

  1. Metoda LogManager.GetCurrentClassLogger(); tworzy obiekt klasy Logger reprezentujący źródło logowania powiązane z bieżącą klasą.
  2. Wywołanie metody Debug() na tym obiekcie powoduje wysłanie zadanego komunikatu za pośrednictwem wskazanego źródła z poziomem logowania Debug
  3. Ponieważ poziom logowania i źródło odpowiadają zdefiniowanej regule, komunikat jest formatowany zgodnie z wartością parametru layout elementu <target /> i wysyłany na konsolę.

Bardziej skomplikowana konfiguracja logowania

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 <target /> typu File, modyfikacji reguły <logger /> a także zmodyfikowania parametru layout.

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
  <targets> 
    <target name="console" xsi:type="ColoredConsole" 
            layout="${date:format=HH\:mm\:ss}|${level}|${stacktrace}|${message}"/> 
    <target name="file" xsi:type="File" fileName="${basedir}/file.txt" 
            layout="${stacktrace} ${message}"/> 
  </targets> 
  <rules> 
    <logger name="*" minlevel="Trace" writeTo="console,file"/> 
  </rules> 
</nlog> 

Wypiszmy także nieco więcej komunikatów na różnych poziomach logowania. Wprowadźmy też kilka dodatkowych metod, tak aby zaobserwować ślad stosu:

static void C() 
{ 
    logger.Info("Info CCC"); 
} 
static void B() 
{ 
    logger.Trace("Trace BBB"); 
    logger.Debug("Debug BBB"); 
    logger.Info("Info BBB"); 
    C(); 
    logger.Warn("Warn BBB"); 
    logger.Error("Error BBB"); 
    logger.Fatal("Fatal BBB"); 
} 
static void A() 
{ 
    logger.Trace("Trace AAA"); 
    logger.Debug("Debug AAA"); 
    logger.Info("Info AAA"); 
    B(); 
    logger.Warn("Warn AAA"); 
    logger.Error("Error AAA"); 
    logger.Fatal("Fatal AAA"); 
} 
static void Main(string[] args) 
{ 
    logger.Trace("Komunikat techniczny na poziomie Trace"); 
    logger.Debug("Komunikat techniczny na poziomie Debug"); 
    logger.Info("Komunikat informacyjny na poziomie Info"); 
    A(); 
    logger.Warn("Komunikat-ostrzeżenie na poziomie Warn"); 
    logger.Error("Komunikat o błędzie na poziomie Error"); 
    logger.Fatal("Komunikat o błędzie krytycznym na poziomie Fatal"); 
}

Uruchomienie programu spowoduje wypisanie na do pliku file.txt następujących informacji:



Program.Main Komunikat techniczny na poziomie Trace 
Program.Main Komunikat techniczny na poziomie Debug 
Program.Main Komunikat informacyjny na poziomie Info 
Program.Main => 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ł:

<rules> 
  <logger name="*" minlevel="Info" writeTo="console"/> 
  <logger name="*" minlevel="Trace" writeTo="file"/> 
</rules> 

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.

Konfiguracja logowania

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:

  • standardowy plik konfiguracyjny programu (zwykle aplikacja.exe.config)
  • plik aplikacja.exe.nlog w katalogu aplikacji
  • plik NLog.config w katalogu aplikacji
  • plik NLog.dll.nlog w katalogu, w którym znajduje się plik NLog.dll
  • jeśli istnieje zmienna środowiskowa NLOG_GLOBAL_CONFIG_FILE, sprawdzane jest miejsce wskazywane przez tę zmienną

W przypadku aplikacji ASP.NET przeszukiwane są:

  • standardowy plik konfiguracyjny web.config
  • plik web.nlog w katalogu aplikacji (obok web.config)
  • plik NLog.config w katalogu aplikacji
  • plik NLog.dll.nlog w katalogu, w którym znajduje się plik NLog.dll (zwykle "bin")
  • jeśli istnieje zmienna środowiskowa NLOG_GLOBAL_CONFIG_FILE, sprawdzane jest miejsce wskazywane przez tę zmienną

W środowisku .NET Compact Framework nie występuje pojęcie pliku konfiguracyjnego aplikacji (*.exe.config) ani zmienne środowiskowe, dlatego sprawdzane są tylko trzy lokalizacje:

  • plik aplikacja.exe.nlog w katalogu aplikacji
  • plik NLog.config w katalogu aplikacji
  • plik NLog.dll.nlog w katalogu, w którym znajduje się plik NLog.dll

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.

Format pliku konfiguracyjnego

Dostępne są 2 formaty konfiguracji NLoga:

  • osadzona wewnątrz pliku konfiguracyjnego aplikacji
  • uproszczona

W pierwszym przypadku posługujemy się standardowym mechanizmem sekcji konfiguracyjnych, dzięki którym nasz plik wygląda jak poniżej:

<configuration> 
  <configSections> 
    <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/> 
  </configSections> 
  <nlog> 
  </nlog> 
</configuration> 

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 <nlog /> jako korzeń. Użycie przestrzeni nazw jest opcjonalne, ale umożliwia działanie mechanizmu Intellisense w Visual Studio.

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
</nlog> 

Elementy konfiguracyjne

Wewnątrz elementu <nlog /> można używać następujących elementów, z czego zwykle używane są dwa pierwsze elementy z poniższej listy.

  • <targets /> - definiuje cele logowania
  • <rules /> - definiuje reguły kierowania komunikatów diagnostycznych
  • <extensions /> - ładuje rozszerzenia NLoga z pliku *.dll
  • <include /> - dołącza zewnętrzny plik konfiguracyjny
  • <variable /> - ustawia wartość zmiennej konfiguracyjnej

Cele

Wewnątrz sekcji <targets /> definiujemy cele logowania, które są reprezentowane przez elementy <target /> Każdy cel musi mieć określone 2 atrybuty:

  • name - nazwa
  • type - typ celu (w przypadku korzystania z przestrzeni nazw xsi:type)

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

Reguły sterowania logowaniem definiujemy w sekcji <rules />. Jest to prosta tabela routingu, gdzie w każdej regule na podstawie nazwy źródła i poziomu logowania możemy skierować wynik do określonego celu lub listy celów. Dodatkowo możemy zdecydować czy po zapisaniu mają być uwzględniane dalsze reguły czy też zakończyć ich przetwarzanie.

Każdy wpis w tabeli routingu ma formę elementu <logger />, który przyjmuje następujące atrybuty:

  • name - nazwa źródła (może zawierać symbole wieloznaczne * na początku i/lub na końcu)
  • minlevel - minimalny poziom logowania wymagany do tego aby reguła zadziałała
  • maxlevel - maksymalny poziom logowania wymagany do tego aby reguła zadziałała
  • level - poziom logowania (pojedynczy) wymagany do tego aby reguła zadziałała
  • levels - lista poziomów logowania, które aktywują regułę
  • writeTo - lista nazw celów, do których zapisać komunikat
  • final - czy reguła jest kończąca. Jeśli tak, po jej zadziałaniu nie są przetwarzane żadne dalsze reguły

Poniżej kilka przykładów:

  • <logger name="Name.Space.Klasa1" minlevel="Debug" writeTo="f1" /> - komunikaty z klasy Klasa1 w przestrzeni nazw Name.Space na poziomie Debug lub wyższym są zapisywane do celu "f1"
  • <logger name="Name.Space.Klasa1" levels="Debug,Error" writeTo="f1" /> - komunikaty z klasy Klasa1 w przestrzeni nazw Name.Space na poziomach Debug i Error zapisywane do celu "f2"
  • <logger name="Name.Space.*" writeTo="f3,f4" /> - komunikaty ze wszystkich klas w przestrzeni nazw Name.Space niezależnie od ich poziomu są zapisywane do celów "f3" i "f4"
  • <logger name="Name.Space.*" minlevel="Debug" maxlevel="Error" final="true" /> - komunikaty ze wszystkich klas w przestrzeni nazw Name.Space na poziomie większym niż Debug i mniejszym niż Error (czyli Debug,Info,Warn,Error) są odrzucane (ponieważ nie podano klauzuli writeTo) i nie są przetwarzane dla nich kolejne reguły (ze względu na final="true")

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.

Informacje kontekstowe

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:

  • bieżąca data i godzina
  • nazwa klasy i metody, która spowodowała wypisanie komunikatu diagnostycznego
  • poziom komunikatu diagnostycznego
  • tekst komunikatu diagnostycznego

Jak to zrobić? Nic prostszego:

<target name="c" xsi:type="Console" 
        layout="${longdate} ${callsite} ${level} ${message}"/> 

Załóżmy teraz, że chcemy aby komunikaty z każdego źródła trafiały do osobnego pliku. To jest równie łatwe:

<target name="f" xsi:type="File" fileName="${logger}.txt"/> 

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:

  • Name.Space.Klasa1.txt
  • Name.Space.Klasa2.txt
  • Name.Space.Klasa3.txt
  • Inny.Name.Space.Klasa1.txt
  • Inny.Name.Space.Klasa2.txt
  • Inny.Name.Space.Klasa3.txt
  • ...

Częstym wymaganiem jest przechowywanie logów z każdego dnia w osobnym pliku. To również jest trywialne:

<target name="f" xsi:type="File" filename="${shortdate}.txt"/> 

Gdybyśmy chcieli dla każdego użytkownika naszej aplikacji zrobić osobny plik, wystarczy poniższy fragment kodu:

<target name="f" xsi:type="File" filename="${windows-identity:domain=false}.txt"/> 

Dzięki tej prostej operacji NLog utworzy nam pliki o nazwach takich jak loginy naszych użytkowników:

  1. Administrator.txt
  2. MaryManager.txt
  3. EdwardEmployee.txt
  4. ...

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:

<target name="f" xsi:type="File" 
        filename="${shortdate}/${windows-identity:domain=false}.txt"/> 

Powstałe pliki to:

  1. 2006-01-01/Administrator.txt
  2. 2006-01-01/MaryManager.txt
  3. 2006-01-01/EdwardEmployee.txt
  4. 2006-01-02/Administrator.txt
  5. 2006-01-02/MaryManager.txt
  6. 2006-01-02/EdwardEmployee.txt
  7. ...

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.

Pliki dołączane i zmienne

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ć <include file="nazwapliku" />. Warto wspomnieć że nazwapliku może zawierać elementy informacji kontekstowej, więc możliwe są zaawansowane scenariusze takie jak dołączanie różnych plików na różnych maszynach, jak w poniższym przykładzie, gdzie wczytujemy plik o takiej nazwie jak nazwa bieżącej maszyny:

<nlog> 
  ... 
  <include file="${basedir}/${machinename}.config"/> 
  ... 
</nlog> 

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ą <variable name="var" value="xxx" />. Aby użyć jej wartość posługujemy się składnią identyczną jak przy informacjach kontekstowych, to znaczy: "${var}".

<nlog> 
  <variable name="logDirectory" value="${basedir}/logs/${shortdate}"/> 
  <targets> 
    <target name="file1" xsi:type="File" filename="${logDirectory}/file1.txt"/> 
    <target name="file2" xsi:type="File" filename="${logDirectory}/file2.txt"/> 
  </targets> 
</nlog> 

Rekonfiguracja

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 <nlog autoReload="true" />.

Rozwiązywanie problemów z logowaniem

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.

  • <nlog throwExceptions="true" />- dodanie atrybutu throwExceptions w pliku konfiguracyjnym powoduje, że NLog nie maskuje wyjątków i są one przekazywane do aplikacji. Ten atrybut jest przydatny w fazie uruchamiania. Po zakończeniu konfiguracji dobrze jest włączyć maskowanie wyjątków, tak aby przypadkowe problemy z logowaniem nie spowodowały awarii naszej aplikacji.
  • <nlog internalLogFile="file.txt" />- dodanie atrybutu internalLogFilew pliku konfiguracyjnym powoduje, że NLog zapisze do wskazanego pliku wewnętrzne informacje ułatwiające debugowanie, w tym wszystkie wyjątki jakie zostaną zgłoszone podczas logowania.
  • <nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" /> - sterujący poziomem wewnętrznego logowania
  • <nlog internalLogToConsole="false|true" /> - wysyła zawartość wewnętrznego logu na konsolę.
  • <nlog internalLogToConsoleError="false|true" /> - wysyła zawartość wewnętrznego logu na wyjście błędów konsoli.

Przetwarzanie asynchroniczne, cele opakowujące i złożone

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:

  • przetwarzanie asynchroniczne (logowanie odbywa się w osobnym wątku)
  • ponawianie próby w przypadku błędu
  • równoważenie obciążenia (load balancing)
  • buforowanie
  • filtrowanie
  • cele zapasowe (w przypadku awarii jedego celu przełączamy się na kolejny)
  • i inne opisane na stronie http://www.nlog-project.org/targets.html

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.:

<targets> 
  <target name="n" xsi:type="AsyncWrapper"> 
    <target xsi:type="RetryingWrapper"> 
      <target xsi:type="File" fileName="${file}.txt"/> 
    </target> 
  </target> 
</targets> 

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 <targets /> dodać atrybut async="true".

Konfiguracja programowa

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:

  1. utworzeniu obiektu klasy LoggingConfiguration
  2. utworzeniu jednego lub więcej celów (obiektów dziedziczących z klasy Target)
  3. ustawienie właściwości celów
  4. zdefiniowanie reguł
  5. aktywowanie konfiguracji

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:

using NLog; 
using NLog.Targets; 
using NLog.Config; 
using NLog.Win32.Targets; 
 
class Example 
{ 
    static void Main(string[] args) 
    { 
        // krok 1. tworzymy obiekt konfiguracji 
         
        LoggingConfiguration config = new LoggingConfiguration(); 
         
        // krok 2. tworzymy cel(e) i dodajemy je do konfiguracji 
 
        ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget(); 
        config.AddTarget("console", consoleTarget); 
         
        FileTarget fileTarget = new FileTarget(); 
        config.AddTarget("file", fileTarget); 
         
        // krok 3 - ustawiamy właściwości celów 
         
        consoleTarget.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}"; 
        fileTarget.FileName = "${basedir}/file.txt"; 
        fileTarget.Layout = "${message}"; 
         
        // krok 4 - definiujemy reguły 
         
        LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget); 
        config.LoggingRules.Add(rule1); 
 
        LoggingRule rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget); 
        config.LoggingRules.Add(rule2); 
         
        // krok 5. aktywowanie konfiguracji 
        LogManager.Configuration = config; 
         
        // przykłady użycia 
 
        Logger logger = LogManager.GetLogger("Example"); 
        logger.Trace("trace log message"); 
        logger.Debug("debug log message"); 
        logger.Info("info log message"); 
        logger.Warn("warn log message"); 
        logger.Error("error log message"); 
        logger.Fatal("fatal log message"); 
    } 
}

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.

Co jeszcze można zrobić w NLogu?

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ł.

Last updated: 2006-07-10 11:32:55

Webwww.nlog-project.org