Wprowadzenie

Jeżeli programujesz w .NET dostatecznie długo prawdopodobnie spotkałeś się z biblioteką log4net. Istnieje od dawna i jest bardzo podobna do log4j - ta biblioteka została stworzona dla Javy. W moim przypadku logowanie błędów odbywało się zwykle za pomocą własnoręcznie przygotowanego kodu. Warto jednak przyjrzeć się bibliotece log4net. W artykule skupimy się na teorii, dokonamy przykładowej implementacji oraz opiszemy najlepsze wskazówki dotyczące pracy z tą biblioteką.

Czym jest log4net i dlaczego warto go używać?

Wciąż możemy spotkać wiele osób, które nie używają żadnej struktury logowania w C#. Zwykle nie jest to problemem na początkowym etapie przygotowywania projektu, dlatego też ten krok jest pomijany. Jednkże, po publikacji naszego projektu w sieci może się okazać, że "coś przestało działać". Bez odpowiedniego logowania nie jesteśmy w stanie szybko zidentyfikować przyczyny problemu.

Struktury logowania są niezwykle ważne ponieważ ułatwiają zapisywanie do dziennika zdarzeń umieszczonego w różnych miejscach dzięki zmianie konfiguracji. Logi mogą być zapisywane do pliku na dysku twardym, bazy danych, systemu zarządzania dziennikami lub potencjalnie dziesiątek innych miejsc – a wszystko to bez zmiany napisanego przez nas kodu.

Instalacja log4net przy użyciu NuGet

Nasz przykład zostanie przygotowany w oparciu o prostą aplikację konsolową przygotowaną w technologii .NET Core. Pierwszym krokiem będzie utworzenie nowego projektu.

Rozpoczęcie pracy z samą biblioteką jest tak proste jak instalacja odpowiedniej paczki. Po utworzeniu projektu przechodzimy do NuGet Package Manager w celu odnalezienia wymaganej biblioteki: Log4net: NuGet Package Manager

Po poprawnej instalacji dodajemy do projektu nowy plik konfiguracyjny dla naszej biblioteki: log4net.config. Musimy pamiętać o odpowiednich ustawieniach tego pliku, tzn. plik ten powinien być zawsze skopiowany do folderu wynikowego bin w trakcie budowania projektu. W tym celu ustawiamy właściwość Copy to Output Directory na Copy Always. Log4net: plik właściwości

W tej cześci artykułu zależy nam na szybkim rozpoczęciu pracy. W tym celu umieść poniższą konfigurację w nowo utworzonym pliku. Pozwoli nam na logowanie wiadomości do dwóch miejsc, w konsoli oraz pliku dziennika. Więcej informacji o dostępnych typach logowania omówimy w dalszych sekcjach artykułu:

<log4net>
	<root>
	<level value="ALL" />
	<appender-ref ref="console" />
	<appender-ref ref="file" />
   </root>
   <!--Logowanie błędów/informacji/ostrzeżeń w oknie konsoli-->
	<appender name="console" type="log4net.Appender.ConsoleAppender">
	<layout type="log4net.Layout.PatternLayout">
	<conversionPattern value="%date`%level %logger - %message%newline" />
      </layout>
      <!--Wpis konfiguracyjny pozwalający na odpowiednie ustawienie kolorów-->
   </appender>
	<appender name="file" type="log4net.Appender.RollingFileAppender">
      <!--File Details/Layout Options-->
	<filter type="log4net.Filter.LevelRangeFilter">
	<levelMin value="ERROR" />
      </filter>
	<file value="myapp.log" />
	<appendToFile value="true" />
	<rollingStyle value="Size" />
	<maxSizeRollBackups value="5" />
	<maximumFileSize value="10MB" />
	<staticLogFileName value="true" />
	<layout type="log4net.Layout.PatternLayout">
	<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
      </layout>
   </appender>
</log4net>

