使用NLog进行日志记录的最佳或最有用的配置是什么?(只要它们有用,它们可以是简单的也可以是复杂的.)
我正在考虑一些示例,例如自动滚动特定大小的日志文件,更改布局(日志消息)是否存在异常,一旦发生错误就升级日志级别等.
以下是一些链接:
NLog演示
源中的示例
wageoghe.. 389
其中一些属于一般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等)全局配置日志记录("*"或根记录器).
记录器有许多配置选项:
有关每个选项的确切含义的详细信息,请参阅NLog帮助.这里最值得注意的项目可能是通配符记录器规则的能力,多个记录器规则可以为单个记录语句"执行"的概念,以及记录器规则可以标记为"最终",因此后续规则不会执行给出记录声明.
使用GlobalDiagnosticContext,MappedDiagnosticContext和NestedDiagnosticContext为输出添加其他上下文.
在配置文件中使用"variable"来简化.例如,您可以为布局定义变量,然后在目标配置中引用变量,而不是直接指定布局.
或者,您可以创建一组"自定义"属性以添加到布局.
或者,您可以通过配置严格创建"日"或"月"布局渲染器等内容:
您还可以使用布局渲染来定义文件名:
如果您每天滚动文件,则每个文件可以命名为"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扩展(什么程序集)在哪里:
使用自定义布局渲染器,如下所示:
使用异步目标:
默认目标包装器:
在适当情况下.有关这些文档的更多信息,请参阅NLog文档.
如果配置发生变化,请告诉NLog监视并自动重新加载配置:
有几个配置选项可帮助您排除NLog故障
有关详细信息,请参阅NLog帮助.
NLog 2.0添加了LayoutRenderer包装器,允许在布局渲染器的输出上执行其他处理(例如修剪空格,大写,小写等).
如果要将代码与NLog的硬依赖性隔离,请正确包装,不要害怕包装记录器.有一些如何包装NLog的github存储库的例子.换行的另一个原因可能是您希望自动为每条记录的消息添加特定的上下文信息(通过将其放入LogEventInfo.Context).
包装(或抽象)NLog(或任何其他日志框架)的优点和缺点.只需稍加努力,您就可以在这里找到关于SO双方的大量信息.
如果您正在考虑包装,请考虑使用Common.Logging.它运行良好,并允许您轻松切换到另一个日志框架,如果您希望这样做.此外,如果您正在考虑包装,请考虑如何处理上下文对象(GDC,MDC,NDC).Common.Logging目前不支持对它们进行抽象,但它应该在要添加的功能队列中.
其中一些属于一般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等)全局配置日志记录("*"或根记录器).
记录器有许多配置选项:
有关每个选项的确切含义的详细信息,请参阅NLog帮助.这里最值得注意的项目可能是通配符记录器规则的能力,多个记录器规则可以为单个记录语句"执行"的概念,以及记录器规则可以标记为"最终",因此后续规则不会执行给出记录声明.
使用GlobalDiagnosticContext,MappedDiagnosticContext和NestedDiagnosticContext为输出添加其他上下文.
在配置文件中使用"variable"来简化.例如,您可以为布局定义变量,然后在目标配置中引用变量,而不是直接指定布局.
或者,您可以创建一组"自定义"属性以添加到布局.
或者,您可以通过配置严格创建"日"或"月"布局渲染器等内容:
您还可以使用布局渲染来定义文件名:
如果您每天滚动文件,则每个文件可以命名为"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扩展(什么程序集)在哪里:
使用自定义布局渲染器,如下所示:
使用异步目标:
默认目标包装器:
在适当情况下.有关这些文档的更多信息,请参阅NLog文档.
如果配置发生变化,请告诉NLog监视并自动重新加载配置:
有几个配置选项可帮助您排除NLog故障
有关详细信息,请参阅NLog帮助.
NLog 2.0添加了LayoutRenderer包装器,允许在布局渲染器的输出上执行其他处理(例如修剪空格,大写,小写等).
如果要将代码与NLog的硬依赖性隔离,请正确包装,不要害怕包装记录器.有一些如何包装NLog的github存储库的例子.换行的另一个原因可能是您希望自动为每条记录的消息添加特定的上下文信息(通过将其放入LogEventInfo.Context).
包装(或抽象)NLog(或任何其他日志框架)的优点和缺点.只需稍加努力,您就可以在这里找到关于SO双方的大量信息.
如果您正在考虑包装,请考虑使用Common.Logging.它运行良好,并允许您轻松切换到另一个日志框架,如果您希望这样做.此外,如果您正在考虑包装,请考虑如何处理上下文对象(GDC,MDC,NDC).Common.Logging目前不支持对它们进行抽象,但它应该在要添加的功能队列中.
我们经常希望在出现异常时获取更多信息.以下配置有两个目标,即文件和控制台,它们会过滤是否存在任何异常信息.(编辑:Jarek发布了一个在vNext中执行此操作的新方法.)
关键是要有一个包装目标 xsi:type="FilteringWrapper" condition="length('${exception}')>0"
显然,您现在可以将NLog与Growl for Windows一起使用.
通过XML配置NLog,但是以编程方式配置
什么?您是否知道可以从应用程序直接向NLog指定NLog XML,而不是让NLog从配置文件中读取它?好吧,你可以.假设您有一个分布式应用程序,并且您希望在任何地方使用相同的配置.您可以在每个位置保留一个配置文件并单独维护,您可以将其保存在一个中心位置并将其推送到卫星位置,或者您可能可以执行许多其他操作.或者,您可以将XML存储在数据库中,在应用程序启动时获取它,并使用该XML直接配置NLog(可能会定期检查以查看它是否已更改).
string xml = @""; 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 = @" "; 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
我不确定这是多么强大,但是这个示例为可能想要尝试这样配置的人提供了一个有用的起点.
此示例允许您在代码中出现错误时获取更多信息.基本上,它缓冲消息并仅输出特定日志级别的消息(例如警告),除非满足某个条件(例如,出现错误,因此日志级别> =错误),然后它将输出更多信息(例如来自日志级别的所有消息> =跟踪).因为消息是缓冲的,所以这使您可以收集有关在记录Error或ErrorException 之前发生的事情的跟踪信息- 非常有用!
我从源代码中的一个例子中改编了这个.我最初被抛出是因为我遗漏了AspNetBufferingWrapper
(因为我的不是ASP应用程序) - 事实证明PostFilteringWrapper需要一些缓冲目标.请注意,target-ref
上面链接的示例中使用的元素不能在NLog 1.0中使用(我在.NET 4.0应用程序中使用1.0 Refresh); 有必要将目标放在包装块中.还要注意逻辑语法(即大于或小于符号,<和>)必须使用符号,而不是那些符号的XML转义(即>
和<
),否则NLog将出错.
的app.config:
level >= LogLevel.Warn
我为这个问题提供了几个相当有趣的答案:
Nlog - 为日志文件生成标题部分
添加标题:
这个问题想知道如何在日志文件中添加标题.使用这样的配置条目允许您与其余日志条目的格式分开定义标头格式.使用单个记录器(可能称为"headerlogger")在应用程序开始时记录单个消息,并获得标题:
定义标题和文件布局:
使用布局定义目标:
定义记录器:
可能在程序的早期写下标题:
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");
这主要是"处理异常异常"理念的另一个版本.
使用不同的布局记录每个日志级别
同样,海报想知道如何更改每个日志记录级别的格式.我不清楚最终目标是什么(以及是否可以以"更好"的方式实现),但我能够提供他所要求的配置:
同样,非常类似于以不同方式处理异常.
登录Twitter
基于这篇关于log4net Twitter Appender的帖子,我以为我会尝试编写NLog Twitter目标(使用NLog 1.0刷新,而不是2.0).唉,到目前为止,我还没有能够成功发布推文.我不知道我的代码,Twitter,我们公司的互联网连接/防火墙或者什么是错误的.我在这里发布代码,以防有人有兴趣尝试它.请注意,有三种不同的"后"方法.我尝试的第一个是PostMessageToTwitter.PostMessageToTwitter与orignal帖子中的PostLoggingEvent基本相同.如果我使用它,我会得到401异常.PostMessageBasic获得相同的异常.PostMessage运行时没有任何错误,但该消息仍然无法通过Twitter.PostMessage和PostMessageBasic基于我在SO上找到的示例.
仅供参考 -我刚才发现@Jason迪勒评论在回答这个帖子,指出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包含目标的程序集:
配置目标:
如果有人试图这样做并取得成功,请回复你的发现.
我想从我们的应用程序中简单地自动报告错误(因为用户通常不会).我能想到的最简单的解决方案是一个公共URL - 一个可以接收输入并将其存储到数据库的网页 - 在发生应用程序错误时发送数据.(然后可以通过开发人员或脚本检查数据库,以了解是否存在新错误.)
我用PHP编写了网页,并创建了一个mysql数据库,用户和表来存储数据.我决定使用四个用户变量,一个id和一个时间戳.可能的变量(包含在URL中或作为POST数据)是:
app
(应用名称)
msg
(消息 - 例如发生异常......)
dev
(开发人员 - 例如Pat)
src
(来源 - 这将来自与应用程序运行的机器相关的变量,例如Environment.MachineName
或某些变量)
log
(日志文件或详细消息)
(所有变量都是可选的,但如果没有设置任何变量,则不报告任何变量 - 因此,如果您只访问网站URL,则不会向数据库发送任何内容.)
为了将数据发送到URL,我使用了NLog的WebService
目标.(注意,我最初对这个目标有一些问题.直到我查看源代码,我才知道我url
不能以a结束/
.)
总而言之,对于密切关注外部应用程序来说,这不是一个糟糕的系统.(当然,礼貌的做法是告知您的用户您将报告可能的敏感数据并为他们提供选择加入/退出的方法.)
(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
$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() . "
"; die("PDO error"); } function isEmpty($array = array()) { foreach ($array as $element) { if (!empty($element)) { return false; } } return true; } ?>
level >= LogLevel.Warn
注意:日志文件的大小可能存在一些问题,但我还没有找到一种截断它的简单方法(例如la*nix的tail
命令).
使用条件布局以不同的布局记录每个日志级别的更简便方法
参见https://github.com/NLog/NLog/wiki/When-Filter了解语法