Najbardziej przydatne konfiguracje NLog [zamknięte]

348

Jakie są najlepsze lub najbardziej przydatne konfiguracje logowania do NLog? (Mogą być proste lub złożone, o ile są przydatne).

Mam na myśli takie przykłady, jak automatyczne przewijanie plików dziennika o określonym rozmiarze, zmiana układu (komunikatu dziennika) niezależnie od tego, czy istnieje wyjątek, eskalacja poziomu dziennika po wystąpieniu błędu itp.

Oto kilka linków:

Poklepać
źródło
3
Oto kilka wskazówek dostrajania wydajności opartych na testach: deep-depth.blogspot.com/2014/01/…
Neil

Odpowiedzi:

391

Niektóre z nich należą do kategorii ogólnych wskazówek dotyczących NLog (lub rejestrowania), a nie wyłącznie sugestii dotyczących konfiguracji.

Oto kilka ogólnych linków do rejestrowania stąd w SO (niektóre lub wszystkie już widziałeś):

log4net vs. Nlog

Rejestrowanie najlepszych praktyk

Jaki jest sens fasady zrębowej?

Dlaczego rejestratorzy zalecają używanie rejestratora dla poszczególnych klas?

Użyj wspólnego wzorca nazewnictwa rejestratora na podstawie klasy Logger logger = LogManager.GetCurrentClassLogger(). Daje to wysoki stopień szczegółowości rejestratorów i daje dużą elastyczność w konfiguracji rejestratorów (sterowanie globalnie, według przestrzeni nazw, według konkretnej nazwy rejestratora itp.).

W razie potrzeby używaj rejestratorów nie opartych na nazwie klasy. Być może masz jedną funkcję, dla której naprawdę chcesz kontrolować rejestrowanie osobno. Być może masz jakieś przekrojowe problemy z rejestrowaniem (rejestrowanie wydajności).

Jeśli nie korzystasz z rejestrowania opartego na nazwie klasy, rozważ nazwanie programów rejestrujących w jakiejś strukturze hierarchicznej (być może według obszaru funkcjonalnego), aby zachować większą elastyczność konfiguracji. Na przykład, możesz mieć obszar funkcjonalny „bazy danych”, FA „analizy” i FA „ui”. Każdy z nich może mieć podobszary. Możesz więc poprosić o rejestratory w następujący sposób:

Logger logger = LogManager.GetLogger("Database.Connect");
Logger logger = LogManager.GetLogger("Database.Query");
Logger logger = LogManager.GetLogger("Database.SQL");
Logger logger = LogManager.GetLogger("Analysis.Financial");
Logger logger = LogManager.GetLogger("Analysis.Personnel");
Logger logger = LogManager.GetLogger("Analysis.Inventory");

I tak dalej. Za pomocą hierarchicznych programów rejestrujących można skonfigurować rejestrowanie globalnie („*” lub root logger), według FA (baza danych, analiza, interfejs użytkownika) lub według podobszaru (Database.Connect itp.).

Rejestratory mają wiele opcji konfiguracji:

<logger name="Name.Space.Class1" minlevel="Debug" writeTo="f1" /> 
<logger name="Name.Space.Class1" levels="Debug,Error" writeTo="f1" /> 
<logger name="Name.Space.*" writeTo="f3,f4" />
<logger name="Name.Space.*" minlevel="Debug" maxlevel="Error" final="true" /> 

Zobacz pomoc NLog, aby uzyskać więcej informacji o tym, co dokładnie oznacza każda z opcji. Prawdopodobnie najbardziej znaczącymi elementami są tutaj zdolność do rejestrowania symboli rejestrujących z użyciem symboli wieloznacznych, koncepcja, że ​​wiele reguł rejestrujących może „wykonać” dla pojedynczej instrukcji rejestrowania oraz że reguła rejestrująca może być oznaczona jako „ostateczna”, więc kolejne reguły nie będą wykonywane dla podana instrukcja logowania.

Użyj GlobalDiagnosticContext, MappedDiagnosticContext i NestedDiagnosticContext, aby dodać dodatkowy kontekst do wyników.

Aby uprościć, użyj „zmiennej” w pliku konfiguracyjnym. Na przykład możesz zdefiniować zmienne dla swoich układów, a następnie odwoływać się do zmiennej w konfiguracji docelowej zamiast określać układ bezpośrednio.

  <variable name="brief" value="${longdate} | ${level} | ${logger} | ${message}"/>
  <variable name="verbose" value="${longdate} | ${machinename} | ${processid} | ${processname} | ${level} | ${logger} | ${message}"/>
  <targets>
    <target name="file" xsi:type="File" layout="${verbose}" fileName="${basedir}/${shortdate}.log" />
    <target name="console" xsi:type="ColoredConsole" layout="${brief}" />
  </targets>

Możesz też utworzyć „niestandardowy” zestaw właściwości, aby dodać go do układu.

  <variable name="mycontext" value="${gdc:item=appname} , ${mdc:item=threadprop}"/>
  <variable name="fmt1withcontext" value="${longdate} | ${level} | ${logger} | [${mycontext}] |${message}"/>
  <variable name="fmt2withcontext" value="${shortdate} | ${level} | ${logger} | [${mycontext}] |${message}"/>

Lub możesz robić takie rzeczy, jak tworzenie rendererów układu „dzień” lub „miesiąc” ściśle poprzez konfigurację:

  <variable name="day" value="${date:format=dddd}"/>
  <variable name="month" value="${date:format=MMMM}"/>
  <variable name="fmt" value="${longdate} | ${level} | ${logger} | ${day} | ${month} | ${message}"/>
  <targets>
    <target name="console" xsi:type="ColoredConsole" layout="${fmt}" />
  </targets>

