Kategoria: c#

Potyczki z Log4net Cz. 2

Na wstępie chciałbym przeprosić, że to tak długo trwało , ale ciężkie życie programisty nie zawsze pozwala mi znaleźć czas na częste blogowanie.
W tej części mojego tutoraiala zajmiemy się samą konfiguracją usługi log4net. Zasadniczo istnieją dwa możliwe podejścia do konfiguracji. Można stworzyć osobny plik konfiguracyjny i nazwać go np. log4net.config lub dodać sekcję do głównego pliku konfiguracyjnego aplikacji. Pierwszym przypadku należy samodzielnie odczytać plik za pomocą api konfiguracyjnego platformy .NET . Np.
using log4net; 
using log4net.Config;
public class MyApp
{
    private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
    static void Main(string[] args)
    {


        // BasicConfigurator replaced with XmlConfigurator.


        XmlConfigurator.Configure(new System.IO.FileInfo(“log4net.config”));
    }
}
Klasa Xmlconfigurator odczytuje plik configuracyjny i konfiguruje środowisko logowania zgodnie z jego zawartością,
W drugim przypadku aby konfiguracja została odczytana należy zadeklarować sekcję w pliku konfiguracyjnym w sekcji <configsections> następującą dyrektywą :
    <configSections> 
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
 </configSections>

