最有用的NLog配置

362

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

例如,自动在特定大小时滚动日志文件,更改布局(日志消息)是否存在异常,一旦发生错误就升级日志级别等。

以下是一些链接:


3
以下是基于测试的性能调优技巧:http://deep-depth.blogspot.com/2014/01/net-low-latency-logging-part-2-nlog.html - Neil
10个回答

409

这些内容有些是通用的NLog(或日志记录)技巧,而不是严格的配置建议。

以下是一些来自SO的通用日志记录链接(您可能已经看到了其中一些或全部):

log4net与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(数据库、分析、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帮助页面以获取有关每个选项的详细信息。这里最显著的项目可能是通配符记录器规则的能力,多个记录器规则可以为单个记录语句“执行”,并且可以将记录器规则标记为“final”,以便后续规则不会为给定的记录语句执行。

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

或者,您可以像通过配置严格创建“day”或“month”布局渲染器一样做一些事情:

  <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(或任何其他日志框架)有利有弊。花点功夫,在这里可以找到足够的信息,介绍两种方法的优缺点。

如果您正在考虑包装,请考虑使用Common.Logging。它工作得相当不错,并允许您轻松地切换到另一个日志框架(如果您希望这样做)。此外,如果您正在考虑包装,请考虑如何处理上下文对象(GDC、MDC、NDC)。Common.Logging目前不支持它们的抽象,但据说将在能力队列中添加。


3
很好的回答。只有一件事要补充,${machine} 应该改为 ${machinename}。请参考 https://github.com/nlog/NLog/wiki/Layout-Renderers。 - liang
2
我分叉了Common.Logging并添加了缺失的抽象,详见GitHub项目NuGet - Danny Varod
我在nlog的官方文档中没有找到任何有用的信息,也许我没有正确地查看github上的示例?谁知道呢。 - JARRRRG
如何在API中使用自定义渲染器(无需配置文件)?这是我想要实现的内容,这里有更多信息。 - InteXX
好的,明白了。NewLine布局可以完成任务。这是我想出来的链接。它比我预期的要简单得多。 - InteXX

68

异常处理不同的方法

当出现异常时,我们通常希望获得更多信息。以下配置有两个目标:一个是文件,一个是控制台,它们会根据是否有异常信息进行过滤。(编辑: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>

1
这个分离的目标和FilteringWrapper格式化异常的方法非常酷。最近我回答了一个问题,有个人想在输出中包含{exception}布局渲染器,但是如果没有异常,他不想得到似乎被记录的()。这种技术对他来说可能很有效。 - wageoghe
1
如果记录了异常,它将被记录两次(VerboseLayout 部分)。 - Tien Do
2
我在我的项目中刚试了一下,因为你设置了规则minlevel="Warn"为"file, fileAsException",所有的日志都会首先通过文件目标(无过滤)进行记录,如果它是一个异常(根据条件筛选),它也将用fileAsException记录。 - Tien Do
3
@Tiendq 哦,我明白了。这很有道理,虽然异常(完整细节)本身只会记录一次(但其消息将记录两次)。您可能可以通过在“target name =” file“中添加condition ="length('${exception}')= 0(或者也许是 ==)来解决这个问题。 - Pat
@Pat,作为一位显然精通 nlog 的大师,您是否可以看一下这篇来自另一个用户的帖子——这是一个合理的问题——但似乎被忽略了:https://dev59.com/53PYa4cB1Zd3GeqPkY5O - Ricibob
显示剩余2条评论

62

显然,你现在可以使用NLog和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>

NLog与Growl for Windows 使用Growl for Windows的NLog追踪消息 使用Growl for Windows的NLog调试消息 使用Growl for Windows的NLog信息消息 使用Growl for Windows的NLog警告消息 使用Growl for Windows的NLog错误消息 使用Growl for Windows的NLog致命消息


你能告诉我如何进行远程连接吗?这个东西在本地主机上运行得很好,但是当我在主机中输入了某个 IP 地址时,它就不能工作了! - Neel
@Neel,你应该检查目标计算机上Growl的“安全”设置。您必须显式启用“LAN”通知,并且您可能需要设置密码(然后需要将其添加到NLog目标)。但我不喜欢远程通知在Growl中显示为“本地机器”的来源;我必须将主机添加到日志条目中以了解通知的来源。 - Kenny Evitt
我可以在本地计算机上使通知工作,但在远程计算机上不行。我的安全设置中growl没有密码,所以我只添加了IP和端口。但是什么都没有发送。 - Jack Reilly
2
这个项目已经彻底死亡了。 - NoWar

28

通过XML进行NLog配置,但以编程方式实现

什么?您是否知道您可以直接从应用程序向NLog指定NLog XML,而不是让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

我不确定这个方法的可靠性,但是这个例子为那些想尝试这种配置的人提供了一个有用的起点。


它运作得非常好……除了使用这个方法之外,动态重新配置日志系统就不再可能了。尤其是当你链接到一个外部文件(包括)时。 - Newtopian
2
这个方法可行,尽管我必须编写“好”的XML,包括:<?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
这是一个很好的引入集中式配置的方式。未来的读者,此示例中的硬编码 XML 仅用于演示(在我看来),从数据库或集中式文件中读取可能是真正的实现。 - granadaCoder
@wageoghe;为什么我会收到错误信息(logger不存在)?我只是复制粘贴了代码。 - Bsflasher

23

根据是否存在错误记录不同级别的日志

该示例允许您在代码中出现错误时获取更多信息。基本上,它会缓冲消息并仅在特定日志级别(例如Warn)下输出这些消息,除非满足某些条件(例如发生错误,因此日志级别>= Error),那么它将输出更多信息(例如来自日志级别>= Trace的所有消息)。由于消息被缓冲,这使您能够收集有关在记录Error或ErrorException之前发生了什么的跟踪信息-非常有用!

我从源代码示例中调整了这个示例。一开始我忘了加入AspNetBufferingWrapper(因为我的应用程序不是ASP应用程序),但结果发现PostFilteringWrapper需要某个被缓冲的目标。请注意,在上面链接的示例中使用的target-ref元素无法在NLog 1.0中使用(我正在使用1.0 Refresh为.NET 4.0应用程序); 必须将您的目标放置在包装器块内部。还要注意逻辑语法(即大于或小于符号,<和>)必须使用符号,而不是这些符号的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>

在某些版本的NLog中(针对mono和我认为的2.0),这会导致StackOverflowException,但在其他版本中不会出现此问题(NLog 1刷新)。 - Pat
关于溢出 - 这似乎只是由于布局为CSV类型 - 如果我使用常规布局就没有问题。 - Pat
文件的 fileAsCsv 目标引用是用来做什么的?我正在尝试让这个示例在 NLog v2.0.0.2000 上运行,但目前还没有成功。 - Peter Mounce
@PeterMounce,“fileAsCsv”被引用仅仅是我测试的一个产物。我相信NLog 2存在/曾经存在CsvLayout的问题,而NLog 1/Refresh则没有。 - Pat

22
我提供了几个相当有趣的答案来回答这个问题:
生成日志文件的头部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> 

处理异常的方式非常相似。


1
太酷了!我以前从未见过 GlobalDiagnosticsContext - Pat

10

推文日志

基于这篇有关log4net Twitter Appender的文章,我想尝试写一个NLog Twitter Target(使用NLog 1.0 refresh,而不是2.0)。但是,到目前为止,我还没有能够成功地发表推文。我不知道是我的代码有问题,还是Twitter、我们公司的互联网连接/防火墙有问题,或者其他原因。如果有人感兴趣尝试它,我在此发布代码。请注意,有三种不同的“Post”方法。我尝试的第一个是PostMessageToTwitter。PostMessageToTwitter本质上与原始帖子中的PostLoggingEvent相同。如果我使用它,我会得到401异常。PostMessageBasic也会得到相同的异常。PostMessage运行时没有错误,但消息仍然无法传达到Twitter。PostMessage和PostMessageBasic是基于我在这里找到的示例。

FYI - 我刚刚在这篇文章的答案评论中找到了@Jason Diller的评论,他说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在http://www.dotnetopenauth.net/上有一个提供程序。 - Pat

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了解语法。


7

向外部网站/数据库报告

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

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

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

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

要将数据发送到URL,我使用了NLog的WebService目标。(注意,我最初在这个目标上遇到了一些问题。直到我查看了源代码才发现我的url不能以/结尾。)

总的来说,这不是一个坏系统,用于跟踪外部应用程序。(当然,礼貌的做法是通知用户,您将报告可能敏感的数据,并为他们提供选择加入/退出的方式。)

MySQL相关

(数据库用户仅具有其自己的数据库中此一个表的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 已启用,文件位于/report文件夹中的index.php

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

注意:日志文件的大小可能会出现一些问题,但我还没有找到一个简单的截断方法(例如类Unix的tail命令)。

这在一个项目中可行,但在其他项目中我遇到了url的问题:InnerException: System.InvalidCastException Message=从'System.String'到'System.Uri'的转换无效。 Source=mscorlib StackTrace: at 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 provider) - Pat
如果您想监视日志并在发生错误时得到通知,另一个选择是使用 Twitter Target。请参阅此链接以获取针对log4net编写的Twitter Appender:http://twitterappender.codeplex.com/,讨论此主题的原始博客文章在这里:http://caseywatson.com/2009/07/07/log4net-twitter-awesome/。为NLog编写类似的东西应该非常容易。 - wageoghe
我曾经尝试编写一个NLog TwitterTarget,但实际上并没有成功发布推文。我已将代码发布为答案。如果您愿意,请随意尝试。 - wageoghe

4

来自Silverlight的日志

当使用NLog与Silverlight时,您可以通过提供的 Web服务将跟踪信息发送到服务器端。如果Web服务器不可用,则还可以写入本地文件存储在隔离存储中。有关详细信息,请参见此处,例如使用以下内容创建目标:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接