Możesz także użyć renderowania układu, aby zdefiniować swoją nazwę pliku:

  <variable name="day" value="${date:format=dddd}"/>
  <targets>
    <target name="file" xsi:type="File" layout="${verbose}" fileName="${basedir}/${day}.log" />
  </targets>

Jeśli codziennie rzutujesz swój plik, każdy plik może mieć nazwę „Monday.log”, „Tuesday.log” itp.

Nie bój się napisać własnego renderera układu. Jest to łatwe i pozwala na dodanie własnych informacji kontekstowych do pliku dziennika poprzez konfigurację. Na przykład tutaj jest mechanizm renderowania układu (oparty na NLog 1.x, a nie 2.0), który może dodać Trace.CorrelationManager.ActivityId do dziennika:

  [LayoutRenderer("ActivityId")]
  class ActivityIdLayoutRenderer : LayoutRenderer
  {
    int estimatedSize = Guid.Empty.ToString().Length;

    protected override void Append(StringBuilder builder, LogEventInfo logEvent)
    {
      builder.Append(Trace.CorrelationManager.ActivityId);
    }

    protected override int GetEstimatedBufferSize(LogEventInfo logEvent)
    {
      return estimatedSize;
    }
  }

Powiedz NLog, gdzie są twoje rozszerzenia NLog (jaki zestaw):

  <extensions>
    <add assembly="MyNLogExtensions"/>
  </extensions>

Użyj mechanizmu renderującego układ niestandardowy w następujący sposób:

  <variable name="fmt" value="${longdate} | ${ActivityId} | ${message}"/>

Użyj asynchronicznych celów:

<nlog>
  <targets async="true">
    <!-- all targets in this section will automatically be asynchronous -->
  </targets>
</nlog>

I domyślne opakowania docelowe:

<nlog>  
  <targets>  
    <default-wrapper xsi:type="BufferingWrapper" bufferSize="100"/>  
    <target name="f1" xsi:type="File" fileName="f1.txt"/>  
    <target name="f2" xsi:type="File" fileName="f2.txt"/>  
  </targets>  
  <targets>  
    <default-wrapper xsi:type="AsyncWrapper">  
      <wrapper xsi:type="RetryingWrapper"/>  
    </default-wrapper>  
    <target name="n1" xsi:type="Network" address="tcp://localhost:4001"/>  
    <target name="n2" xsi:type="Network" address="tcp://localhost:4002"/>  
    <target name="n3" xsi:type="Network" address="tcp://localhost:4003"/>  
  </targets>  
</nlog>

w stosownych przypadkach. Zobacz dokumentację NLog, aby uzyskać więcej informacji na ich temat.

Poinformuj NLog, aby obejrzał i automatycznie przeładował konfigurację, jeśli się zmieni:

<nlog autoReload="true" /> 

Istnieje kilka opcji konfiguracji, które pomogą rozwiązać problemy z NLog

<nlog throwExceptions="true" />
<nlog internalLogFile="file.txt" />
<nlog internalLogLevel="Trace|Debug|Info|Warn|Error|Fatal" />
<nlog internalLogToConsole="false|true" />
<nlog internalLogToConsoleError="false|true" />

Aby uzyskać więcej informacji, zobacz Pomoc NLog.

NLog 2.0 dodaje opakowania LayoutRenderer, które pozwalają na dodatkowe przetwarzanie danych wyjściowych renderera układu (takie jak przycinanie białych znaków, górnej i dolnej itd.).

Nie bój się owinąć programu rejestrującego, jeśli chcesz odizolować swój kod od twardej zależności od NLog, ale poprawnie go zawiń. Istnieją przykłady tego, jak owinąć w repozytorium github NLog. Innym powodem zawijania może być to, że chcesz automatycznie dodawać określone informacje kontekstowe do każdej zarejestrowanej wiadomości (umieszczając ją w LogEventInfo.Context).

Istnieje wiele zalet i wad zawijania (lub wyodrębniania) NLog (lub innych ram rejestrowania w tym zakresie). Przy odrobinie wysiłku możesz znaleźć wiele informacji tutaj na SO prezentujących obie strony.

Jeśli rozważasz owijanie, rozważ użycie Common.Logging . Działa całkiem dobrze i pozwala łatwo przejść do innej struktury rejestrowania, jeśli chcesz. Również jeśli rozważasz zawijanie, zastanów się, jak poradzisz sobie z obiektami kontekstowymi (GDC, MDC, NDC). Common.Logging obecnie nie obsługuje dla nich abstrakcji, ale podobno jest w kolejce możliwości dodawania.

wageoghe
źródło
3
Świetna odpowiedź. Wystarczy dodać jedną rzecz: $ {machine} powinna być $ {machinename}. Zobacz github.com/nlog/NLog/wiki/Layout-Renderers .
liang
2
Rozwidliłem Common.Logging i dodałem brakującą abstrakcję, patrz projekt GitHub lub NuGet .
Danny Varod,
Nie udało mi się znaleźć czegoś tak pouczającego na temat nlog w ich własnej dokumentacji, być może przeglądam przykłady github w niewłaściwy sposób? Kto wie.
JARRRRG
Jak korzystać z tego niestandardowego mechanizmu renderującego z interfejsem API (bez pliku konfiguracyjnego)? Oto, co próbuję osiągnąć.
InteXX
Ok, rozumiem. NewLineUkład realizuje zadanie. Oto, co wymyśliłem. To na pewno o wiele prostsze, niż się spodziewałem.
InteXX
65

Traktowanie wyjątków inaczej