Po dodaniu powyższej konfiguracji możecie zobaczyć następujące ostrzeżenie: Log4net: ostrzeżenie o błędzie Wiele osób powie: "to ostrzeżenie, zignoruj to!" Jeżeli jednak interesujecie się co jest przyczyną problemu i jak go rozwiązać zapraszam do zapoznania się z kilkoma poniższymi krokami:
  • brakuje nam pliku .XSD, który zawiera w sobie definicję elementów i atrybutów, które mogą pojawić się w pliku XML;
  • w pierwszym kroku musimy udać się do NIEOFICJALNEGO źródła jakim jest http://csharptest.net/downloads/schema/log4net.xsd w celu pobrania odpowiedniej definicji;
  • operujemy na nowym projekcie. Warto dodać nowy folder Resources w którym umieścimy definicję struktury XML: Log4net: folder Resources
  • musimy jeszcze dokonać restartu Visual Studio - w przeciwnym wypadku nasz plik nie będzie widoczny w trakcie wykonywania kolejnego kroku;
  • otwieramy nasz plik konfiguracyjny i przechodzimy do górnego menu XML: Visual Studio: XML Menu Jeżeli nasz plik nie jest widoczny na liście dodajemy go manualnie a następnie zaznaczamy kolumnę Use i klikamy przycisk Ok. Ostrzeżenie nie powinno już być widoczne: Log4net: brak ostrzeżenia w pliku xml
Wszystko przebiegło pomyślnie, ostrzeżenie zostało wyeliminowane. Możemy powrócić do właściwej części artykułu.

Przygotowaliśmy konfigurację ale nie jest nigdzie ładowana – w tym momencie jest dla nas bezużyteczna. Posłużymy się poniższym kodem:

XmlDocument log4netConfig = new XmlDocument();
// ładowanie konfiugracji

log4netConfig.Load(File.OpenRead("log4net.config"));
var repo = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(),
			typeof(log4net.Repository.Hierarchy.Hierarchy));

// tworzenie finalnej konfiguracji
log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);

Spróbujmy teraz dokonać pierwszego logowania. Posłużymy się poniższym kodem, który wyświetli (zgodnie z nasza konfiguracją) informację na konsoli:

class Program
{
	private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
	static void Main(string[] args)
	{
		XmlDocument log4netConfig = new XmlDocument();

		// ładowanie konfiugracji
		log4netConfig.Load(File.OpenRead("log4net.config"));
		var repo = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(),
					typeof(log4net.Repository.Hierarchy.Hierarchy));

		// tworzenie finalnej konfiguracji
		log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);
		log.Info("Witaj świecie z log4net!");
		Console.ReadLine();
	}
}
Dodatkowo powinien zostać utworzony plik dziennika pod nazwą myapp.log. Możecie go znaleźć w folderze wynikowym Waszego projektu: Dziennik zdarzeń

Loggery: czym są i co należy o nich wiedzieć?

Typ loggera definiuje sposób w jaki kierujesz swoje dzienniki. Najpopularniesze z nich to RollingFileAppender oraz ConsoleAppender. Warto również skorzystać z DebugAppender - pozwala on podejrzeć cały dziennik w oknie Visual Studio Debug (dzięki temu nie musisz za każdym razem otwierać pliku dziennika by podejrzeć dziennik zdarzeń).

Jeżeli używasz konsoli warto korzystać z ManagedColoredConsoleAppender. W zależności od poziomu zapisanej informacji będzie ona wyświetlana w innym kolorze. W tym celu zmodyfikujemy nieco nasz plik konfiguracji:

<log4net>
	<root>
	<level value="ALL" />
	<appender-ref ref="ManagedColoredConsoleAppender" />
	<appender-ref ref="file" />
  </root>
  <!--Zmiana typu loggera na: ManagedColoredConsoleAppender-->
	<appender name="ManagedColoredConsoleAppender" type="log4net.Appender.ManagedColoredConsoleAppender">
	<layout type="log4net.Layout.PatternLayout">
	<conversionPattern value="%date`%level %logger - %message%newline" />
    </layout>
      <!--Wpis konfiguracyjny pozwalający na odpowiednie ustawienie kolorów-->
		<mapping>
		<level value="ERROR" />
		<foreColor value="Red" />
		<backColor value="Black" />
    </mapping>
		<mapping>
		<level value="DEBUG" />
		<foreColor value="Green" />
		<backColor value="Black" />
    </mapping>
		<mapping>
		<level value="INFO" />
		<foreColor value="White" />
		<backColor value="Black" />
    </mapping>
  </appender>
		<appender name="file" type="log4net.Appender.RollingFileAppender">
		<file value="myapp.log" />
		<appendToFile value="true" />
		<rollingStyle value="Size" />
		<maxSizeRollBackups value="5" />
		<maximumFileSize value="10MB" />
		<staticLogFileName value="true" />
		<layout type="log4net.Layout.PatternLayout">
		<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
    </layout>
  </appender>
