最有用的 NLog 配置

Posted

技术标签:

【中文标题】最有用的 NLog 配置【英文标题】:Most useful NLog configurations [closed] 【发布时间】:2011-05-04 17:53:18 【问题描述】:

使用 NLog 进行日志记录的最佳或最有用的配置是什么? (这些可以是简单的也可以是复杂的,只要它们有用。)

我正在考虑一些示例,例如自动滚动特定大小的日志文件、无论是否存在异常都更改布局(日志消息)、一旦发生错误就升级日志级别等。

这里有一些链接:

NLog Demo Examples in the source

【问题讨论】:

以下是一些基于测试的性能调优技巧:deep-depth.blogspot.com/2014/01/… 【参考方案1】:

其中一些属于一般 NLog(或日志记录)提示类别,而不是严格的配置建议。

这里有一些来自 SO 的常规日志链接(您可能已经看过其中的部分或全部):

log4net vs. Nlog

Logging best practices

What's the point of a logging facade?

Why do loggers recommend using a logger per class?

使用基于类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(数据库、分析、UI)或子区域(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 help,了解有关每个选项的确切含义的更多信息。可能这里最值得注意的项目是通配符记录器规则的能力,多个记录器规则可以为单个记录语句“执行”的概念,并且记录器规则可以标记为“最终”,因此后续规则不会执行给定日志语句。

使用 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 目前不支持对它们进行抽象,但据说它在要添加的功能队列中。

【讨论】:

很好的答案。只需添加一件事,$machine 应该是 $machinename。见github.com/nlog/NLog/wiki/Layout-Renderers。 我分叉了 Common.Logging 并添加了缺失的抽象,请参阅 GitHub project 或 NuGet。 我在他们自己的文档中找不到任何关于 nlog 的信息,也许我以错误的方式浏览 github 示例?谁知道呢。 如何将自定义渲染器与 API 一起使用(无配置文件)? Here's 我正在努力完成的工作。 好的,知道了。 NewLine 布局完成了任务。 Here's 我想出了什么。它肯定比我预期的要简单得多。【参考方案2】:

以不同的方式处理异常

我们经常希望在出现异常时获得更多信息。以下配置有两个目标,一个文件和控制台,它们过滤是否有任何异常信息。 (编辑:Jarek 发布了关于 new method of doing this in vNext 的帖子。)

关键是要有一个带有xsi:type="FilteringWrapper" condition="length('$exception')&gt;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>

【讨论】:

使用单独的目标和 FilteringWrapper 来格式化异常非常酷。我刚刚回答了一个人最近提出的一个问题,他想在他的输出中包含 exception 布局渲染器,但如果没有异常,他不想获得显然已记录的 ()。这种技术可能对他很有效。 +1 非常好。我已经收藏了很长时间,并从另一个关于条件布局的 SO 问题中提到了“Pat 的评论”。 如果记录了异常,则会记录两次(VerboseLayout 部分)。 我明天刚在我的项目中尝试过,因为您将规则 minlevel="Warn" 设置为“file, fileAsException”,所有日志将首先使用文件目标(无过滤器)记录,如果这是一个异常(按条件过滤),它也将使用 fileAsException 记录。 @Tiendq 哦,我明白了。这是有道理的,尽管异常本身(详细)只会被记录一次(但它的消息将被记录两次)。您可以通过将condition="length('$exception')=0(或者可能是==)添加到target name="file" 来解决这个问题。【参考方案3】:

显然,您现在可以使用NLog with Growl for 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>

【讨论】:

你能告诉我怎么做remort连接吗?这对我来说适用于本地主机,但是当我在主机中提供了一些 IP 地址时它不起作用! @Neel,您应该检查目标计算机上 Growl 中的“安全”设置。您必须明确启用“LAN”通知,并且您可能想要设置密码(然后您需要将其添加到您的 NLog 目标中)。但我不喜欢 Growl 中出现的远程通知,其“来源”为“本地机器”;我必须将主机添加到日志条目中才能知道通知的来源。 我可以让通知在我的本地机器上工作,但不能远程工作。我的安全设置在咆哮时没有密码,所以我只添加了 IP 和端口。但是什么都没有发送。 这个项目100%死了【参考方案4】:

通过 XML 配置 NLog,但以编程方式

什么?您是否知道您可以从您的应用程序中直接将 NLog XML 指定为 NLog,而不是让 NLog 从配置文件中读取它?嗯,你可以。假设您有一个分布式应用程序,并且您想在任何地方使用相同的配置。您可以在每个位置保留一个配置文件并单独维护它,您可以在中心位置维护一个并将其推送到卫星位置,或者您可能会做很多其他事情。或者,您可以将您的 XML 存储在数据库中,在应用启动时获取它,然后使用该 XML 直接配置 NLog(可能会定期检查它是否已更改)。

  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

我不确定这有多健壮,但这个示例为可能想要尝试这样配置的人提供了一个有用的起点。

【讨论】:

它工作得很好......除非使用它,它不再可能动态地重新配置日志系统。如果您链接到外部文件(包含),则尤其如此 这行得通,虽然我必须写出“好”的 XML,包括:&lt;?xml version='1.0' encoding='utf-8' ?&gt;&lt;nlog xmlns='http://nlog-project.org/schemas/NLog.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'&gt; 这是一个很好的集中配置。未来的读者,此示例中的硬编码 xml 仅用于演示(恕我直言),从数据库或集中文件中读取它可能是真正的实现。 @wageoghe ;为什么我得到错误(记录器不存在)?我只是复制并粘贴代码【参考方案5】:

根据是否有错误记录不同的级别

此示例允许您在代码中出现错误时获取更多信息。基本上,它会缓冲消息并仅输出特定日志级别的消息(例如警告)除非满足特定条件(例如出现错误,因此日志级别 >= 错误),然后它将输出更多信息(例如来自日志级别的所有消息> = Trace)。由于消息是缓冲的,因此您可以收集有关记录错误或 ErrorException 之前发生的事情的跟踪信息 - 非常有用!

我从an example in the source code 改编了这个。起初我被抛出,因为我遗漏了 AspNetBufferingWrapper(因为我的不是 ASP 应用程序) - 事实证明 PostFilteringWrapper 需要一些缓冲目标。请注意,上面链接示例中使用的target-ref 元素不能在 NLog 1.0 中使用(我正在为 .NET 4.0 应用程序使用 1.0 Refresh);有必要将您的目标放在包装块内。另请注意,逻辑语法(即大于或小于符号、)必须使用符号,而不是这些符号的 XML 转义(即 &amp;gt;&amp;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>

【讨论】:

在某些版本的 NLog 中(对于单声道,我认为是 2.0),这会导致 ***Exception,但在其他版本中不会(NLog 1 刷新)。 关于溢出 - 这似乎只是由于布局是 CSV 类型 - 如果我做一个常规布局没有问题。 fileAsCsv target-ref 的用途是什么?我试图让这个示例适用于 NLog v2.0.0.2000,但到目前为止失败了。 @PeterMounce fileAsCsv target-ref 只是我测试的产物。我相信 NLog 2 与 CsvLayouts 有/有问题,而 NLog 1/Refresh 没有。【参考方案6】:

我为这个问题提供了几个相当有趣的答案:

Nlog - Generating Header Section for a log file

添加标题:

问题想知道如何向日志文件添加标头。使用这样的配置条目允许您将标头格式与其余日志条目的格式分开定义。使用单个记录器(可能称为“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> 

再次,非常类似于以不同的方式处理异常

【讨论】:

酷!我之前没见过GlobalDiagnosticsContext【参考方案7】:

登录 Twitter

基于this post about a log4net Twitter Appender,我想我会尝试编写一个 NLog Twitter Target(使用 NLog 1.0 刷新,而不是 2.0)。唉,到目前为止,我还没有能够成功发布一条推文。我不知道我的代码、Twitter、我们公司的互联网连接/防火墙是否有问题,或者是什么。我在这里发布代码以防有人有兴趣尝试一下。请注意,有三种不同的“发布”方法。我尝试的第一个是 PostMessageToTwitter。 PostMessageToTwitter 本质上与原始帖子中的 PostLoggingEvent 相同。如果我使用它,我会得到 401 异常。 PostMessageBasic 得到相同的异常。 PostMessage 运行时没有错误,但该消息仍然无法发送到 Twitter。 PostMessage 和 PostMessageBasic 基于我在 SO 上找到的示例。

仅供参考 - 我刚刚发现 @Jason Diller 对 this post 中的回答的评论说 Twitter 将在“下个月”关闭基本身份验证。这早在 2010 年 5 月,现在是 2010 年 12 月,所以我想这可能是这不起作用的原因。

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

这样配置:

告诉 NLog 包含目标的程序集:

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

配置目标:

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

如果有人尝试了这个并取得了成功,请回复您的发现。

【讨论】:

Twitter 使用 OAuth - .NET 在 dotnetopenauth.net 中有一个提供者【参考方案8】:

使用条件布局以不同的布局更轻松地记录每个日志级别

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

语法见https://github.com/NLog/NLog/wiki/When-Filter

【讨论】:

【参考方案9】:

向外部网站/数据库报告

我想要一种方法来简单地自动报告我们的应用程序中的错误(因为用户通常不这样做)。我能想到的最简单的解决方案是一个公共 URL——一个可以接受输入并将其存储到数据库的网页——它在应用程序错误时发送数据。 (然后可以由开发人员或脚本检查数据库以了解是否存在新错误。)

我用 php 编写了网页,并创建了一个 mysql 数据库、用户和表来存储数据。我决定使用四个用户变量、一个 ID 和一个时间戳。可能的变量(包括在 URL 中或作为 POST 数据)是:

app(应用名称) msg(消息 - 例如发生异常......) dev(开发者 - 例如 Pat) src(来源 - 这将来自与运行应用程序的机器相关的变量,例如 Environment.MachineName 或类似的) log(日志文件或详细消息)

(所有变量都是可选的,但如果没有设置任何变量,则不会报告任何内容 - 因此,如果您只是访问网站 URL,则不会将任何内容发送到数据库。)

为了将数据发送到 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 enabled,文件是 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 command)。

【讨论】:

这适用于一个项目,但在其他项目中我遇到了url 的问题:InnerException: System.InvalidCastException Message=Invalid cast from 'System.String' to 'System.Uri'。 Source=mscorlib StackTrace:在 System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider) at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType , IFormatProvider 提供程序) 如果您希望能够监控日志并在发生错误时收到通知,那么另一个选项是 Twitter Target。请参阅此链接以获取为 log4net 编写的 Twitter Appender:twitterappender.codeplex.com 讨论此问题的原始博客帖子在这里:caseywatson.com/2009/07/07/log4net-twitter-awesome 为 NLog 编写类似的内容应该很容易。 我在写 NLog TwitterTarget 时胡闹,但实际上并未成功发布推文。我已经发布了代码作为答案。如果您愿意,请随时尝试。【参考方案10】:

来自 Silverlight 的日志

将 NLog 与 Silverlight 一起使用时,您可以通过 provided Web 服务将跟踪发送到服务器端。您还可以写入独立存储中的本地文件,如果 Web 服务器不可用,它会派上用场。有关详细信息,请参阅here,即使用这样的东西让自己成为目标:

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

【讨论】:

以上是关于最有用的 NLog 配置的主要内容,如果未能解决你的问题,请参考以下文章

NLog日志框架使用探究

NLog写入Mongo日志配置

NLog 忽略配置文件

.Net -- NLog日志框架配置与使用

NLog学习笔记二:深入学习

NLOG配置