Często chcemy uzyskać więcej informacji, gdy istnieje wyjątek. Poniższa konfiguracja ma dwa cele, plik i konsolę, które filtrują, czy są jakieś informacje o wyjątku. (EDYCJA: Jarek napisał o nowej metodzie robienia tego w vNext .)

Kluczem jest posiadanie celu opakowania xsi:type="FilteringWrapper" condition="length('${exception}')>0"

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.mono2.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Warn"
      internalLogFile="nlog log.log"
      >
    <variable name="VerboseLayout" 
              value="${longdate} ${level:upperCase=true} ${message}  
                    (${callsite:includSourcePath=true})"            />
    <variable name="ExceptionVerboseLayout"  
              value="${VerboseLayout} (${stacktrace:topFrames=10})  
                     ${exception:format=ToString}"                  />

    <targets async="true">
        <target name="file" xsi:type="File" fileName="log.log"
                layout="${VerboseLayout}">
        </target>

        <target name="fileAsException"  
                xsi:type="FilteringWrapper" 
                condition="length('${exception}')>0">
            <target xsi:type="File"  
                    fileName="log.log"  
                    layout="${ExceptionVerboseLayout}" />
        </target>

        <target xsi:type="ColoredConsole"
                name="console"
                layout="${NormalLayout}"/>

        <target xsi:type="FilteringWrapper"  
                condition="length('${exception}')>0"  
                name="consoleException">
            <target xsi:type="ColoredConsole" 
                    layout="${ExceptionVerboseLayout}" />
        </target>
    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="console,consoleException" />
        <logger name="*" minlevel="Warn" writeTo="file,fileAsException" />
    </rules>

</nlog>
Poklepać
źródło
1
To całkiem fajne z osobnym celem i FilteringWrapper, aby sformatować wyjątek. Właśnie odpowiedziałem na pytanie niedawno od faceta, który chciał dołączyć do swojego wyjścia moduł renderujący układ {wyjątek}, ale nie chciał uzyskać () widocznego logowania, jeśli NIE ma wyjątku. Ta technika prawdopodobnie dobrze by mu pasowała.
wageoghe
+1 Bardzo miło. Długo miałem tę zakładkę i odniosłem się do „komentarza Pata” z innego pytania SO dotyczącego układu warunkowego.
eduncan911
1
Jeśli zarejestrowany jest wyjątek, zostanie on zarejestrowany dwukrotnie (część VerboseLayout).
Tien Do
2
Właśnie wypróbowałem to jutro w moim projekcie, ponieważ ustawiłeś regułę minlevel = „Warn” na „file, fileAsException”, wszystkie dzienniki będą najpierw rejestrowane z docelowym plikiem (bez filtru), a jeśli jest to wyjątek (filtrowany przez warunek) będzie również rejestrowany za pomocą wyjątku fileAsException.
Tien Do
3
@Tiendq Oh, rozumiem. Ma to sens, chociaż sam wyjątek (szczegółowo) zostanie zarejestrowany tylko raz (ale jego komunikat zostanie zarejestrowany dwukrotnie). Prawdopodobnie możesz to naprawić, dodając condition="length('${exception}')=0(a może to ==) do target name="file".
Pat
60

Najwyraźniej możesz teraz używać NLog z Growl dla Windows .

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <extensions>
        <add assembly="NLog.Targets.GrowlNotify" />
    </extensions>

    <targets>
        <target name="growl" type="GrowlNotify" password="" host="" port="" />
    </targets>

    <rules>
        <logger name="*" minLevel="Trace" appendTo="growl"/>
    </rules>

</nlog>

NLog with Growl dla Windows NL Śledzenie wiadomości z Growl dla Windows Komunikat debugowania NLog z Growl dla Windows NL Wiadomość informacyjna z Growl dla Windows NLog ostrzec wiadomość za pomocą Growl dla Windows Komunikat o błędzie NLog z Growl dla Windows NLog fatalny komunikat z Growl dla Windows

Çağdaş Tekin
źródło
czy możesz mi powiedzieć, co zrobić dla połączenia remort? rzecz działa dla mnie dla localhost, ale kiedy podałem jakiś adres IP w hoście, to nie działa !!
Neel
@Neel, powinieneś sprawdzić ustawienia „Bezpieczeństwo” w Growl na komputerze docelowym. Musisz jawnie włączyć powiadomienia „LAN” i możesz chcieć ustawić hasło (które musiałbyś następnie dodać do swojego celu NLog). Ale nie podobało mi się, że zdalne powiadomienia pojawiły się w Growl z „Origin” „Local Machine”; Musiałbym dodać hosta do wpisów w dzienniku, aby wiedzieć, skąd pochodzą powiadomienia.
Kenny Evitt
Mogę otrzymywać powiadomienia, aby działały na moim komputerze lokalnym, ale nie zdalnie. Moje ustawienia zabezpieczeń nie mają hasła do warczenia, więc dodałem tylko adres IP i port. Ale nic nie zostaje wysłane.
Jack Reilly,
1
Ten projekt nie żyje 100%
deweloper
28

Skonfiguruj NLog poprzez XML, ale programowo

