最有用的NLogconfiguration
使用NLoglogging最好或最有用的configuration是什么? (这些可以简单或复杂,只要它们有用。)
我想到了一些例子,例如自动滚动日志文件到特定大小,更改布局(日志消息),是否有exception,发生错误时升级日志级别等。
这里有一些链接:
- NLog演示
- 源代码中的示例
其中一些属于一般的NLog(或日志logging)提示的类别,而不是严格的configurationbuild议。
这里有一些通用的日志logging链接(你可能已经看到了一些或所有这些):
log4net与Nlog
logging最佳实践
伐木外观有什么意义?
为什么logging器推荐使用每个类的logging器?
使用基于类Logger logger = LogManager.GetCurrentClassLogger()
命名您的logging器的通用模式。 这为您的logging器提供了高度的粒度,并为logging器的configuration(全局控制,命名空间控制,特定logging器名称等)提供了极大的灵活性。
在适当的地方使用非基于类名的logging器。 也许你有一个function,你真的想单独控制日志logging。 也许你有一些横切的logging问题(性能logging)。
如果您不使用基于类名的日志logging,请考虑以某种分层结构命名logging器(可能按function区域),以便在configuration中保持更大的灵活性。 例如,您可能有一个“数据库”function区,一个“分析”FA和一个“UI”FA。 其中每一个都可能有子区域。 所以,你可能会要求这样的logging器:
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");
等等。 使用分层logging器,可以通过FA(数据库,分析,UI)或分区(Database.Connect等)来全局configuration日志logging(“*”或根logging器)。
logging器有许多configuration选项:
<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的帮助 ,了解更多关于每个选项的含义。 这里最值得注意的可能是通配符logging器规则的能力,多个logging器规则可以“执行”单个日志logging语句的概念,并且logging器规则可以被标记为“最终”,以便后续规则不会执行给出日志logging。
使用GlobalDiagnosticContext,MappedDiagnosticContext和NestedDiagnosticContext为输出添加额外的上下文。
在你的configuration文件中使用“variables”来简化。 例如,您可以为布局定义variables,然后在目标configuration中引用variables,而不是直接指定布局。
<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>
或者,您可以创build一个“自定义”属性集以添加到布局。
<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}"/>
或者,您可以通过configuration严格执行诸如创build“日”或“月”布局渲染器之类的内容:
<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”等。
不要害怕编写自己的布局渲染器。 这很容易,并允许您通过configuration将您自己的上下文信息添加到日志文件。 例如,下面是一个布局渲染器(基于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}"/>
使用asynchronous目标:
<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观察并自动重新加载configuration,如果更改:
<nlog autoReload="true" />
有几个configuration选项可以帮助排除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的硬依赖中隔离你的代码,但是包装正确,不要害怕包装logging器。 有一些如何在NLog的github仓库中打包的例子。 换行的另一个原因可能是你想自动添加特定的上下文信息到每个logging的消息(通过将其放入LogEventInfo.Context中)。
包装(或抽象)NLog(或任何其他日志框架)有利弊。 通过一点努力,你可以在这里find大量的信息,介绍双方。
如果您正在考虑换行,请考虑使用Common.Logging 。 它工作得很好,如果你愿意的话,可以轻松地切换到另一个日志框架。 另外,如果您正在考虑包装,请考虑如何处理上下文对象(GDC,MDC,NDC)。 Common.Logging目前不支持对它们的抽象,但它应该是在要添加的function队列中。
以不同方式处理exception
我们经常希望在发生exception时获得更多信息。 以下configuration有两个目标,一个文件和控制台,过滤是否有任何exception信息。 (编辑: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>
显然,你现在可以使用Nlog与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>
通过XMLconfigurationNLog,但以编程方式
什么? 您是否知道,您可以从应用程序直接指定NLog XML到NLog,而不是让NLog从configuration文件中读取它? 那么,你可以。 假设你有一个分布式应用程序,并且你想在任何地方使用相同的configuration。 你可以在每个位置保留一个configuration文件并分开维护它,你可以在一个中心位置维护一个configuration文件并把它推送到卫星位置,或者你可以做很多其他的事情。 或者,您可以将XML存储在数据库中,在应用程序启动时获取它,并直接使用该XMLconfigurationNLog(可能会定期检查以确定是否已更改)。
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
我不确定这是多么强大,但是这个例子为那些可能想要尝试像这样configuration的人提供了一个有用的起点。
根据是否有错误logging不同的级别
这个例子允许你在代码中有错误的时候获得更多的信息。 基本上,它缓冲消息,只输出那些在一定的日志级别(例如警告), 除非一定条件得到满足(例如,有错误,所以日志级别> =错误),那么它会输出更多的信息(例如来自日志级别> = Trace的所有消息)。 因为消息被缓冲,所以这可以让你收集有关发生Error或ErrorException 之前发生的事情的跟踪信息 – 非常有用!
我从源代码中的一个例子中适应了这个。 我起初被抛出,因为我遗漏了AspNetBufferingWrapper
(因为我的不是一个ASP应用程序) – 事实certificate, PostFilteringWrapper需要一些缓冲目标。 请注意,在上面链接的示例中使用的target-ref
元素不能在NLog 1.0中使用(我正在为.NET 4.0应用程序使用1.0 Refresh); 有必要把你的目标放在包装块内。 还要注意,逻辑语法(即大于或小于符号,<和>)必须使用符号,而不是这些符号的XML转义符(即>
和<
),否则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 – 为日志文件生成标题部分
添加标题:
这个问题想知道如何添加一个头到日志文件。 使用像这样的configuration条目可以让您定义头文件格式与其余日志条目的格式分开。 使用一个单一的logging器,也许叫做“headerlogger”在应用程序的开始处logging一条消息,并得到你的头文件:
定义标题和文件布局:
<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}" />
定义logging器:
<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");
这在很大程度上只是另一个版本的“处理exception不同”的想法。
用不同的布局logging每个日志级别
同样,海报想知道如何更改每个日志logging级别的格式。 我不清楚最终目标是什么(以及是否能以“更好”的方式实现),但是我能够提供一个他所问的configuration:
<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>
再次,非常相似处理例外不同 。
login到Twitter
基于这篇关于log4net Twitter Appender的文章 ,我想我会努力写一个NLog Twitter目标(使用NLog 1.0刷新,而不是2.0)。 唉,到目前为止,我还没有能够得到一个Tweet实际发布成功。 我不知道在我的代码,Twitter,我们公司的互联网连接/防火墙,或者什么是错的。 我在这里发布代码,以防有人有兴趣尝试一下。 请注意,有三种不同的“Post”方法。 我尝试的第一个是PostMessageToTwitter。 PostMessageToTwitter与orignal文章中的PostLoggingEvent基本相同。 如果我使用,我得到一个401例外。 PostMessageBasic获取相同的exception。 PostMessage运行没有任何错误,但是这个消息仍然没有弥补到Twitter。 PostMessage和PostMessageBasic基于我在这里find的例子。
仅供参考 – 我刚才发现@Jason Diller在本文中回答了一个问题,他说Twitter将在下个月closures基本身份validation。 这是在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 (aka 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) + "..."; } } }
像这样configuration它:
告诉NLog包含目标的程序集:
<extensions> <add assembly="NLogExtensions"/> </extensions>
configuration目标:
<targets> <target name="twitter" type="TwitterTarget" TwitterUserName="yourtwittername" TwitterPassword="yourtwitterpassword" layout="${longdate} ${logger} ${level} ${message}" /> </targets>
如果有人试过并取得成功,请发回你的调查结果。
向外部网站/数据库报告
我想要一个方法来自动地报告错误(因为用户通常不会)从我们的应用程序。 我能想到的最简单的解决scheme是一个公共URL–一个网页,可以input并将其存储到数据库中 – 根据应用程序错误发送数据。 (然后可以通过开发人员或脚本检查数据库,以确定是否有新错误。)
我用PHP编写了网页,并创build了一个mysql数据库,用户和表来存储数据。 我决定了四个用户variables,一个ID和一个时间戳。 可能的variables(包含在URL中或作为POST数据)是:
-
app
(应用名称) -
msg
(消息 – 例如发生exception…) -
dev
(开发人员 – 例如Pat) -
src
(来源 – 这将来自与应用程序运行的机器有关的variables,例如Environment.MachineName
或其他) -
log
(日志文件或详细消息)
(所有的variables都是可选的,但是如果没有设置任何variables,那么就不会有任何报告 – 所以如果你只是访问网站的URL没有任何东西被发送到数据库)。
为了将数据发送到URL,我使用了NLog的WebService
目标 。 (注意,起初我有一些这个目标的问题,直到我看到源代码,我发现我的url
不能以/
结尾)。
总而言之,保持外部应用程序的标签并不是一个糟糕的系统。 (当然,礼貌的做法是通知用户 ,你将会报告可能敏感的数据,并给他们一个select进/出的方法。)
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,文件是/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; } ?>
应用程序代码(NLogconfiguration文件)
<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>
Note: there may be some issues with the size of the log file, but I haven't figured out a simple way to truncate it (eg a la *nix's tail
command ).
Easier Way To Log each log level with a different layout using Conditional Layouts
<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}" />
See http://nlog-project.org/wiki/Conditions for syntax
Log from Silverlight
When using NLog with Silverlight you can send the trace to the server side via the provided web service. You can also write to a local file in the Isolated Storage, which come in handy if the web server is unavailable. See here for details, ie use something like this to make yourself a target:
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); } } } } } }