Наиболее полезные конфигурации NLog

Каковы наилучшие или наиболее полезные конфигурации для ведения журнала с помощью NLog? (Они могут быть простыми или сложными, если они полезны.)

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

Вот несколько ссылок:

Ответ 1

Некоторые из них относятся к категории общих советов NLog (или ведения журнала), а не к рекомендациям по настройке.

Вот некоторые общие ссылки для ведения журнала здесь, в SO (вы могли бы увидеть некоторые или все из них уже):

log4net vs. Nlog

Протоколы ведения журналов

В чем смысл фасада лесозаготовки?

Почему регистраторы рекомендуют использовать логгер для каждого класса?

Используйте общий шаблон именования вашего регистратора на основе класса Logger logger = LogManager.GetCurrentClassLogger(). Это дает вам высокую степень детализации в ваших регистраторах и дает вам большую гибкость в настройке регистраторов (управление глобально, пространство имен, конкретное имя регистратора и т.д.).

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

Если вы не используете ведение журнала на основе имени, подумайте об именах ваших регистраторов в какой-то иерархической структуре (возможно, по функциональной области), чтобы вы могли поддерживать большую гибкость в своей конфигурации. Например, у вас может быть функциональная область "база данных", "анализ" FA и "ui" FA. У каждого из них могут быть под-области. Таким образом, вы можете запросить регистраторы следующим образом:

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");

И так далее. С помощью иерархических регистраторов вы можете настроить ведение журнала в глобальном масштабе ( "*" или корневой журнал), FA (база данных, анализ, пользовательский интерфейс) или подрайон (Database.Connect и т.д.).

У регистраторов есть много вариантов конфигурации:

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

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

Используйте GlobalDiagnosticContext, MappedDiagnosticContext и NestedDiagnosticContext, чтобы добавить дополнительный контекст к вашему выводу.

Используйте "переменную" в вашем файле конфигурации для упрощения. Например, вы можете определять переменные для своих макетов, а затем ссылаться на переменную в целевой конфигурации, а не указывать макет напрямую.

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

Или вы можете создать "настраиваемый" набор свойств для добавления в макет.

  <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}"/>

Или вы можете делать такие вещи, как создание рендеринга макетов "день" или "месяц" строго по конфигурации:

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

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

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

Если вы ежедневно катите файл, каждый файл может быть назван "Monday.log", "Tuesday.log" и т.д.

Не бойтесь писать свой собственный рендеринг макета. Это легко и позволяет добавлять вашу собственную контекстную информацию в файл журнала через конфигурацию. Например, вот рендеринг макета (на основе NLog 1.x, а не 2.0), который может добавить Trace.CorrelationManager.ActivityId в журнал:

  [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;
    }
  }

Сообщите NLog, где ваши расширения NLog (какая сборка):

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

Использовать собственный рендеринг макета:

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

Использовать асинхронные цели:

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

И целевые обертки по умолчанию:

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

где это необходимо. Дополнительную информацию о них можно найти в документах NLog.

Скажите NLog для просмотра и автоматической перезагрузки конфигурации, если она изменится:

<nlog autoReload="true" /> 

Существует несколько вариантов настройки, помогающих устранить неполадки NLog

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

Подробнее см. в справке NLog.

В NLog 2.0 добавлены обертки LayoutRenderer, которые позволяют выполнять дополнительную обработку на выходе средства визуализации макета (например, обрезание пробелов, верхний регистр, нижний регистр и т.д.).

Не бойтесь обернуть логгер, если хотите изолировать свой код от жесткой зависимости от NLog, но оберните его правильно. Есть примеры того, как обернуть в репозиторий NLog github. Еще одна причина для переноса может заключаться в том, что вы хотите автоматически добавлять определенную контекстную информацию к каждому зарегистрированному сообщению (помещая его в LogEventInfo.Context).

Есть плюсы и минусы обертывания (или абстрагирования) NLog (или любой другой структуры ведения журнала, если на то пошло). С небольшим усилием вы можете найти много информации здесь о SO, представляющем обе стороны.