Co? Czy wiesz, że możesz określić plik NLog XML bezpośrednio do pliku NLog w swojej aplikacji, w przeciwieństwie do konieczności odczytu go z pliku konfiguracyjnego przez plik NLog? Cóż, możesz. Załóżmy, że masz aplikację rozproszoną i chcesz wszędzie używać tej samej konfiguracji. Możesz przechowywać plik konfiguracyjny w każdej lokalizacji i utrzymywać go osobno, możesz utrzymać go w centralnej lokalizacji i wypchnąć go do lokalizacji satelitarnych, lub prawdopodobnie możesz zrobić wiele innych rzeczy. Lub możesz zapisać swój XML w bazie danych, pobrać go przy uruchomieniu aplikacji i skonfigurować NLog bezpośrednio z tym XML (może sprawdzać okresowo, czy się zmienił).

  string xml = @"<nlog>
                   <targets>
                     <target name='console' type='Console' layout='${message}' />
                   </targets>

                   <rules>
                     <logger name='*' minlevel='Error' writeTo='console' />
                   </rules>
                 </nlog>";

  StringReader sr = new StringReader(xml);
  XmlReader xr = XmlReader.Create(sr);
  XmlLoggingConfiguration config = new XmlLoggingConfiguration(xr, null);
  LogManager.Configuration = config;
  //NLog is now configured just as if the XML above had been in NLog.config or app.config

  logger.Trace("Hello - Trace"); //Won't log
  logger.Debug("Hello - Debug"); //Won't log
  logger.Info("Hello - Info");   //Won't log
  logger.Warn("Hello - Warn");   //Won't log
  logger.Error("Hello - Error"); //Will log
  logger.Fatal("Hello - Fatal"); //Will log

  //Now let's change the config (the root logging level) ...
  string xml2 = @"<nlog>
                  <targets>
                     <target name='console' type='Console' layout='${message}' />
                   </targets>

                   <rules>
                     <logger name='*' minlevel='Trace' writeTo='console' />
                   </rules>
                 </nlog>";

  StringReader sr2 = new StringReader(xml2);
  XmlReader xr2 = XmlReader.Create(sr2);
  XmlLoggingConfiguration config2 = new XmlLoggingConfiguration(xr2, null);
  LogManager.Configuration = config2;

  logger.Trace("Hello - Trace"); //Will log
  logger.Debug("Hello - Debug"); //Will log
  logger.Info("Hello - Info");   //Will log
  logger.Warn("Hello - Warn");   //Will log
  logger.Error("Hello - Error"); //Will log
  logger.Fatal("Hello - Fatal"); //Will log

Nie jestem pewien, na ile jest to solidne, ale ten przykład stanowi przydatny punkt wyjścia dla osób, które mogą chcieć spróbować skonfigurować w ten sposób.

wageoghe
źródło
działa bardzo dobrze ... oprócz tego, nie można już dynamicznie rekonfigurować systemu rejestrowania. Jest to szczególnie
ważne,
2
To zadziałało, chociaż musiałem napisać „dobry” XML, włączając:<?xml version='1.0' encoding='utf-8' ?><nlog xmlns='http://nlog-project.org/schemas/NLog.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
Gady
1
To dobry sposób na scentralizowaną konfigurację. Przyszli czytelnicy, xml zakodowany na stałe w tym przykładzie jest przeznaczony tylko do prezentacji (IMHO), odczytanie go z bazy danych lub pliku scentralizowanego może być prawdziwą implementacją.
granadaCoder
@wageoghe; Dlaczego dostaję błąd (rejestrator nie istnieje)? Po prostu
kopiuję i wklejam
22

Rejestrowanie różnych poziomów w zależności od tego, czy wystąpił błąd

Ten przykład pozwala uzyskać więcej informacji w przypadku wystąpienia błędu w kodzie. Zasadniczo buforuje komunikaty i wysyła tylko te na określonym poziomie dziennika (np. Ostrzegaj), chyba że spełniony jest określony warunek (np. Wystąpił błąd, więc poziom dziennika wynosi> = Błąd), a następnie wyświetli więcej informacji (np. wszystkie wiadomości z poziomów dziennika> = Śledzenie). Ponieważ komunikaty są buforowane, pozwala to gromadzić informacje o śledzeniu, co się wydarzyło przed zarejestrowaniem wyjątku Error lub ErrorException - bardzo przydatne!

Zaadaptowałem ten z przykładu w kodzie źródłowym . Najpierw zostałem wyrzucony, ponieważ pominąłemAspNetBufferingWrapper (ponieważ moja nie jest aplikacją ASP) - okazuje się, że PostFilteringWrapper wymaga buforowanego celu. Zauważ, że target-refelement użyty w powyższym przykładzie nie może być używany w NLog 1.0 (używam 1.0 Refresh dla aplikacji .NET 4.0); konieczne jest umieszczenie celu w bloku opakowania. Zauważ też, że składnia logiczna (tj. Symbole większe niż lub mniejsze niż, <i>) musi używać symboli, a nie ucieczki XML dla tych symboli (tj. &gt;I &lt;), w przeciwnym razie NLog popełni błąd.

app.config:

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

    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          throwExceptions="true" internalLogToConsole="true" internalLogLevel="Warn" internalLogFile="nlog.log">
        <variable name="appTitle" value="My app"/>
        <variable name="csvPath" value="${specialfolder:folder=Desktop:file=${appTitle} log.csv}"/>

        <targets async="true">
            <!--The following will keep the default number of log messages in a buffer and write out certain levels if there is an error and other levels if there is not. Messages that appeared before the error (in code) will be included, since they are buffered.-->
            <wrapper-target xsi:type="BufferingWrapper" name="smartLog">
                <wrapper-target xsi:type="PostFilteringWrapper">
                    <!--<target-ref name="fileAsCsv"/>-->
                    <target xsi:type="File" fileName="${csvPath}"
                    archiveAboveSize="4194304" concurrentWrites="false" maxArchiveFiles="1" archiveNumbering="Sequence"
                    >
                        <layout xsi:type="CsvLayout" delimiter="Tab" withHeader="false">
                            <column name="time" layout="${longdate}" />
                            <column name="level" layout="${level:upperCase=true}"/>
                            <column name="message" layout="${message}" />
                            <column name="callsite" layout="${callsite:includeSourcePath=true}" />
                            <column name="stacktrace" layout="${stacktrace:topFrames=10}" />
                            <column name="exception" layout="${exception:format=ToString}"/>
                            <!--<column name="logger" layout="${logger}"/>-->
                        </layout>
                    </target>

                     <!--during normal execution only log certain messages--> 
                    <defaultFilter>level >= LogLevel.Warn</defaultFilter>

                     <!--if there is at least one error, log everything from trace level--> 
                    <when exists="level >= LogLevel.Error" filter="level >= LogLevel.Trace" />
                </wrapper-target>
            </wrapper-target>

        </targets>

        <rules>
            <logger name="*" minlevel="Trace" writeTo="smartLog"/>
        </rules>
    </nlog>
