应用程序日志记录有哪些模式和反模式?[关闭]


66

最近,我不得不调查我们的大型企业应用程序的一个领域问题。不得不梳理日志以发现问题,这让我感到恐惧,最终,日志根本无法帮助您识别/隔离错误。

注意:我了解并非所有错误都可以通过日志发现。这不会改变日志令人恐惧的事实。

我们的日志记录存在一些显而易见的问题,我们已经可以尝试解决。我不想在这里列出这些内容,因此我不能简单地向您显示我们的日志文件,以便您可以就如何做提供建议。

相反,为了评估我们在日志记录方面的表现,我想知道:

  1. 关于应用程序(尤其是大型应用程序)的日志记录有哪些准则(如果有)。
  2. 是否有我们应该遵循的模式或我们应该注意的反模式
  3. 这是要修复的重要事情,还是可以修复,还是所有日志文件都非常庞大,并且您需要补充脚本来分析它们?

旁注:我们使用log4j。

Answers:


55

我的实践证明有用的几点:

  • 将所有日志记录代码保留在生产代码中。能够启用生产中的更多/更少的详细日志记录,最好是按子系统,并且无需重新启动程序。

  • 使日志易于被grep人和眼睛解析。在每一行的开头都坚持几个共同的领域。确定每一行中的时间,严重性和子系统。清楚地表达信息。使每个日志消息都易于映射到其源代码行。

  • 如果发生错误,请尝试收集并记录尽可能多的信息。可能要花很长时间,但可以,因为正常的处理还是会失败。当生产中发生相同情况并连接调试器时,不必等待,这是无价的。

监视和故障排除最需要日志。将自己放在疑难解答者的鞋中,想一想当发生错误或在深夜里发生什么事情时,您想要什么样的日志。


10
我喜欢这个答案,但我想补充一点,重要的是要记录决策点做出的选择。我已经看到许多系统记录了很多垃圾,但是关键决策没有被记录。因此,95%的日志记录基本上是无用的。同样对于请求/响应类型的系统,每个请求的日志记录能力比子系统更为重要。
凯文

4
+1。我喜欢您关于将自己置于疑难解答者之内的观点。听起来日志语句应该包含比我们一直在做的质量更高的消息...
c_maker

1
重要的是要注意,错误日志记录应该记录到适当的事件日志以及应用程序日志中。
史蒂文·埃弗斯

2
@SnOrfus:有多种存储日志的方法,但是本质是日志消息需要在系统崩溃的最后一秒才可用-就像飞机上的黑匣子一样。如果您使用任何类型的缓冲,请提供一个绕过它的选项/刷新每条消息。
rwong 2011年

1
@Rig:另一方面,许多本地记录器没有实现任何缓冲(并忠实地刷新了每条消息),从而导致性能很差。这就是为什么必须使其成为可选的原因。
rwong

28

我使用的是对安全至关重要的实时系统,日志记录通常是捕获罕见错误的唯一方法,如果您发现我的漂移,它会在满月的每第53个星期二的蓝色月亮出现一次。这种让您沉迷于该主题的东西,所以如果我开始在嘴上起泡沫,我现在就道歉。以下内容是为本机代码调试日志编写的,但大多数内容也适用于托管世界...

使用文本日志文件。似乎很明显,但是有些人确实尝试生成二进制日志文件:这只是愚蠢的,因为当我在现场时,我不需要寻找阅读器工具。另外,如果是文本且调试很冗长,则现场工程师很有可能无需回过头就可以读取文件并诊断问题。每个人都赢。

我设计的系统几乎可以记录所有内容,但是默认情况下我不启用所有功能。调试信息将发送到一个隐藏的调试对话框,该对话框会对其进行时间戳记,然后将其输出到列表框(删除前限制为大约500行),并且该对话框允许我停止它,自动将其保存到日志文件或将其转移到附加的调试器。这种转移使我可以看到来自多个应用程序的调试输出,它们都被整齐地序列化了,有时可以节省生命。我曾经使用数字日志记录级别(设置的级别越高,捕获的内容越多):

off
errors only
basic
detailed
everything

但这太不灵活了-当您朝着错误的方向努力时,能够准确地登录所需的内容而不必经历大量的碎屑,效率会大大提高,这可能是一种特殊的事务或操作导致错误。如果这需要您打开所有内容,则只会使自己的工作变得更加困难。您需要更细粒度的东西。