Całą konfigurację “ubieramy” w znacznik <log4net></log4net>.
Tyle różnic między tymi dwoma metodami. Zawartość konfiuguracji dla obydwu metod jest taka sama. Jakie są mozliwe sekcje ?
  • appender – zero lub więcej elementów . definiuje appendera.
  • logger – zero lub więcej elementów . definiuje konfigurację loggera.
  • renderer – zero lub więcej elementów . Definiuje renderer obiektów.
  • root – opcjonalny element, definiuje konfigurację nadrzędnego loggera.
  • param – zero lub więcej pramaterów definiuje parametry repozytorium.
  • Apendery

    Apendery jak pamiętamy z poprzedniego,artu służą do definiowania targetów na które będą wypluwane wiadomości z naszego loga np. konsola, plik tekstowy. Przykładowa konfiguracja appendera:
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
    </layout>
    </appender>


    I tak każdy appender musi mieć nazwę (name). Potrzebne jest do odwołania się do appendera w konfiguracji loggera. Następnie trzeba zdefiniować layout podając typ i format wiadomości. Dokładną listę wszystkich zmiennych można znaleźć w dokumentacji technicznej log4net. Całą magia polega na tym że potem tylko podajemy treść wiadomości,a reszta jest logowana za nas. Oprócz tego tego niektóre appendery wymagają podania dodatkowych paramterów. Definiuje się ję za pomocą elementu <param> .
    Kilka przykładowych appenderów

  • FileAppender – pzowala logować do pliku tekstowego .
  • AdoNetAppender – loguje do bazy danych za pomocą zapytań lub procedur składowych
  • EventLogAppender – zapisuje informacje do dziennika zdarzeń systemu Windows
  • SmtpAppender  – wysyła wiadomości pod wskazany adres e – mail.

    Loggery

    Ogólnie, istnieją dwa rodzaje loggerów w log4net. Logger nadrzędy – root (opcjonalny), oraz loggery zwykłe które dziedziczą od roota. Żeby zdefiniować loggera trzeba określić minimalny poziom logowania oraz opcjonalnie podać odpowiedni appender:

    <logger name="LoggerName"> 
        <level value="DEBUG" />
        <appender-ref ref="ConsoleAppender" />
    </logger>


      Oprócz tego w każdym loggerze można zdefiniować parametr additivity=”true/false” który definiuje czy logger ma dziedziczyć ustawienia z loggerów nadrzędnych.Tak samo jak appender logger może mieć parametry, które definiuje się elementem <param>.

    Renderery      

  • Dla przypomnienia, renderery wywoływane są w momencie gdy jakiś obiekt musi zostać logowany. Rendery są dziećmi elementu log4net i przy deklaracji definiujemy jakiej klasy dotyczy renderer i jak się nazywa klasa która ma go renderować (musi ona implementować interfejs IObjectRenderer ). Przykładowa deklaracja:

    <renderer renderingClass="MyClass.MyRenderer"
    renderedClass="MyClass.MyFunkyObject" />
    Parametry

    Parametry są obecne w całej konfiguracji log4neta i ich użycie sprowadza się
    do zdefiniowania nazwy parametru (name) i jego wartości (value). 
    Warto wspomnieć,że możliwy jest też krótszy zapis . Zamiast:

     <param name="Threshold" value="WARN"/>

    Można napisać :

     <threshold value="WARN"/>

    Na koniec, żeby to wszystko lepiej ogarnąć chciałbym pokazać, 

    jak wygląda gotowa konfiguracja.

    <?xml version="1.0"?> <log4net debug="false">   <root>
        <level value="ALL" />
        <appender-ref ref="FileAppender" />
        <appender-ref ref="ColoredConsoleAppender" />
      </root>
      <logger name="NHibernate" additivity="false">
        <level value="ERROR" />
        <appender-ref ref="FileAppender" />
        <appender-ref ref="ColoredConsoleAppender" />
      </logger>
      <logger name="NHibernate.SQL">
        <level value="ERROR" />
        <appender-ref ref="FileAppender" />
      </logger>
      <!-- Logging - Appenders -->   <appender name="FileAppender" type="log4net.Appender.RollingFileAppender">
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
        <file value="D:LogsLogs.txt" />
        <appendToFile value="true" />
        <maximumFileSize value="10000KB" />
        <maxSizeRollBackups value="100" />
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%d{ISO8601} %-5p %c{1} - %m%n" />
        </layout>
      </appender>
      <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
        <mapping>
          <level value="ERROR" />
          <foreColor value="White" />
          <backColor value="Red, HighIntensity" />
        </mapping>
        <mapping>
          <level value="WARN" />
          <foreColor value="White" />
          <backColor value="Yellow" />
        </mapping>
        <mapping>
          <level value="DEBUG" />
          <backColor value="Green" />
        </mapping>
        <layout type="log4net.Layout.PatternLayout">
          <conversionPattern value="%m%n" />
        </layout>
      </appender>
    </log4net>

    Cannot access a disposed object. Object name: ‚DataContext accessed after Dispose

    Czasami podczas wykonywania kodu może się zdarzyć, że nasz kod się wysypie z tym wyjątkiem. Problem ten występuje kiedy próbujemy się odwołać do atrybutów klas LINQ2SQL po usunięciu z pamięci powiązanego z nimi DataContextu, czyli defakto oderwania obiektów od bazy danych. Podstawową przyczyną tego problemu jest niejawna optymalizacja linq2sql zwana lazy loading. Mechanizm ten zakłada że nie ładowane są wszystkie atrybuty i kolejcjie z bazy a jedynie to co jest potrzebne, na podstawie wyrażeń lambda oraz zapytań linq.
    Po pewnym czasie to co jest potrzebne do dalszego przetwarzania zostaje załadowane z pamięci, a DataContext jest niszczony. I właśnie tu pojawia się wspomniany wyjątek . Jest on o tyle ‚zdradliwy’, że może pojawić się znienacka. Typowym scenariuszem, jak udało mi się zauważyć jest przypadek kiedy przekazujemy obiekty linq2sql do Widoku w aplikacji ASP.NET MVC. Bierzemy taki obiekt i odwołujemy kilka stopni głębokości w jego strukturę.Przykładowo mamy sobie obiekt klasy user który zawiera kolekcję wszystkich postów użytkownika, a każdy post zawiera z kolei kolekcję komentarzy. Typowa struktura np. dla Bloga. W tym momecie przy odwołaniu typu User.Posts[0].Coments dostajemy nie za wiele mówiący komentarz ‚Cannot access a disposed object Object name: ‚DataContext accessed after Dispose’. Jest to własnie spowodowane tym co wspomniałem wyżej – linq2sql nie załadował kolekcji komentarzy do pamięci bo zwyczajnie nie wiedział że takowa będzie potrzebna. Czy jest na to jakiś sposób? Oczywiście tzw. best practice mówi żeby nie korzystać z obiektów linq2sql w widokach, no ale co jeśli mamy deadline za pięć dwunasta a musimy poprawić bug w tej aplikacji której programista się rozpędził i nie zawracał sobie głowy jakimiś ViewModelami ? Wtedy jedyną możliwością jest jawne przekazanie do linq żeby ładował kolekcje komentarzy dla każdego Posta. Jak to zrobić ? Przyjrzyjmy się temu fragmentowi kodu:

    var db = new DataContext();
    var dataLoadOptions = new DataLoadOptions();
    dataLoadOptions.LoadWith<Post>(x=>x.Comments);
    db.LoadOptions = dataLoadOptions;

    Tworzymy obiekt klasy DataLoadOptions jest to klasa która definiuje jak się ma zachowywać DataConext podczas ładowania obiektów. Mówimy tu jawnie że jeśli musisz załadować Post to załaduj też jego kolekcję Comments. Taki prosty workaround pozwoli nam cieszyć się działającą aplikacji.

    P.S Chcecie żebym w następnych postach dał kod przykładowych aplikacji ? Dajcie znać w komentarzach.