</configuration>
Poklepać
źródło
W niektórych wersjach NLog (dla mono i chyba 2.0) powoduje to wyjątek StackOverflowException, ale nie w innych (odświeżanie NLog 1).
Pat
Jeśli chodzi o przepełnienie - wydaje się, że jest to spowodowane jedynie układem typu CSV - jeśli robię standardowy układ, nie ma problemu.
Pat
Do czego służy file-asCsv? Staram się, aby ten przykład działał przeciwko NLog v2.0.0.2000, ale jak dotąd zawodzi.
Peter Mounce
@PeterMounce fileAsCsvCel-ref jest tylko artefaktem z moich testów. Uważam, że NLog 2 ma / miał problemy z CsvLayouts, których nie miał NLog 1 / Refresh.
Pat
22

Podałem kilka dość interesujących odpowiedzi na to pytanie:

Nlog - Generowanie sekcji nagłówka dla pliku dziennika

Dodawanie nagłówka:

Pytanie chciało wiedzieć, jak dodać nagłówek do pliku dziennika. Korzystanie z takich wpisów konfiguracji pozwala zdefiniować format nagłówka oddzielnie od formatu pozostałych wpisów dziennika. Użyj pojedynczego programu rejestrującego, nazywanego być może „headerlogger”, aby zarejestrować pojedynczy komunikat na początku aplikacji, a otrzymasz nagłówek:

Zdefiniuj układ nagłówka i pliku:

  <variable name="HeaderLayout" value="This is the header.  Start time = ${longdate} Machine = ${machinename} Product version = ${gdc:item=version}"/>
  <variable name="FileLayout" value="${longdate} | ${logger} | ${level} | ${message}" />

Zdefiniuj cele za pomocą układów:

<target name="fileHeader" xsi:type="File" fileName="xxx.log" layout="${HeaderLayout}" />
<target name="file" xsi:type="File" fileName="xxx.log" layout="${InfoLayout}" />

Zdefiniuj rejestratory:

<rules>
  <logger name="headerlogger" minlevel="Trace" writeTo="fileHeader" final="true" />
  <logger name="*" minlevel="Trace" writeTo="file" />
</rules>

Napisz nagłówek, prawdopodobnie na początku programu:

  GlobalDiagnosticsContext.Set("version", "01.00.00.25");

  LogManager.GetLogger("headerlogger").Info("It doesn't matter what this is because the header format does not include the message, although it could");

To w dużej mierze kolejna wersja idei „Traktowanie wyjątków inaczej”.

Zaloguj każdy poziom dziennika przy użyciu innego układu

Podobnie plakat chciał wiedzieć, jak zmienić format według poziomu rejestrowania. Nie było dla mnie jasne, jaki był cel końcowy (i czy można go osiągnąć w „lepszy” sposób), ale byłem w stanie zapewnić konfigurację, która spełniłaby jego oczekiwania:

  <variable name="TraceLayout" value="This is a TRACE - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <variable name="DebugLayout" value="This is a DEBUG - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <variable name="InfoLayout" value="This is an INFO - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <variable name="WarnLayout" value="This is a WARN - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <variable name="ErrorLayout" value="This is an ERROR - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <variable name="FatalLayout" value="This is a FATAL - ${longdate} | ${logger} | ${level} | ${message}"/> 
  <targets> 
    <target name="fileAsTrace" xsi:type="FilteringWrapper" condition="level==LogLevel.Trace"> 
      <target xsi:type="File" fileName="xxx.log" layout="${TraceLayout}" /> 
    </target> 
    <target name="fileAsDebug" xsi:type="FilteringWrapper" condition="level==LogLevel.Debug"> 
      <target xsi:type="File" fileName="xxx.log" layout="${DebugLayout}" /> 
    </target> 
    <target name="fileAsInfo" xsi:type="FilteringWrapper" condition="level==LogLevel.Info"> 
      <target xsi:type="File" fileName="xxx.log" layout="${InfoLayout}" /> 
    </target> 
    <target name="fileAsWarn" xsi:type="FilteringWrapper" condition="level==LogLevel.Warn"> 
      <target xsi:type="File" fileName="xxx.log" layout="${WarnLayout}" /> 
    </target> 
    <target name="fileAsError" xsi:type="FilteringWrapper" condition="level==LogLevel.Error"> 
      <target xsi:type="File" fileName="xxx.log" layout="${ErrorLayout}" /> 
    </target> 
    <target name="fileAsFatal" xsi:type="FilteringWrapper" condition="level==LogLevel.Fatal"> 
      <target xsi:type="File" fileName="xxx.log" layout="${FatalLayout}" /> 
    </target> 
  </targets> 


    <rules> 
      <logger name="*" minlevel="Trace" writeTo="fileAsTrace,fileAsDebug,fileAsInfo,fileAsWarn,fileAsError,fileAsFatal" /> 
      <logger name="*" minlevel="Info" writeTo="dbg" /> 
    </rules> 

Ponownie bardzo podobny do Traktowania wyjątków inaczej .