所以现在我正在基于标志系统切换到日志记录。记录的所有内容都有一个标志,详细说明该操作的类型,并且有一组复选框允许我定义要记录的内容。通常,该列表如下所示:

#define DEBUG_ERROR          1
#define DEBUG_BASIC          2
#define DEBUG_DETAIL         4
#define DEBUG_MSG_BASIC      8
#define DEBUG_MSG_POLL       16
#define DEBUG_MSG_STATUS     32
#define DEBUG_METRICS        64
#define DEBUG_EXCEPTION      128
#define DEBUG_STATE_CHANGE   256
#define DEBUG_DB_READ        512
#define DEBUG_DB_WRITE       1024
#define DEBUG_SQL_TEXT       2048
#define DEBUG_MSG_CONTENTS   4096

此日志记录系统附带发行版本,默认情况下已打开并保存到文件。如果发现该错误平均每6个月发生一次且您无法重现,那么现在就发现您应该已经在进行日志记录已经太迟了。仅适用于调试版本的日志记录是正确的。普通 哑。

该软件通常附带ERROR,BASIC,STATE_CHANGE和EXCEPTION,但可以通过调试对话框(或保存这些内容的注册表/ ini / cfg设置)在现场进行更改。

哦,还有一件事-我的调试系统每天生成一个文件。您的要求可能有所不同。但是请确保您的调试代码以日期,正在运行的代码的版本以及每个文件的ID(系统可能的位置),每个客户ID,系统位置或其他任何内容开头。您可以从现场获得大量的日志文件,并且需要一些记录,这些记录来自何处以及它们运行的​​系统的哪个版本实际上在数据本身中,并且您无法信任客户/现场工程师告诉您他们所拥有的版本-他们可能只是告诉您他们所拥有的版本。更糟糕的是,他们可能会报告磁盘上的exe版本,但旧版本仍在运行,因为他们在更换后忘了重启。让您的代码告诉您自己。

最后,您不希望您的代码产生自己的问题,因此请放置计时器功能,以便在数天或数周后清除日志文件(只需检查现在和创建文件之间的时间差)。对于始终运行的服务器应用程序,这是可以的,在客户端应用程序上,您可以在启动时清除所有旧数据。我们通常会在30天左右后清除系统,而在没有工程师频繁访问的情况下,您可能希望将其保留更长时间。显然,这也取决于日志文件的大小。


1
+1通常是一个很好的答案,但特别是对于将应用程序ID和版本信息放入日志文件的情况,不幸的是,这种情况经常被遗漏。
Binary Worrier 2013年

27

关于日志记录准则,我最喜欢的公共资源是Apache JCL最佳实践

JCL的最佳实践分为两类:常规和企业。一般原则很明确。企业实践更多地涉及其中,并且并不总是很清楚为什么它们如此重要。

企业最佳实践原则适用于预期在“企业”级环境中执行的中间件组件和工具。这些问题与国际化日志记录和故障检测有关。企业需要付出更多的精力和计划,但是在生产级别的系统中强烈建议(如果不需要)。不同的公司企业/环境有不同的要求,因此保持灵活性总是有帮助的...

尽管以JCL为目标,但它们似乎足够通用,因此通常可以用于日志记录。

  • 我个人的日志“指南”是在调试级别,我试图使日志读起来像一个故事-具有可理解的逻辑和足够的(但不超载)详细信息。

最著名的反模式可能是“吞咽异常”-只需在网上搜索即可。

至于巨大的日志文件,在我的实践中,这通常是正常情况。是的,您所说的补充脚本和/或电锯之类的工具对我来说也很普通。

  • 以上并不意味着您必须始终盲目地将所有日志放入一个大文件中。有时将一些日志写入/复制到单独的文件中可能会很有用。例如,在我最近的项目质量检查人员中,要求提供专用文件以获取度量和计时数据以及有关系统操作的简短报告。他们说他们将从中受益,而开发人员做到了(简短报告文件的好处确实很重要)。

PS。关于反模式,其他想到的是“泛滥的”和毫无意义的信息。

  • 当我看到来自多次迭代的循环中的多个类似消息时,我称之为泛洪。对我来说,当我在源代码中检测到泛洪时,它就足以消除它。通常,对其进行改进需要一些技巧-因为在循环中发生的事情可能会很有趣。当我没有时间进一步改善它时,我尝试至少将此类消息的日志记录级别更改为最低级别,以使其更易于过滤。

  • 毫无意义的消息似乎很流行。当阅读源代码时,这些看起来无害-我想必须经历分析调试输出的痛苦,就像...

    step #1
    step #2
    step #3
    

    ...深深地欣赏他们内在的丑陋。我最喜欢在源代码级别检测这种问题的启发式方法(由同事在我过去的一个项目中提出)是计算记录中使用的字符串文字中空格符号出现的次数。以我的经验,零空间基本上可以保证日志记录语句是毫无意义的,一个空间也可以很好地表明潜在问题。