Если вы рассматриваете возможность переноса, рассмотрите возможность использования Common.Logging, Он работает очень хорошо и позволяет вам легко переключаться на другую структуру ведения журнала, если вы этого хотите. Также, если вы рассматриваете возможность переноса, подумайте о том, как вы будете обрабатывать объекты контекста (GDC, MDC, NDC). Common.Logging в настоящее время не поддерживает абстракцию для них, но предположительно находится в очереди возможностей для добавления.

Ответ 2

Обработка исключений по-разному

Мы часто хотим получить дополнительную информацию, когда есть исключение. Следующая конфигурация имеет две цели: файл и консоль, которые фильтруют, есть ли информация об исключении. (EDIT: Jarek опубликовал около новый способ сделать это в vNext.)

Ключ должен иметь цель-оболочку с 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>

Ответ 3

По-видимому, теперь вы можете использовать NLog с Growl для 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 for WindowsNLog trace message with Growl for WindowsNLog debug message with Growl for WindowsNLog info message with Growl for WindowsNLog warn message with Growl for WindowsNLog error message with Growl for WindowsNLog fatal message with Growl for Windows

Ответ 4

Настроить NLog через XML, но программно

Что? Знаете ли вы, что вы можете указать NLog XML непосредственно в NLog из вашего приложения, а не в том, чтобы NLog читал его из файла конфигурации? Ну, ты можешь. Скажем, у вас есть распределенное приложение, и вы хотите использовать ту же конфигурацию повсюду. Вы можете хранить файл конфигурации в каждом месте и поддерживать его отдельно, вы можете поддерживать его в центральном месте и выталкивать его в места расположения спутников, или вы, вероятно, могли бы сделать много других вещей. Или вы можете сохранить свой XML в базе данных, получить его при запуске приложения и настроить NLog непосредственно с этим XML (возможно, периодически проверять, изменилось ли оно).

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

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

Ответ 5

Регистрация различных уровней в зависимости от наличия или отсутствия ошибки

В этом примере вы можете получить дополнительную информацию, если в коде есть ошибка. В основном, он буферизует сообщения и выводит только те, которые находятся на определенном уровне журнала (например, Warn) , если не выполняется определенное условие (например, была ошибка, поэтому уровень журнала равен = =), затем он выведет больше информации (например, все сообщения с уровня журналa >= трассировка). Поскольку сообщения буферизованы, это позволяет собирать информацию о трассировке о том, что произошло до Ошибка регистрации Error или ErrorException - очень полезно!

Я адаптировал этот вариант из пример в исходном коде. Сначала меня бросили, потому что я оставил AspNetBufferingWrapper (поскольку мой не является ASP-приложением) - получается, что PostFilteringWrapper требует некоторой буферной цели. Обратите внимание, что элемент target-ref, используемый в приведенном выше примере, не может использоваться в NLog 1.0 (я использую 1.0 Refresh для приложения .NET 4.0); необходимо поставить свою цель внутри блока обертки. Также обратите внимание, что логический синтаксис (т.е. больше или меньше символов, < и > ) должен использовать символы, а не escape-символы XML для этих символов (т.е. &gt; и &lt;), иначе NLog будет ошибочно.

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>

Ответ 6

Я представил пару достаточно интересных ответов на этот вопрос:

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

Добавление заголовка:

Вопрос хотел узнать, как добавить заголовок в файл журнала. Использование таких конфигурационных записей позволяет вам определить формат заголовка отдельно от формата остальных записей журнала. Используйте один регистратор, возможно, называемый "headerlogger" для регистрации одного сообщения в начале приложения, и вы получите свой заголовок:

Определите макеты заголовков и файлов:

  <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}" />

Определите цели с помощью макетов:

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

Определите регистраторы:

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

Напишите заголовок, возможно, в начале программы:

  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");

Это в значительной степени просто другая версия идеи "Лечение исключений по-разному".

Запишите каждый уровень журнала с другим макетом

