Nlog - Создание заголовка для файла журнала

Совсем недавно начал экспериментировать с NLog, и мне пришло в голову, что я хотел бы иметь возможность добавить информацию заголовка в верхний файл журнала, такой как:

Executable name
File version
Release Date
Windows User ID
etc...

После некоторых поисков я не смог найти ничего в существующей онлайновой документации или форумах по коду, которые бы указывали на этот тип функциональности. Это возможно? Ранее я всегда включал такую информацию в файлы журналов и нашел ее полезной во многих случаях в прошлом при поиске информации о производственных проблемах на сайтах клиентов. Следует признать, что эта функциональность была специально разработана для решений и не основывалась ни на одной из существующих платформ ведения журналов .NET.

Ответ 1

Я не знаю, как сделать это очень легко. Сказав это, все примеры, которые вы даете, доступны (или довольно легко доступны с помощью специального кода) для добавления в каждое сообщение журнала. То есть каждое зарегистрированное сообщение может быть помечено исполняемым именем, версией файла, датой выпуска, идентификатором пользователя Windows и т.д. Через Layout и LayoutRenderers.

Это, очевидно, не то же самое, что просто создать заголовок в верхней части файла журнала, поэтому он может быть вам не полезен.

С другой стороны, вы можете использовать технику, упомянутую в разделе "Ответ" в этом сообщении, чтобы связать несколько средств визуализации макета с одной и той же целью. Вы можете определить макет, который содержит поля, которые вы хотите в своем заголовке, и установить фильтр в FilteringWrapper только для применения этого макета для первого сообщения сеанса (или вы можете использовать какой-либо другой метод, который он добавляет в выходной файл только один раз).

Используя свой файл NLog.config, вы можете достичь того, чего хотите. Обратите внимание, что я не пробовал это, поэтому я не знаю, является ли этот файл конфигурации действительным или, если это так, если он будет генерировать нужные результаты.

<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="HeaderLayout" value="${processname} ${gdc:item=version} ${gdc:item=releasedate} ${windows-identity}" /> 
    <variable name="NormalLayout" value="${longdate} ${logger} ${level} ${message} /> 

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

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

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

</nlog> 

В вашем коде логика запуска может выглядеть так:

public void Main()
{
  AddHeaderToLogFile();
}

public void AddHeaderToLogFile()
{
  Logger headerlogger = LogManager.GetLogger("HeaderLogger");

  //Use GlobalDiagnosticContext in 2.0, GDC in pre-2.0
  GlobalDiagnosticContext["releasedate"] = GetReleaseDate();    
  GlobalDiagnosticContext["version"] = GetFileVersion();     
  GlobalDiagnosticContext["someotherproperty"] = GetSomeOtherProperty();

  headerlogger.Info("message doesn't matter since it is not specified in the layout");

  //Log file should now have the header as defined by the HeaderLayout

  //You could remove the global properties now if you are not going to log them in any
  //more messages.
}

Идея здесь в том, что вы ставите версию файла, дату выпуска и т.д. в GDC при запуске программы. Запишите сообщение с регистратором "HeaderLogger" . Это сообщение будет записано в файл журнала, используя "HeaderLayout", поскольку "HeaderLogger" связан с целью "fileHeader", которая связана с "HeaderLayout". Поля, определенные в макете заголовка, записываются в файл журнала. Сообщения журнала подпоследовательности, так как они не будут использовать "HeaderLogger" , будут использовать макет "root" (*). Они перейдут к одному файлу, так как цели "file" и "fileHeader" в конечном итоге указывают на одно и то же имя файла.

Прежде чем я начал набирать этот ответ, я не был уверен, насколько легко вы можете добавить заголовок в свой файл журнала. Набрав это, я думаю, что это может быть довольно легко!

Удачи!

[EDIT] Что-то вроде этого может работать, чтобы изменить макет на основе уровня. В первом разделе я определил несколько переменных, каждый из которых определяет макет. В следующем разделе я определил несколько целей, каждый из которых использует один и тот же файл, но фильтруется, чтобы разрешать только сообщения определенного уровня. В заключительном разделе я определяю одно правило, которое будет отправлять все сообщения (отсюда имя журнала "*" ) ко всем целям. Поскольку каждая цель фильтруется по уровню, цель "трассировка" будет писать только сообщения "трассировки" и т.д. Таким образом, сообщения "трассировка" будут записаны с использованием макета "трассировка" , сообщения "отладки" будут записаны с помощью "отладки", макет и т.д. Поскольку все цели в конечном счете записываются в один и тот же файл, все сообщения попадают в один и тот же файл. Я не пробовал это, но думаю, что он, вероятно, сработает.

<variable name="TraceLayout" value="THIS IS A TRACE: ${longdate} ${level:upperCase=true} ${message}" /> 
<variable name="DebugLayout" value="THIS IS A DEBUG: ${longdate} ${level:upperCase=true} ${message}" /> 
<variable name="InfoLayout" value="THIS IS AN INFO: ${longdate} ${level:upperCase=true} ${message}" /> 