</log4net>
oraz wyświetlimy stosowane informacje celem sprawdzenia poprawności działania naszej konfiguracji:
log.Info("Witaj świecie z log4net!");
log.Debug("Info dla programisty: Log4Net -> Program -> Main");
log.Error("Oby takich błędów było jak najmniej!");
Tak prezentuje się teraz dziennik wyświetlony w konsoli: Log4net: tryb różnych kolorów aplikacji konsolowej

Wiele poziomów logowania i filtrowania

Najważaniejsze w prowadzaniu dziennika logowania jest odpowiednie oznaczanie różnego typu informacji. Upewnij się, że używasz odpowiedniego filtrowania dotyczącego operacji debuggowania, informacji, ostrzeżenia czy komunikatów błędów. Nie możemy wszystkiego traktować jako informacji debugowania. Co równie istotne, zanim zaczniemy "projektować" nasz dziennik zdarzeń musimy upewnić się w jaki sposób będzie przeglądać ten dziennik, na jakich informacjach nam zależy – będzie to niezwykle istotne z punktu widzenia wyciągania informacji z rejestru zdarzeń.

Konfiugracja log4net pozwala nam okreslić, które poziomy rejestrowania będą zapisywanie w dzienniku. Jest to niezwykle istotny wpis konfiguracyjny jeżeli zależy nam na określeniu poziomów logowania w konkretnej aplikacji bądź też na ograniczeniu logowania na serwerach produkcyjnych. Plik ten pozwala na logowanie większej ilości danych bez zmiany kodu.

Poziomy logowania w log4net:

  • All - logujemy wszystkie informacje;
  • Debug - informacje debuggowania;
  • Info - informacje;
  • Warn - ostrzeżenia;
  • Error - błędy;
  • Fatal - błędy krytyczne;
  • Off - nie logujemy żadnych informacji.

W powyższym przypadku brakuje nam przykładu różnego stopnia logowania. W aplikacji konsolowej wyświetlimy wszystkie informacje a w dzienniku zdarzeń (załóżmy, że jest to aplikacja uruchamiana na serwerze produkcyjnym – tylko błędy). Cały czas obracamy się w pliku konfiguracyjnym – żadne zmiany w naszej implementacji nie są wymagane. Dodajemy do pliku konfiguracyjnego poniższy wpis (dla jednego z typów logowania):
<appender name="ManagedColoredConsoleAppender" type="log4net.Appender.ManagedColoredConsoleAppender">
<!—Filtrowanie poziomu zapisu do dziennika-->
	<filter type="log4net.Filter.LevelRangeFilter">
	<levelMin value="ALL" />
</filter>
...
...
...
W przypadku naszego kolejnego typu logowania ustawiamy poziom filtrowania na ERROR. Wynikiem takiej definicji pliku konfiguracjnego jest odmienny poziom logowania do odpowiednich dzienników: Log4net: tryb logowania błędów
Podsumowanie

Pierwotny zamysł artykułu polegał na jednym długim wpisie. W trakcie pisania doszedłem do wnioski, że nie ma to większego sensu – sam nie lubię czytać zbyt długich wpisów. Mam nadzieje, że powyższe wprowadzenie ze wszystkimi niezbędnymi opisami, przykładami i wyjaśnieniami jest w pełni wystarczające.

Jeżeli chcecie poznać najlepsze praktyki związane z pracą z biblioteką log4net zapraszam do kolejnego artykułu, uzupełnienia bieżącego tematu, który możecie znaleźć pod adresem: .NET Core - log4net: najlepsze praktyki