wageoghe
źródło
1
Fajne! Nie widziałem GlobalDiagnosticsContextwcześniej.
Pat
10

Zaloguj się na Twitterze

Na podstawie tego postu o log4net na Twitterze Appender, Myślałem, że spróbuję swoich sił w pisaniu NLog Twitter Target (używając odświeżania NLog 1.0, a nie 2.0). Niestety, do tej pory nie udało mi się uzyskać tweeta, aby faktycznie opublikować pomyślnie. Nie wiem, czy coś jest nie tak z moim kodem, Twitterem, połączeniem internetowym / zaporą naszej firmy, czy co. Publikuję kod tutaj, na wypadek, gdyby ktoś był zainteresowany jego wypróbowaniem. Pamiętaj, że istnieją trzy różne metody „wysyłania”. Pierwszy, którego próbowałem, to PostMessageToTwitter. PostMessageToTwitter jest zasadniczo taki sam jak PostLoggingEvent w oryginalnym poście. Jeśli go użyję, otrzymam wyjątek 401. PostMessageBasic otrzymuje ten sam wyjątek. PostMessage działa bez błędów, ale wiadomość nadal nie jest na Twitterze. PostMessage i PostMessageBasic są oparte na przykładach, które znalazłem tutaj na SO.

Do Twojej wiadomości - Właśnie znalazłem komentarz @Jason Diller do odpowiedzi w tym poście, który mówi, że Twitter wyłączy podstawowe uwierzytelnianie „w przyszłym miesiącu”. To było w maju 2010 roku, a teraz w grudniu 2010 roku, więc myślę, że to może być powód, dla którego to nie działa.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Web;
using System.IO;

using NLog;
using NLog.Targets;
using NLog.Config;

namespace NLogExtensions
{
  [Target("TwitterTarget")]
  public class TwitterTarget : TargetWithLayout
  {
    private const string REQUEST_CONTENT_TYPE = "application/x-www-form-urlencoded";  

    private const string REQUEST_METHOD = "POST";  

    // The source attribute has been removed from the Twitter API,  
    // unless you're using OAuth.  
    // Even if you are using OAuth, there's still an approval process.  
    // Not worth it; "API" will work for now!  
    // private const string TWITTER_SOURCE_NAME = "Log4Net";  
    private const string TWITTER_UPDATE_URL_FORMAT = "http://twitter.com/statuses/update.xml?status={0}";  

    [RequiredParameter]
    public string TwitterUserName { get; set; }

    [RequiredParameter]
    public string TwitterPassword { get; set; }

    protected override void Write(LogEventInfo logEvent)
    {
      if (string.IsNullOrWhiteSpace(TwitterUserName) || string.IsNullOrWhiteSpace(TwitterPassword)) return;

      string msg = this.CompiledLayout.GetFormattedMessage(logEvent);

      if (string.IsNullOrWhiteSpace(msg)) return;

      try
      {
        //PostMessageToTwitter(msg);
        PostMessageBasic(msg);
      }
      catch (Exception ex)
      {
        //Should probably do something here ...
      }
    }

    private void PostMessageBasic(string msg)
    {
      // Create a webclient with the twitter account credentials, which will be used to set the HTTP header for basic authentication 
      WebClient client = new WebClient { Credentials = new NetworkCredential { UserName = TwitterUserName, Password = TwitterPassword } };

      // Don't wait to receive a 100 Continue HTTP response from the server before sending out the message body 
      ServicePointManager.Expect100Continue = false;

      // Construct the message body 
      byte[] messageBody = Encoding.ASCII.GetBytes("status=" + msg);

      // Send the HTTP headers and message body (a.k.a. Post the data) 
      client.UploadData(@"http://twitter.com/statuses/update.xml", messageBody);
    }

    private void PostMessage(string msg)
    {
      string user = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(TwitterUserName + ":" + TwitterPassword));
      byte [] bytes = System.Text.Encoding.UTF8.GetBytes("status=" + msg.ToTweet());
      HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://twitter.com/statuses/update.xml");
      request.Method = "POST";
      request.ServicePoint.Expect100Continue = false;
      request.Headers.Add("Authorization", "Basic " + user);
      request.ContentType = "application/x-www-form-urlencoded";
      request.ContentLength = bytes.Length;
      Stream reqStream = request.GetRequestStream();
      reqStream.Write(bytes, 0, bytes.Length);
      reqStream.Close();
    }

    private void PostMessageToTwitter(string msg)
    {
      var updateRequest = HttpWebRequest.Create(string.Format(TWITTER_UPDATE_URL_FORMAT,
                                                HttpUtility.UrlEncode(msg.ToTweet()))) as HttpWebRequest;
      updateRequest.ContentLength = 0;
      updateRequest.ContentType = REQUEST_CONTENT_TYPE;
      updateRequest.Credentials = new NetworkCredential(TwitterUserName, TwitterPassword);
      updateRequest.Method = REQUEST_METHOD;

      updateRequest.ServicePoint.Expect100Continue = false;

      var updateResponse = updateRequest.GetResponse() as HttpWebResponse;

      if (updateResponse.StatusCode != HttpStatusCode.OK && updateResponse.StatusCode != HttpStatusCode.Continue)
      {
        throw new Exception(string.Format("An error occurred while invoking the Twitter REST API [Response Code: {0}]", updateResponse.StatusCode));
      }
    }
  }

  public static class Extensions
  {
    public static string ToTweet(this string s)
    {
      if (string.IsNullOrEmpty(s) || s.Length < 140)
      {
        return s;
      }

      return s.Substring(0, 137) + "...";
    }
  }
}

Skonfiguruj to w następujący sposób:

Powiedz NLog zestawowi zawierającemu cel:

<extensions>
  <add assembly="NLogExtensions"/>
