日志记录最佳实践

我想了解人们如何在实际应用程序中处理跟踪和登录。这里有一些问题可能有助于解释你的答案。

框架

你使用什么框架?

  • log4net
  • System.Diagnostics.Trace
  • System.Diagnostics.TraceSource
  • 日志应用程序块
  • 其他的吗?

如果你使用跟踪,你使用Trace.Correlation.StartLogicalOperation吗?

您是手动编写这些代码,还是使用某种形式的面向方面编程来完成这些代码?愿意分享一段代码片段吗?

您是否提供了任何形式的跟踪源粒度?例如,WPF TraceSources允许你在不同的级别上配置它们:

  • 系统。Windows - 所有WPF的设置
  • System.Windows.Animation - 重写动画。

听众

您使用什么日志输出?

  • 文本文件
  • XML文件
  • 事件日志
  • 其他的吗?

如果使用文件,您是使用滚动日志还是单个文件?如何让人们可以使用这些日志?

查看

您使用什么工具查看日志?

  • 记事本
  • 尾巴
  • 事件查看器
  • 系统中心运营经理/微软运营经理
  • WCF服务跟踪查看器
  • 其他的吗?

如果您正在构建ASP。NET解决方案,你也用ASP。NET运行状况监视?运行状况监控器事件中是否包含跟踪输出?Trace.axd呢?

那么自定义性能计数器呢?

175050 次浏览

我没有资格评论。net的日志,因为我的面包和butter是Java,但是在过去的8年里,我们在日志方面有了一个迁移,你可能会发现一个有用的类比来回答你的问题。

我们从JVM中的每个线程都使用的Singleton记录器开始,并为整个进程设置日志记录级别。如果我们不得不调试系统的一个非常特定的部分,就会产生大量的日志,所以第一个教训是对日志进行分段。

当前的日志记录器允许多个实例,其中一个被定义为默认实例。我们可以实例化任意数量的具有不同日志级别的子日志记录器,但是这种体系结构最有用的方面是能够通过简单地更改日志属性为单个包和类创建日志记录器。第二课是创建一个灵活的系统,允许在不更改代码的情况下重写其行为。

我们使用的是包裹在Log4J周围的Apache公共日志库。

希望这能有所帮助!

*编辑*

在阅读了Jeffrey Hantin下面的文章之后,我意识到我应该注意到我们的内部日志包装实际上变成了什么样子。它现在本质上是一个工厂,严格用于使用正确的属性文件(由于遗留原因,没有移动到默认位置)获得工作日志记录器。由于您现在可以在命令行上指定日志记录配置文件,我认为它将变得更加精简,如果您正在启动一个新的应用程序,我绝对同意他的说法,即您甚至不应该费心包装日志记录器。

我们在工作中使用Log4Net作为日志记录提供者,并为日志实例使用单例包装器(尽管单例正在审查中,质疑它们是否是一个好主意)。

我们选择它的原因如下:

  • 在各种环境上进行简单的配置/重新配置
  • 良好的预建附件数量
  • 我们用的一个CMS已经内置了
  • 大量的日志级别和相关配置

我应该提一下,这是从ASP讲的。NET开发的观点

我可以看到使用. net框架中的Trace的一些优点,但我并不完全相信它,主要是因为我使用的组件并没有真正执行任何Trace调用。我唯一经常使用的是System.Net.Mail,从我可以告诉。

所以我们有一个包装log4net的库,在我们的代码中,我们只需要这样的东西:

Logger.Instance.Warn("Something to warn about");
Logger.Instance.Fatal("Something went bad!", new Exception());


try {
var i = int.Parse("Hello World");
} catch(FormatException, ex) {
Logger.Instance.Error(ex);
}

在方法中,我们检查日志记录级别是否启用,这样就不会对log4net API进行冗余调用(因此,如果Debug未启用,调试语句将被忽略),但当我有时间时,我将更新它以公开这些语句,以便您可以自己进行检查。这将防止在不应该进行的情况下进行评估,例如:

