最有用的NLog配置[关闭]


348

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

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

以下是一些链接:


3
以下是一些基于测试的性能调整技巧:deep-depth.blogspot.com/2014/01/…–
Neil

Answers:


391

其中一些属于常规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帮助。此处最值得注意的项目可能是通配符记录器规则的功能,以下概念:多个记录器规则可以针对单个记录语句“执行”,并且记录器规则可以标记为“最终”,因此后续规则将不会对一个记录执行。给定日志记录语句。

使用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(或与此有关的任何其他日志记录框架)有其优缺点。稍加努力,您就可以在此处找到很多信息,以便对双方进行介绍。

如果要考虑包装,请考虑使用Common.Logging。它工作得很好,如果需要,您可以轻松切换到另一个日志框架。另外,如果您正在考虑包装,请考虑如何处理上下文对象(GDC,MDC,NDC)。Common.Logging当前不支持它们的抽象,但是应该将其添加到队列中。


3
好答案。只需添加一件事,$ {machine}应该是$ {machinename}。见github.com/nlog/NLog/wiki/Layout-Renderers
liang

2
我分叉了Common.Logging并添加了缺少的抽象,请参阅GitHub projectNuGet
Danny Varod 2014年

我没有在他们自己的文档中找到任何有关nlog的信息,也许我在以错误的方式浏览github示例?谁知道。
JARRRRG 2014年

如何通过API使用该自定义渲染器(无配置文件)?是我要完成的工作。
InteXX '17

好的,我知道了。在NewLine布局完成的任务。是我想出的。它肯定比我期望的要简单得多。
InteXX '17

65

区别对待异常

发生异常时,我们通常希望获得更多信息。以下配置有两个目标,即文件和控制台,它们根据是否存在任何异常信息进行过滤。(编辑: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}布局渲染器的人的问题,但他不想获取()没有异常的情况下显然已记录的()。这项技术可能对他很有效。
10年

+1非常好。我已经为此添加了书签很长时间,并且从另一个关于条件布局的SO问题中引用了“帕特的评论”。
eduncan911

1
如果记录了异常,则会记录两次(VerboseLayout部分)。
天前

2
我明天才在我的项目中尝试过,因为您将规则minlevel =“ Warn”设置为“ file,fileAsException”,所有日志将首先使用文件目标(无过滤器)记录,如果是例外(通过条件),也将使用fileAsException进行记录。
Tien Do

3
@Tiendq哦,我知道了。这是有道理的,尽管异常本身(详细信息)将仅记录一次(但其消息将记录两次)。您可以通过在中添加condition="length('${exception}')=0(或可能是==)来解决此问题target name="file"
2012年

60

显然,您现在可以将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与Windows低吼 Windows的Growl的NLog跟踪消息 Windows版Growl的NLog调试消息 Windows的Growl的NLog信息消息 Windows的Growl的NLog警告消息 Windows版Growl的NLog错误消息 Windows版Growl的NLog致命消息


您能告诉我如何进行固定连接吗?东西对我来说适用于本地主机,但是当我在主机中提供了一些IP地址时,它不起作用!!
Neel 2013年

@Neel,您应该在目标计算机上的Growl中检查“安全性”设置。您必须显式启用“ LAN”通知,并且可能要设置一个密码(然后将其添加到NLog目标中)。但是我不喜欢在远程通知中以“本地计算机”的“来源”出现在Growl中。我必须将主机添加到日志条目中,才能知道通知的来源。
肯尼·埃维特

我可以使通知在我的本地计算机上运行,​​但不能在远程运行。我的安全设置没有咆哮的密码,所以我添加的只是IP和端口。但是什么也没发送。
杰克·赖利

1
这个项目死了100%
开发者

28

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

我不确定它的鲁棒性如何,但是此示例为可能想要尝试这样配置的人们提供了一个有用的起点。


它工作得很好...除了使用它之外,不再可以动态地重新配置日志记录系统。如果您链接到外部文件(包括),则尤其如此
Newtopian 2013年

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'>
确实可行

1
这是进入集中式配置的不错的选择。将来的读者,此示例中的硬编码xml仅用于演示(IMHO),从数据库或集中式文件中读取它可能是真正的实现。
granadaCoder

@wageoghe; 为什么会出现错误(记录器不存在)?我只是复制并粘贴代码
Bsflasher

22

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

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

我从源代码示例中改编了此代码。起初我被抛弃是因为我遗漏了AspNetBufferingWrapper(因为我的不是ASP应用程序)-事实证明PostFilteringWrapper需要一些缓冲的目标。请注意,target-ref以上链接的示例中使用的元素不能在NLog 1.0中使用(我对.NET 4.0应用程序使用1.0 Refresh);有必要将您的目标放在包装程序块中。还要注意,逻辑语法(即大于和小于符号,<和>)必须使用符号,而不是这些符号(即&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刷新)。
帕特

关于溢出-似乎仅是因为CSV类型的布局-如果我进行常规布局没有问题。
帕特

那里的fileAsCsv target-ref是什么?我正在尝试使此示例可与NLog v2.0.0.2000一起使用,但到目前为止仍失败。
彼得·蒙斯

@PeterMounce fileAsCsvtarget-ref只是我测试中的产物。我相信NLog 2的CsvLayouts有/有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

10

登录Twitter

基于有关log4net Twitter Appender的帖子,我认为我会尝试编写NLog Twitter Target(使用NLog 1.0刷新,而不是2.0)。las,到目前为止,我还没有获得一条Tweet才能成功发布。我不知道我的代码,Twitter,我们公司的Internet连接/防火墙或什么地方有问题。我在这里发布代码,以防有人有兴趣尝试。请注意,有三种不同的“发布”方法。我尝试的第一个是PostMessageToTwitter。PostMessageToTwitter本质上与原始帖子中的PostLoggingEvent相同。如果我使用它,则会得到401异常。PostMessageBasic获取相同的异常。PostMessage可以正常运行,但该消息仍不能由Twitter来弥补。PostMessage和PostMessageBasic基于我在SO上找到的示例。

仅供参考 -我现在发现@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
Pat

7

向外部网站/数据库报告

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

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

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

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

要将数据发送到URL,我使用了NLog的WebServicetarget。(请注意,我最初在此目标上遇到了一些问题。直到查看了源代码后,我才发现我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'

网站代码

启用PDO的 PHP 5.3或5.2 ,文件位于文件夹中)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命令)。


这适用于一个项目,但是在其他项目中,我遇到了以下问题url:InnerException:System.InvalidCastException Message =从'System.String'到'System.Uri'的无效转换。Source = mscorlib StackTrace:位于System.String.System.IConvertible.ToType(类型类型,IFormatProvider提供者)位于System.Convert.DefaultToType(IConvertible值,类型targetType,IFormatProvider提供程序),位于System.Convert.ChangeType(对象值,类型conversionType) ,IFormatProvider提供程序)
Pat

如果您希望能够监视日志并在发生错误时得到通知,则另一个选择是Twitter Target。请参阅此链接为写一个Twitter的Appender log4net的: twitterappender.codeplex.com 原来的博客中讨论这个是在这里: caseywatson.com/2009/07/07/log4net-twitter-awesome 应该是很容易类似写点东西NLog。
10年

我自欺欺人地编写了NLog TwitterTarget,但实际上并未成功发布Tweet。我已将代码发布为答案。如果您愿意,可以尝试一下。
10年

6

使用条件布局以不同的布局记录每个日志级别的更简便方法

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


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);
                    }
                }
            }
        }
    } 
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.