</extensions>

Skonfiguruj cel:

<targets>
    <target name="twitter" type="TwitterTarget" TwitterUserName="yourtwittername" TwitterPassword="yourtwitterpassword" layout="${longdate} ${logger} ${level} ${message}" />
</targets>

Jeśli ktoś to wypróbuje i odniesie sukces, odeślij swoje wyniki.

wageoghe
źródło
Twitter używa OAuth - .NET ma dostawcę w dotnetopenauth.net
Pat
7

Raportowanie do zewnętrznej strony internetowej / bazy danych

Chciałem sposobu na proste i automatyczne zgłaszanie błędów (ponieważ użytkownicy często tego nie robią) z naszych aplikacji. Najprostszym rozwiązaniem, jakie mogłem wymyślić, był publiczny adres URL - strona internetowa, która mogła pobierać dane wejściowe i przechowywać je w bazie danych - wysyłane dane w przypadku błędu aplikacji. (Baza danych może być następnie sprawdzona przez programistę lub skrypt, aby dowiedzieć się, czy wystąpiły nowe błędy).

Napisałem stronę internetową w PHP i stworzyłem bazę danych mysql, użytkownika i tabelę do przechowywania danych. Zdecydowałem się na cztery zmienne użytkownika, identyfikator i znacznik czasu. Możliwe zmienne (zawarte w adresie URL lub jako dane POST) to:

  • app (Nazwa aplikacji)
  • msg (komunikat - np. Wystąpił wyjątek ...)
  • dev (programista - np. Pat)
  • src(źródło - pochodziłoby od zmiennej odnoszącej się do komputera, na którym aplikacja była uruchomiona, np. Environment.MachineNamelub jakiś inny)
  • log (plik dziennika lub pełny komunikat)

(Wszystkie zmienne są opcjonalne, ale nic nie jest raportowane, jeśli żadna z nich nie jest ustawiona - więc jeśli tylko odwiedzisz adres URL witryny, nic nie zostanie wysłane do bazy danych.)

Aby wysłać dane do adresu URL, użyłem nlog za WebServicecel . (Uwaga: na początku miałem kilka problemów z tym celem. Dopiero gdy spojrzałem na źródło, zorientowałem się, że urlnie mogę zakończyć się na /.)

Podsumowując, nie jest to zły system do kontrolowania aplikacji zewnętrznych. (Oczywiście uprzejmie należy poinformować użytkowników , że będziesz zgłaszać potencjalnie wrażliwe dane, i dać im możliwość wyrażenia zgody na rezygnację.)

Rzeczy MySQL

(Użytkownik db ma INSERTuprawnienia tylko do tej jednej tabeli we własnej bazie danych).

CREATE TABLE `reports` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `ts` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `applicationName` text,
  `message` text,
  `developer` text,
  `source` text,
  `logData` longtext,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='storage place for reports from external applications'

Kod strony internetowej

(PHP 5.3 lub 5.2 z włączoną funkcją PDO , plik znajduje się w folderze)index.php/report

<?php
$app = $_REQUEST['app'];
$msg = $_REQUEST['msg'];
$dev = $_REQUEST['dev'];
$src = $_REQUEST['src'];
$log = $_REQUEST['log'];

$dbData =
    array(  ':app' => $app,
            ':msg' => $msg,
            ':dev' => $dev,
            ':src' => $src,
            ':log' => $log
    );
//print_r($dbData); // For debugging only! This could allow XSS attacks.
if(isEmpty($dbData)) die("No data provided");

try {
$db = new PDO("mysql:host=$host;dbname=reporting", "reporter", $pass, array(
    PDO::ATTR_PERSISTENT => true
));
$s = $db->prepare("INSERT INTO reporting.reports 
    (
    applicationName, 
    message, 
    developer, 
    source, 
    logData
    )
    VALUES
    (
    :app, 
    :msg, 
    :dev, 
    :src, 
    :log
    );"
    );
$s->execute($dbData);
print "Added report to database";
} catch (PDOException $e) {
// Sensitive information can be displayed if this exception isn't handled
//print "Error!: " . $e->getMessage() . "<br/>";
die("PDO error");
}

function isEmpty($array = array()) {
    foreach ($array as $element) {
        if (!empty($element)) {
            return false;
        }
    }
    return true;
}
?>

Kod aplikacji (plik konfiguracyjny NLog)

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true" internalLogToConsole="true" internalLogLevel="Warn" internalLogFile="nlog.log">
    <variable name="appTitle" value="My External App"/>
    <variable name="csvPath" value="${specialfolder:folder=Desktop:file=${appTitle} log.csv}"/>
    <variable name="developer" value="Pat"/>

    <targets async="true">
        <!--The following will keep the default number of log messages in a buffer and write out certain levels if there is an error and other levels if there is not. Messages that appeared before the error (in code) will be included, since they are buffered.-->
        <wrapper-target xsi:type="BufferingWrapper" name="smartLog">
            <wrapper-target xsi:type="PostFilteringWrapper">
                <target xsi:type="File" fileName="${csvPath}"
                archiveAboveSize="4194304" concurrentWrites="false" maxArchiveFiles="1" archiveNumbering="Sequence"
                >
                    <layout xsi:type="CsvLayout" delimiter="Comma" withHeader="false">
                        <column name="time" layout="${longdate}" />
                        <column name="level" layout="${level:upperCase=true}"/>
                        <column name="message" layout="${message}" />
                        <column name="callsite" layout="${callsite:includeSourcePath=true}" />
                        <column name="stacktrace" layout="${stacktrace:topFrames=10}" />
                        <column name="exception" layout="${exception:format=ToString}"/>
                        <!--<column name="logger" layout="${logger}"/>-->
                    </layout>
                </target>

                 <!--during normal execution only log certain messages--> 
                <defaultFilter>level >= LogLevel.Warn</defaultFilter>

                 <!--if there is at least one error, log everything from trace level--> 
                <when exists="level >= LogLevel.Error" filter="level >= LogLevel.Trace" />
            </wrapper-target>
        </wrapper-target>

        <target xsi:type="WebService" name="web"
                url="http://example.com/report" 
                methodName=""
                namespace=""
                protocol="HttpPost"
                >
            <parameter name="app" layout="${appTitle}"/>
            <parameter name="msg" layout="${message}"/>
            <parameter name="dev" layout="${developer}"/>
            <parameter name="src" layout="${environment:variable=UserName} (${windows-identity}) on ${machinename} running os ${environment:variable=OSVersion} with CLR v${environment:variable=Version}"/>
            <parameter name="log" layout="${file-contents:fileName=${csvPath}}"/>
        </target>

    </targets>

    <rules>
        <logger name="*" minlevel="Trace" writeTo="smartLog"/>
        <logger name="*" minlevel="Error" writeTo="web"/>
    </rules>