4
为了避免泛洪,我通常收集循环的启发式方法,并在循环后将其输出。意味着循环中发生的任何有趣的事情都应存储在变量中(例如somethingSpecialHappenedCount),然后输出到记录器。
Spoike

@Spoike好点!存放在变量确实是我个人最喜欢的招数打洪水一个
蚊蚋

1
循环结束后,我确实将所有不同的计数器作为ASCII表输出到记录器,以便可以轻松比较它们。表的想法是由Spring的StopWatch.prettyPrint()生成的。除此之外,使日志文本可读并相关仍然是答案中前面提到的“艺术”。
Spoike

@Spoike :(和@gnat)这很有趣。因此,基本上,您仅将实际代码添加到业务逻辑中就是为了进行记录吗?我以前从未听说过或做过此事,也不确定如何将其证明给我的同事。恐怕如果我们开始这样做,某些开发人员将使源代码混乱到一定程度,以致业务逻辑变得混乱且难以阅读。仅仅记录一条语句就已经使源代码看起来更丑陋。
c_maker 2011年

2
@c_maker您关于混合日志记录与业务逻辑的观点似乎值得一个专门的问题。就个人而言,我对这些问题还没有强烈的意见。从理论上讲,可以想象使用AOP和iirc可以对分离进行一些改进,甚至这种方法还有实际应用。但是,在实践中,我坚持使用“混合”方法,到目前为止,我还没有遇到重大问题。源代码混乱是真正的危险,但是到目前为止,我仍然能够“和平地”使它与日志记录代码共存。当然,这需要一定的努力。
蚊蚋

11

仅记录一次异常!

我注意到的常见痛点之一是记录并重新抛出异常。结果,日志文件在多个堆栈级别上多次包含相同的异常。


5

这是一种反模式:在数据库表中创建两个“ genericvariable”字段以跟踪任何可能的内容,然后为不同类型的日志提供88个(并计数)不同的枚举值。


+1-我已经看到了。“错误表”具有诸如string1,string2,string3,string4,string5之类的列,其中将所有列都加以隐含会导致错误代码,该错误代码在任何文档中都未引用。结果是日志既混乱又无用;也称为“带有自定义开发调试地狱的第三方企业应用程序”。
Morgan Herlocker 2011年

就我而言,这是“手动记录系统,根本不知道记录实际上涉及什么”
Wayne Molina

4

我对日志的经验越多越好,但是要保持一致,以使其可以通过计算机过滤,并且可以分别为应用程序的每个组件配置严重性级别。

另外,很难预测找到将来的错误所需的日志记录。在产品出厂之前,大多数显而易见的错误记录位置已固定。错误报告的结果并不罕见,因为您只是添加了日志以帮助诊断它是否再次发生。


2

这是房屋运营方面的一些注意事项:

1)确保日志在本地可配置,最好使用不超过文本编辑器的工具。大多数情况下,我们不想获取TRACE级别的日志记录,但是我们希望能够将其打开。

2)尽可能确保可以使用不超过文本编辑器的工具读取日志。当生产系统出现故障时,没有比在奇数小时内进行工具搜寻更糟糕的了。


1

根据我在使用Web应用程序方面的经验:

(并且考虑到如今的存储非常便宜)

  • 记录尽可能多的可用信息(此时)。
  • 我总是在日志字符串中包含DateTime.Now。
  • 我总是(如果可能的话)记录某些特定“动作”的持续时间。
  • 与您的日志字符串保持一致。既然我总是使用这种模式:

    • “ [信息X] [信息Y] [信息Z] [等]”

1

除了堆栈跟踪,还记录当前应用程序状态和输入。

软件是确定性的,通常这两个是重现该错误所需的唯一操作。在某些情况下,存储完整状态可能很麻烦,因此,例如通过先前的输入来重现当前状态的方法也不错。

当然,更多的数据总是更好,但是至少这两个是最容易崩溃的良好起点。


3
不幸的是,“软件是确定性的” => 以并发错误为例。
assylias 2012年
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.