Аналогично, плакат хотел знать, как изменить формат на уровень ведения журнала. Мне было непонятно, какова конечная цель (и может ли она быть достигнута "лучше" ), но я смог предоставить конфигурацию, которая сделала то, что он спросил:

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

Опять же, очень похоже на Обработка исключений по-разному.

Ответ 7

Войдите в Twitter

Основываясь на этот пост о log4net Twitter Appender, я подумал, что попробую свои силы при написании NLog Twitter Target (используя NLog 1.0 обновить, а не 2.0). Увы, до сих пор я не смог получить твиттер, чтобы действительно опубликовать сообщение. Я не знаю, что-то не так в моем коде, Twitter, нашем интернет-соединении/брандмауэре нашей компании или что. Я размещаю код здесь, если кто-то заинтересован в его проверке. Обратите внимание, что существует три разных метода "Почта". Первый, который я пробовал, - PostMessageToTwitter. PostMessageToTwitter по существу такой же, как PostLoggingEvent в сообщении orignal. Если я использую это, я получаю исключение 401. То же исключение получает PostMessageBasic. PostMessage работает без ошибок, но сообщение по-прежнему не распространяется на Twitter. PostMessage и PostMessageBasic основаны на примерах, которые я нашел здесь на SO.

FYI. Я только что нашел комментарий от @Jason Diller на ответ в этом сообщении, в котором говорится, что твиттер будет повернут off basic authentication "в следующем месяце". Это было в мае 2010 года, и сейчас это декабрь 2010 года, поэтому я думаю, что это может быть поэтому не работает.

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 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) + "...";
    }
  }
}

Настройте его так:

Скажите NLog в сборке, содержащей цель:

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

Настроить цель:

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

Если кто-то попробует это и имеет успех, отправьте назад свои выводы.

Ответ 8

Отчеты на внешний веб-сайт/базу данных

Мне нужен способ просто и автоматически сообщать об ошибках (так как пользователи часто не используют) из наших приложений. Самое легкое решение, с которым я мог столкнуться, - это общедоступный URL-адрес - веб-страница, которая может принимать входные данные и хранить их в базе данных, - это данные передаются при ошибке приложения. (Затем база данных может быть проверена dev или script, чтобы узнать, есть ли новые ошибки.)

Я написал веб-страницу в PHP и создал базу данных, пользователя и таблицу mysql для хранения данных. Я решил использовать четыре пользовательские переменные, идентификатор и временную метку. Возможные переменные (включенные в URL-адрес или данные POST):

  • app (имя приложения)
  • msg (сообщение - например, произошло исключение)
  • dev (разработчик - например, Pat)
  • src (источник - это будет происходить из переменной, относящейся к машине, на которой выполнялось приложение, например Environment.MachineName или некоторым таким)
  • log (файл журнала или подробное сообщение)

(Все переменные являются необязательными, но ничего не сообщается, если ни один из них не задан - поэтому, если вы просто посещаете URL-адрес веб-сайта, ничего не отправляется в db.)

Чтобы отправить данные в URL-адрес, я использовал NLog WebService target. (Обратите внимание: сначала у меня было несколько проблем с этой целью. Только когда я посмотрел на источник, я понял, что мой url не может закончиться с /.)

В целом, это не плохая система для хранения вкладок на внешние приложения. (Разумеется, вежливая вещь - сообщить своим пользователям, что вы будете сообщать о возможно чувствительных данных и дать им возможность выбрать/отключить.)

Материал MySQL

(Пользователь db имеет только INSERT привилегии в этой таблице в своей собственной базе данных.)

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'

Код сайта

(PHP 5.3 или 5.2 с PDO включен, файл 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;
}
?>

Код приложения (файл конфигурации 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>

Примечание: могут возникнуть некоторые проблемы с размером файла журнала, но я не выяснил простой способ его усечения (например, la * nix tail).

Ответ 9

Простой способ зарегистрировать каждый уровень журнала с другим макетом с использованием условных макетов

<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}" />

См. http://nlog-project.org/wiki/Conditions для синтаксиса

Ответ 10

Журнал из Silverlight

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

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);
                    }
                }
            }
        }
    } 
}