</nlog>

Uwaga: mogą występować pewne problemy z rozmiarem pliku dziennika, ale nie znalazłem prostego sposobu na jego obcięcie (np. tailPolecenie la * nix ).

Poklepać
źródło
To zadziałało w przypadku jednego projektu, ale w innych miałem problemy z url: InnerException: System.InvalidCastException Message = Niepoprawna rzutowanie z „System.String” na „System.Uri”. Źródło = mscorlib StackTrace: at System.Convert.DefaultToType (IConvertible value, Type targetType, IFormatProvider dostawca) w System.String.System.IConvertible.ToType (Type type, IFormatProvider dostawca) w System.Convert.ChangeType (wartość obiektu, typ konwersji Type) , Dostawca IFormatProvider)
Pat
Inną opcją, jeśli chcesz móc monitorować dziennik i otrzymywać powiadomienia w przypadku błędu, byłby cel na Twitterze. Zobacz ten link do aplikacji dołączającej do Twittera napisanej dla log4net: twitterappender.codeplex.com Oryginalny blog na ten temat omawiający jest tutaj: caseywatson.com/2009/07/07/log4net-twitter-awesome Napisanie czegoś podobnego dla NLog.
wageoghe
Wygłupiałem się, pisząc NLog TwitterTarget, ale nie udało mi się opublikować Tweeta. Kod opublikowałem jako odpowiedź. Jeśli chcesz, możesz go wypróbować.
wageoghe
6

Łatwiejszy sposób rejestrowania każdego poziomu dziennika przy użyciu innego układu przy użyciu układów warunkowych

<variable name="VerboseLayout" value="${level:uppercase=true}: ${longdate} | ${logger}    : 
${when:when=level == LogLevel.Trace:inner=MONITOR_TRACE ${message}} 
${when:when=level == LogLevel.Debug:inner=MONITOR_DEBUG ${message}} 
${when:when=level == LogLevel.Info:inner=MONITOR_INFO ${message}} 
${when:when=level == LogLevel.Warn:inner=MONITOR_WARN ${message}} 
${when:when=level == LogLevel.Error:inner=MONITOR_ERROR ${message}} 
${when:when=level == LogLevel.Fatal:inner=MONITOR_CRITICAL ${message}} |     
${exception:format=tostring} | ${newline} ${newline}" />

Zobacz https://github.com/NLog/NLog/wiki/When-Filter dla składni

Lukie
źródło
4

Zaloguj się z Silverlight

Korzystając z NLog z Silverlight, możesz wysłać dane śledzenia po stronie serwera za pośrednictwem dostarczonej usługi internetowej. Możesz także zapisywać pliki lokalne w Izolowanym magazynie, które przydają się, jeśli serwer WWW jest niedostępny. Zobacz tutaj, aby uzyskać szczegółowe informacje, np. Użyj czegoś takiego, aby stać się celem:

namespace NLogTargets
{
    [Target("IsolatedStorageTarget")]
    public sealed class IsolatedStorageTarget : TargetWithLayout
    {
        IsolatedStorageFile _storageFile = null;
        string _fileName = "Nlog.log"; // Default. Configurable through the 'filename' attribute in nlog.config

        public IsolatedStorageTarget()
        {
        }

        ~IsolatedStorageTarget()
        {
            if (_storageFile != null)
            {
                _storageFile.Dispose();
                _storageFile = null;
            }
        }

        public string filename
        {
            set
            {
                _fileName = value; 
            }
            get
            {
                return _fileName;  
            }
         }

        protected override void Write(LogEventInfo logEvent)
        {
            try
            {
                writeToIsolatedStorage(this.Layout.Render(logEvent));
            }
            catch (Exception e)
            {
                // Not much to do about his....
            }
        }

        public void writeToIsolatedStorage(string msg)
        {
            if (_storageFile == null)
                _storageFile = IsolatedStorageFile.GetUserStoreForApplication();
            using (IsolatedStorageFile isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                // The isolated storage is limited in size. So, when approaching the limit
                // simply purge the log file. (Yeah yeah, the file should be circular, I know...)
                if (_storageFile.AvailableFreeSpace < msg.Length * 100)
                {
                    using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(_fileName, FileMode.Truncate, FileAccess.Write, isolatedStorage))
                    { }
                }
                // Write to isolated storage
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(_fileName, FileMode.Append, FileAccess.Write, isolatedStorage))
                {
                    using (TextWriter writer = new StreamWriter(stream))
                    {
                        writer.WriteLine(msg);
                    }
                }
            }
        }
    } 
}
BaBu
źródło