Logger.Instance.Debug(string.Format("Something to debug at {0}", DateTime.Now);

这将变成:

if(Logger.DebugEnabled) Logger.Instance.Debug(string.Format("Something to debug at {0}", DateTime.Now);

(节省了一点浪费时间)

默认情况下,我们在两个位置进行日志记录:

  1. 网站的文件系统(未提供的文件扩展名)
  2. 电子邮件发送错误致命的

文件以每天卷或10mb (IIRC)的方式完成。我们不使用EventLog,因为它需要比我们通常想要的站点更高的安全性。

我发现记事本可以很好地读取日志。

我们在web应用程序中使用log4net。

当应用程序在运行时出现故障,需要查看更多信息时,它可以通过更改XML配置文件在运行时自定义日志记录。

它还允许您锁定要登录的特定类或属性。当您知道错误发生在哪里时,这是非常方便的。一个经典的例子是NHibernate,你想看到的只是SQL进入数据库。

编辑:

我们将所有事件写入数据库和Trace系统。我们用于错误或异常的事件日志。我们将大多数事件记录到数据库中,这样我们就可以创建自定义报告,如果用户想从应用程序中查看日志的话。

我不得不加入推荐log4net的行列,在我的案例中,从平台灵活性(desktop . net / compact Framework, 32/64位)的角度来看。

然而,将它包装在私有标签API中是主要的反模式log4net.ILogger已经是通用日志包装器API的. net对等体,所以耦合已经为你最小化了,而且由于它也是一个Apache库,这通常甚至都不是一个问题,因为你没有放弃任何控制:如果你必须要fork它。

我所见过的大多数家用包装器库也会犯一个或多个错误:

  1. 使用全局单例记录器(或等价于静态入口点),由于没有其他选择性增益而失去了推荐的logger-per-class模式的精细分辨率。
  2. 未能公开可选的Exception参数,导致多个问题:
    • 这使得异常日志记录策略更难维护,因此在异常情况下什么都不会做。
    • 即使使用一致的策略,将异常格式化为字符串也会过早地丢失数据。我已经编写了一个自定义ILayout装饰器,它对异常执行详细的向下钻取,以确定事件链。
    • 李< / ul > < / >
    • 未能公开IsLevelEnabled属性,当日志记录的区域或级别被关闭时,它放弃了跳过格式化代码的能力。

我不经常在asp.net中开发,但是当涉及到记录器时,我认为许多最佳实践是通用的。以下是我多年来对日志的一些随机想法:

框架

  • 使用记录器抽象框架-如slf4j(或滚动您自己的),这样您就可以将记录器实现与API分离。我见过许多日志记录器框架的出现和消失,您最好能够采用一个新的框架,而不需要太多的麻烦。
  • 尝试找到一个支持多种输出格式的框架。
  • 尝试找到一个支持插件/自定义过滤器的框架。
  • 使用可以由外部文件配置的框架,这样您的客户/消费者就可以轻松地调整日志输出,以便商业日志管理应用程序可以轻松地读取它。
  • 请确保不要在自定义日志级别上走极端,否则您可能无法迁移到不同的日志框架。

日志输出

  • 尽量避免XML/RSS样式的日志,因为这些日志可能会遇到灾难性的失败。这很重要,因为如果电源开关在记录器没有写入关闭</xxx>标记的情况下被关闭,那么日志就坏了。
  • 日志线程。否则,很难跟踪程序的流程。
  • 如果您必须国际化您的日志,您可能需要一个只使用英语(或您选择的语言)的开发人员日志。
  • 有时,在调试情况下,将日志语句插入SQL查询的选项可以成为救星。如:
    -- Invoking Class: com.foocorp.foopackage.FooClass:9021
SELECT * FROM foo;
  • 您需要类级日志记录。你通常也不需要记录器的静态实例——它不值得进行微观优化。
  • 标记和分类记录的异常有时是有用的,因为并非所有的异常创建都是相同的。因此,如果您有一个日志监视器,需要在关键状态时发送通知,那么提前了解重要异常的子集是很有帮助的。
  • 复制过滤器将节省你的视力和硬盘。你真的想看到相同的日志语句重复10^10000000次吗?如果能收到这样的信息不是更好吗 李This is my logging statement - Repeated 100 times < / >

也可参见我的这个问题

你使用什么框架?

我们混合使用了日志应用程序块和一个自定义日志助手,它围绕。net框架工作。LAB被配置为输出相当广泛的日志文件,包括用于服务方法进入/退出的单独通用跟踪文件和用于意外问题的特定错误文件。配置包括日期/时间、线程、pId等,用于调试辅助,以及完整的异常细节和堆栈(在意外异常的情况下)。

自定义日志助手使用Trace。相关性和在WF的日志记录上下文中特别方便。例如,我们有一个调用一系列顺序工作流的状态机。在每个调用活动中,我们记录开始(使用StartLogicalOperation),然后在结束时,使用gereric返回事件处理程序停止逻辑操作。

当尝试调试复杂业务序列中的故障时,这已经被证明是有用的,因为它允许我们根据活动执行序列更快地确定If/Else分支决策等。

您使用什么日志输出?

我们使用文本文件和XML文件。文本文件是通过应用程序块配置的,但我们也从WF服务获得了XML输出。这使我们能够捕获运行时事件(持久性等)以及通用业务类型异常。文本文件是按日期和大小滚动的日志(我认为1MB的总大小是滚动点)。

您使用什么工具查看日志?

我们正在使用记事本和WCF服务跟踪查看器,这取决于我们正在查看的输出组。如果你的输出设置正确,WCF Service Trace Viewer是非常非常方便的,它可以使读取输出变得更简单。也就是说,如果我大致知道错误在哪里-只是阅读一个带注释的文本文件也很好。

日志被发送到单个目录,然后根据源服务将该目录划分为子目录。根目录是通过一个网站公开的,该网站的访问由一个支持用户组控制。这使我们可以查看生产日志,而不必提出请求,并通过冗长的繁文缛节流程获取生产数据。

作为该工具的作者,我们当然使用SmartInspect来记录和跟踪。net应用程序。我们通常对实时日志使用命名管道协议,对最终用户日志使用(加密的)二进制日志文件。我们使用SmartInspect控制台作为查看器和监视工具。

实际上有相当多的。net日志框架和工具。下面是DotNetLogging.com中不同工具的概述和比较。


更新:用于系统扩展。诊断,提供您可能需要的一些缺失的侦听器,请参阅基本。CodePlex诊断(http://essentialdiagnostics.codeplex.com/)


< em > < / em >的框架

问:你使用什么框架?

System.Diagnostics。TraceSource,内置于。net 2.0中。

它为应用程序提供了强大、灵活、高性能的日志记录,但是许多开发人员没有意识到它的功能,也没有充分利用它们。

在某些领域,额外的功能是有用的,或者有时功能存在但没有很好地记录,但这并不意味着整个日志框架(被设计为可扩展的)应该被抛弃,并像一些流行的替代品(NLog, log4net, Common)一样被完全取代。日志,甚至是EntLib日志)。

而不是改变向应用程序添加日志语句的方式,重新发明轮子,只是扩展了系统。诊断框架在少数地方你需要它。

在我看来,其他的框架,甚至是EntLib,都只是遭受了“不是在这里发明的”综合症,我认为他们浪费了时间来重新发明已经在系统中工作得很好的基础知识。诊断(例如如何编写日志语句),而不是填补存在的少数空白。简而言之,不要使用它们——它们不需要。

你可能不知道的特点:

  • 使用带有格式字符串和参数的TraceEvent重载可以提高性能,因为在Filter.ShouldTrace()成功之前,参数将作为单独的引用保存。这意味着在系统确认消息将被实际记录之前,不会对参数值调用昂贵的ToString()。
  • 跟踪。CorrelationManager允许您关联关于相同逻辑操作的日志语句(参见下面)。
  • VisualBasic.Logging.FileLogTraceListener适合写入日志文件,并支持文件旋转。尽管是在VisualBasic名称空间中,但它也可以简单地通过包含DLL在c#(或其他语言)项目中使用。
  • 当使用EventLogTraceListener时,如果你使用多个参数和空或空格式字符串调用TraceEvent,那么如果你使用本地化的消息资源,参数将直接传递给EventLog.WriteEntry()。
  • Service Trace Viewer工具(来自WCF)对于查看活动相关日志文件的图表非常有用(即使您不使用WCF)。这确实有助于调试涉及多个线程/活动的复杂问题。
  • 通过清除所有侦听器(或删除默认)来避免开销;否则Default将把所有内容传递给跟踪系统(并招致所有ToString()开销)。

你可能想要扩展的领域(如果需要):

  • 数据库跟踪侦听器
  • 彩色控制台跟踪侦听器
  • MSMQ / Email / WMI跟踪监听器(如果需要)
  • 实现一个FileSystemWatcher来调用Trace。为动态配置更改刷新

其他建议:

使用结构化的事件id,并保持一个引用列表(例如,在enum中记录它们)。

为系统中的每个(重要)事件设置唯一的事件id对于关联和查找特定问题非常有用。可以很容易地追踪到记录/使用事件id的特定代码,并且可以很容易地为常见错误提供指导,例如错误5178意味着数据库连接字符串是错误的,等等。

事件id应该遵循某种结构(类似于电子邮件和HTTP中使用的回复代码理论),这允许您在不知道具体代码的情况下按类别处理它们。

例:第一个数字可以详细说明一般类别:1xxx可以用于“开始”操作,2xxx用于正常行为,3xxx用于活动跟踪,4xxx用于警告,5xxx用于错误,8xxx用于“停止”操作,9xxx用于致命错误,等等。

第二个数字可以详细说明区域,例如21xx表示数据库信息(41xx表示数据库警告,51xx表示数据库错误),22xx表示计算模式(42xx表示计算警告等),23xx表示另一个模块,等等。

分配的结构化事件id还允许在过滤器中使用它们。

问:如果你使用跟踪,你会使用Trace.Correlation.StartLogicalOperation吗?

答:跟踪。CorrelationManager对于在任何类型的多线程环境中关联日志语句非常有用(现在几乎是任何环境)。

为了相互关联,您至少需要为每个逻辑操作设置一次ActivityId。

然后,启动/停止LogicalOperationStack可以用于简单的基于堆栈的上下文。对于更复杂的上下文(例如异步操作),使用TraceTransfer到新的ActivityId(在更改它之前),允许相关性。

Service Trace Viewer工具对于查看活动图非常有用(即使您不使用WCF)。

问:您是手动编写这些代码,还是使用某种形式的面向方面编程来实现?愿意分享一段代码片段吗?

答:你可能想要创建一个作用域类,例如LogicalOperationScope,它(A)在创建时设置上下文,(b)在释放时重置上下文。

这允许你编写如下代码来自动换行操作:

  using( LogicalOperationScope operation = new LogicalOperationScope("Operation") )
{
// .. do work here
}

在创建作用域时,如果需要,可以先设置ActivityId,调用StartLogicalOperation,然后记录TraceEventType。消息开始。在Dispose上,它可以记录一个Stop消息,然后调用StopLogicalOperation。

问:你提供任何形式的跟踪源粒度吗?例如,WPF TraceSources允许您在不同级别上配置它们。

答:是的,当系统变得更大时,多个跟踪源是有用/重要的。

虽然你可能想要一致地记录所有的警告&以上,或所有资料对于任何规模合理的系统,活动跟踪(启动、停止等)和详细日志记录的量都太大了。

与其只有一个开关来打开或关闭所有信息,不如能够一次为系统的一个部分打开这些信息。

通过这种方式,您可以从通常的日志(所有警告、错误等)中定位重要的问题,然后“放大”您想要的部分,并将它们设置为活动跟踪甚至调试级别。

所需跟踪源的数量取决于您的应用程序,例如,您可能希望每个程序集或应用程序的每个主要部分都有一个跟踪源。

如果您需要更精细的控制,可以添加单独的布尔开关来打开/关闭特定的大容量跟踪,例如原始消息转储。(或者可以使用单独的跟踪源,类似于WCF/WPF)。

您可能还想考虑为活动跟踪与一般(其他)日志记录考虑单独的跟踪源,因为这样可以更容易地按照您想要的方式配置过滤器。

请注意,即使使用了不同的源,消息仍然可以通过ActivityId进行关联,所以您需要使用多少就使用多少。


听众

问:你使用什么日志输出?

这取决于您正在编写的应用程序类型,以及要记录的内容。通常不同的东西在不同的地方(即多个输出)。

我通常将输出分为三类:

(1)事件- Windows事件日志(和跟踪文件)

例:如果写一个服务器/服务,那么在Windows上的最佳实践是使用Windows事件日志(你没有一个UI来报告)。

在这种情况下,所有致命、错误、警告和(服务级别)信息事件都应该进入Windows事件日志。信息级别应该为这些类型的高级事件保留,那些你想要进入事件日志的事件,例如。“服务已启动”,“服务已停止”,“连接到Xyz”,甚至可能是“计划已启动”,“用户已登录”,等等。

在某些情况下,您可能希望将写入事件日志作为应用程序的内置部分,而不是通过跟踪系统(即直接写入事件日志条目)。这意味着它不会被意外关闭。(请注意,您仍然希望在跟踪系统中记录相同的事件,以便进行关联)。

相反,Windows GUI应用程序通常会向用户报告这些事件(尽管它们也可能记录到Windows事件日志中)。

事件也可能有相关的性能计数器(例如,错误数/秒),协调任何直接写入事件日志、性能计数器、写入跟踪系统和报告给用户,使它们同时发生是很重要的。

例如,如果用户在特定时间看到错误消息,你应该能够在Windows事件日志中找到相同的错误消息,然后在跟踪日志中找到具有相同时间戳的相同事件(以及其他跟踪细节)。

(2)活动——应用程序日志文件或数据库表(和跟踪文件)

这是系统所做的常规活动,例如,提供网页、提交股票市场交易、接收订单、执行计算等。

活动跟踪(启动、停止等)在这里很有用(在正确的粒度上)。

此外,使用特定的应用程序日志(有时称为审计日志)是非常常见的。通常这是一个数据库表或应用程序日志文件,包含结构化数据(即一组字段)。

根据应用程序的不同,这里的情况可能会有点模糊。一个很好的例子可能是一个web服务器,它将每个请求写入一个web日志;类似的例子可能是消息传递系统或计算系统,其中每个操作都记录了特定于应用程序的详细信息。

一个不太好的例子是股票市场交易或销售订单系统。在这些系统中,您可能已经记录了具有重要业务价值的活动,但是将它们与其他操作关联的原则仍然很重要。

除了自定义应用程序日志,活动通常也有相关的性能计数器,例如每秒事务数。

一般来说,您应该协调跨不同系统的活动日志记录,即当您增加性能计数器并记录到跟踪系统时,同时写入应用程序日志。如果您同时执行所有这些操作(或者在代码中依次执行),那么调试问题会更容易(而不是它们都出现在代码中的不同时间/位置)。

(3)调试跟踪-文本文件,或者XML或数据库。

这是Verbose级别或更低级别的信息(例如,打开/关闭原始数据转储的自定义布尔开关)。这提供了系统在子活动级别上所做事情的内容或细节。

这是您希望能够为应用程序的各个部分(因此是多个源)打开/关闭的级别。你不希望这些东西搅乱Windows事件日志。有时会使用数据库,但更有可能使用在特定时间后清除的滚动日志文件。

此信息与应用程序日志文件之间的一个很大区别是,它是非结构化的。虽然应用程序日志可能有To、From、Amount等字段,但详细调试跟踪可能是程序员输入的任何内容,例如。“检查值X={value}, Y=false”,或者像“完成,再试一次”这样的随机注释/标记。

一个重要的实践是确保你放在应用程序日志文件或Windows事件日志中的东西也被记录到跟踪系统中,具有相同的细节(例如时间戳)。这允许您在调查时关联不同的日志。

如果您计划使用特定的日志查看器,因为您有复杂的相关性,例如服务跟踪查看器,那么您需要使用适当的格式,即XML。否则,一个简单的文本文件通常就足够了——在较低的级别上,信息基本上是非结构化的,因此您可能会发现数组转储、堆栈转储等。如果您可以在更高的级别上与更结构化的日志相关联,那么事情应该不会有问题。

问:如果使用文件,您是使用滚动日志还是单个文件?如何让人们可以使用这些日志?

答:对于文件,从可管理性的角度来看,通常需要滚动日志文件(使用System. log)。诊断只需使用VisualBasic.Logging.FileLogTraceListener)。

可用性同样取决于系统。如果您只讨论文件,那么对于服务器/服务,滚动文件只能在必要时访问。(Windows事件日志或数据库应用程序日志将有自己的访问机制)。

如果不容易访问文件系统,那么调试到数据库的跟踪可能会更容易一些。[即实现一个数据库TraceListener]。

我看到的一个有趣的Windows GUI应用程序的解决方案是,它在运行时将非常详细的跟踪信息记录到“飞行记录器”中,然后当你关闭它时,如果它没有问题,它就会简单地删除文件。

然而,如果它崩溃或遇到问题,那么文件不会被删除。如果它捕捉到错误,或者下次运行时它会注意到该文件,然后它可以采取行动,例如压缩它(例如7zip)并通过电子邮件发送或以其他方式提供。

如今,许多系统都将故障自动报告到中央服务器(在与用户进行检查后,例如出于隐私原因)。


查看

问:你使用什么工具来查看日志?

答:如果您由于不同的原因有多个日志,那么您将使用多个查看器。

Notepad/vi/ notepad++或任何其他文本编辑器是纯文本日志的基础。

如果您有复杂的操作,例如带有传输的活动,那么您显然会使用专门的工具,如服务跟踪查看器。(但如果你不需要它,那么文本编辑器更容易)。

由于我通常将高级信息记录到Windows事件日志中,因此它提供了一种以结构化的方式快速获得概述的方法(寻找漂亮的错误/警告图标)。只有当日志中没有足够的文本文件时,您才需要开始查找文本文件,尽管至少日志为您提供了一个起点。(在这一点上,确保您的日志具有协调的整体变得很有用)。

通常,Windows事件日志也使这些重要事件可用于监视工具,如MOM或OpenView。

其他——

如果你登录到一个数据库,它可以很容易地过滤和排序信息(例如,放大一个特定的活动id。(对于文本文件,您可以使用Grep/PowerShell或类似的过滤器对您想要的特定GUID)

Excel(或其他电子表格程序)。这对于分析结构化或半结构化信息非常有用,如果您可以使用正确的分隔符导入信息,以便不同的值放在不同的列中。

当在调试/测试中运行服务时,为了简单起见,我通常将它托管在控制台应用程序中,我发现彩色的控制台记录器很有用(例如,红色表示错误,黄色表示警告,等等)。您需要实现一个自定义跟踪侦听器。

请注意,该框架不包括彩色控制台记录器或数据库记录器,因此,如果您需要它们,您将需要编写它们(这并不太难)。

这真的让我很恼火,一些框架(log4net, EntLib等)浪费时间重新发明轮子,重新实现基本的日志记录,过滤,并将日志记录到文本文件,Windows事件日志和XML文件,每一个都有自己不同的方式(日志语句各不相同);然后,每个都实现了自己版本的数据库记录器,例如,当大多数数据库记录器已经存在时,所需要的只是System.Diagnostics的两个跟踪侦听器。这是对重复工作的巨大浪费。

问:如果你正在构建一个ASP。NET解决方案,你也用ASP。NET运行状况监视?运行状况监控器事件中是否包含跟踪输出?Trace.axd呢?

这些东西可以根据需要打开/关闭。我找到了特蕾西。Axd在调试服务器如何响应某些事情时非常有用,但在大量使用的环境或长期跟踪中通常不太有用。

问:自定义性能计数器如何?

对于专业应用程序,特别是服务器/服务,我希望看到它完全配备了Performance Monitor计数器和日志到Windows事件日志。这些都是Windows中应该使用的标准工具。

您需要确保为您所使用的性能计数器和事件日志包含安装程序;这些应该在安装时创建(以管理员身份安装时)。当您的应用程序正常运行时,它不需要管理权限(因此不能创建丢失的日志)。

这是一个以非管理员身份进行开发的好理由(当您需要安装服务时,请使用单独的管理帐户等)。如果写入事件日志,.NET会在你第一次写入时自动创建一个丢失的日志;如果你以非管理员身份开发,你就能尽早发现这个问题,避免客户安装了你的系统后因为不是管理员身份而无法使用它。

至于面向方面日志,我在另一个SO问题上推荐了PostSharp

面向方面的日志记录与统一\T4\任何其他

如果您正在评估日志记录框架,答案中提供的链接值得访问。

答案中有很多很棒的建议。

一般的最佳实践是考虑谁将阅读日志。在我的例子中,它将是客户端站点的管理员。所以我记录消息,给他们一些可以采取行动的东西。例如,“无法初始化应用程序。这通常是由......引起的”