<targets async="true"> 
    <target name="fileAsTrace" xsi:type="FilteringWrapper" condition="level==LogLevel.Trace"> 
        <target xsi:type="File" fileName="log.log" layout="${TraceLayout}" /> 
    </target> 
    <target name="fileAsDebug" xsi:type="FilteringWrapper" condition="level==LogLevel.Debug"> 
        <target xsi:type="File" fileName="log.log" layout="${DebugLayout}" /> 
    </target> 
    <target name="fileAsInfo" xsi:type="FilteringWrapper" condition="level==LogLevel.Info"> 
        <target xsi:type="File" fileName="log.log" layout="${InfoLayout}" /> 
    </target>  
</targets> 

<rules> 
    <logger name="*" minlevel="Trace" writeTo="fileAsTrace, fileAsDebug, fileAsInfo" /> 
</rules> 

(Обратите внимание, что здесь я включил только 3 уровня).

Показывая, как (если это работает, так или иначе) применять другой макет, основанный на уровне, это похоже на необычный вариант использования. Я не говорю, что это хорошая идея или плохая идея, но я не могу сказать, что я действительно видел это очень. В зависимости от того, как вы хотите, чтобы ваш конечный результат выглядел, я показал, что вы можете или не можете быть лучшим способом его достижения. Возможно, вы можете опубликовать несколько примеров того, как вы хотите, чтобы ваш результат выглядел.

Вы также можете подумать о принятии моего первоначального ответа, а затем задать новый вопрос об изменении формата вывода на уровень, чтобы мы могли сосредоточить обсуждение в этом вопросе на проблеме уровня/макета. Это зависит от вас, если это кажется полезным или нет.

Это работает:

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

Я установил один макет для каждого уровня ведения журнала, добавив в начале литеральную строку, которая описывает уровень сообщения (это означает, что для каждого уровня используется другой формат). Каждый макет связан с фильтром FilteringWrapper, который фильтрует на основе уровня сообщения и направляет любые сообщения, которые передают фильтр для регистрации в выходном файле. Каждый фильтр FilteringWrapper обертывает один и тот же выходной файл, поэтому все сообщения журнала будут регистрироваться в одном файле.

Вот раздел кода, который я использовал для тестирования:

  logger.Trace("Trace msg");
  logger.Debug("Debug msg");
  logger.Info("Info msg");
  logger.Warn("Warn msg");
  logger.Error("Error msg");
  logger.Fatal("Fatal msg");

И вот как выглядит результат:

This is a TRACE - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Trace | Trace msg
This is a DEBUG - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Debug | Debug msg
This is an INFO - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Info | Info msg
This is a WARN - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Warn | Warn msg
This is an ERROR - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Error | Error msg
This is a FATAL - 2010-11-22 13:20:00.4131 | NLogTest.Form1 | Fatal | Fatal msg

По-видимому, проблема в моей предыдущей информации о конфигурации была пространством между значениями "writeTo". Я думаю, что NLog чувствителен к этому. У меня было что-то вроде "writeTo=blah1, blah2, blah3". Когда я изменил это на "writeTo=blah1,blah2,blah3", ошибка исчезла. Удачи!

Ответ 2

Просто случайно наткнулся на это, глядя на репликацию верхнего/нижнего колонтитула в журнале, который один из моих коллег создал с помощью log4net. Я нашел это в каком-то проекте с открытым исходным кодом и адаптировал его как внутренний пример. Я думаю, что это должно быть просто изменить для ваших нужд.

<target name="logfile2" xsi:type="File" fileName="Logs\NLogDemo2.txt">
  <layout xsi:type="LayoutWithHeaderAndFooter">
    <header xsi:type="SimpleLayout" text="----------NLog Demo Starting---------&#xD;&#xA;"/>
    <layout xsi:type="SimpleLayout" text="${longdate}|${level:uppercase=true}|${logger}|${message}" />
    <footer xsi:type="SimpleLayout" text="----------NLog Demo Ending-----------&#xD;&#xA;"/>
  </layout>
</target>

Это дает мне вывод, который выглядит так:

----------NLog Demo Starting---------

2013-03-01 16:40:19.5404|INFO|Project.Form1|Sample informational message
2013-03-01 16:40:19.5714|WARN|Project.Form1|Sample warning message
2013-03-01 16:40:19.5714|ERROR|Project.Form1|Sample error message
2013-03-01 16:40:19.5714|FATAL|Project.Form1|Sample fatal error message
----------NLog Demo Ending-----------

Я понятия не имею, почему это кажется недокументированным. Единственная ссылка, которую я смог найти, была здесь: https://github.com/nlog/NLog/wiki/LayoutWithHeaderAndFooter

-Jody

Ответ 3

Вы можете создать секцию заголовка/нижнего колонтитула для каждого "экземпляра" (т.е. при первом приеме приложения и в последний раз, когда приложение будет записываться в любой файл), используя макеты как указанные предыдущим ответить:

Подробнее: