ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

13 个日志最佳实践

2021-07-17 17:29:44  阅读:152  来源: 互联网

标签:13 记录 最佳 消息 使用 日志 上下文 级别


知道如何记录以及记录什么是软件工程师必须完成的最艰巨的任务之一。主要是因为这项任务类似于占卜。很难知道在故障排除期间需要哪些信息。这就是我希望这 13 个最佳实践能够帮助您增强应用程序日志记录的原因,从而为运维工程师带来巨大利益。

1. 不要自己写日志(又名不要重新发明轮子)

永远不要使用printf或自己将日志条目写入文件,或自己处理日志轮换。请帮个忙,并为此使用标准库或系统 API 调用。通过这种方式,您可以确定正在运行的应用程序将与其他系统组件很好地协同工作,并且无需特殊的系统配置即可登录到正确的位置或网络服务。

因此,如果您只使用系统 API,那么这意味着使用 syslog(3). 了解如何使用它。

如果你更喜欢使用日志库,那么在 Java 世界中有很多这样的库,比如 Log4j、  JCL、  slf4j 和 logback。我最喜欢的是 slf4j 和 logback 的组合,因为它非常强大并且相对容易配置(并且允许 JMX 配置或重新加载配置文件)。

slf4j 最好的一点是,您可以在认为合适时更改日志记录后端。如果您正在编写一个库,这一点尤其重要,因为它允许任何人使用您的库和他们自己的日志后端,而无需对您的库进行任何修改。

还有其他几个用于不同语言的日志库,例如 ruby​​:Log4r、  stdlib logger或几乎完美的 Jordan Sissel 的 Ruby-cabin

如果你不使用日志库的论点是 CPU 消耗,那么我允许你跳过这篇博文。当然,您不应该将日志语句放在紧密的内部循环中,否则,您将永远看不到差异。

2. 在适当的级别登录

如果您遵循第一个最佳实践,那么您可以在应用程序中为每个日志语句使用不同的日志级别。最困难的任务之一是找到应该在哪个级别记录此日志条目。

这里我给出了一些建议:

  • TRACE 级别: 如果在生产中使用,这是一种代码气味。这应该在开发过程中用于跟踪错误,但永远不要提交给您的 VCS。
  • 调试级别:在此级别记录程序中发生的任何事情。这主要在调试时使用,我提倡在进入生产阶段之前减少调试语句的数量,以便只留下最有意义的条目,并且可以在故障排除时激活。
  • 信息级别:在此级别记录用户驱动或系统特定的所有操作(即定期计划的操作......)
  • 注意级别:这肯定是程序在生产时运行的级别。在此级别记录所有不被视为错误的显着事件。
  • WARN 级别:在此级别记录所有可能成为错误的事件。例如,如果一个数据库调用花费的时间超过预定义的时间,或者内存中的缓存接近容量。这将允许适当的自动警报,并且在故障排除期间将允许更好地了解系统在故障前的行为。
  • 错误级别:记录此级别的每个错误情况。这可以是返回错误或内部错误条件的 API 调用。
  • 致命级别:太糟糕了,这是世界末日。很少使用它,这在实际程序中不应该经常发生。通常在此级别进行日志记录表示程序结束。例如,如果网络守护进程无法绑定网络套接字,则在此级别登录并退出是唯一明智的做法。

请注意,您的程序或服务中的默认运行级别可能会有很大差异。例如,我 通常在INFO级别运行我的服务器代码 ,但我的桌面程序在级别DEBUG运行 。因为在您无法访问的计算机上解决问题非常困难,而且在提供支持或客户服务时要求用户向您发送日志比教她更改日志级别然后发送给您要容易得多日志。

当然是YMMV。

3. 使用正确的日志类别

我在第一个技巧中引用的大多数日志库都允许您指定日志类别。这个类别允许我们对日志消息进行分类,最终将根据日志框架配置以不同的方式记录或根本不记录。

大多数情况下,Java 开发人员使用完全限定的类名,其中日志语句作为类别出现。如果您的程序遵守简单的责任原则,那么这种方案的效果会相对较好。

Java 日志库中的日志类别是分层的,因此例如使用 category 进行日志记录 com.daysofwonder.ranking.ELORankingComputation 将匹配顶级 category  com.daysofwonder.ranking。这将允许运维工程师通过仅为此类别指定配置来设置适用于所有排名子系统的日志记录配置。但同时,如果需要,它可以为子类别生成日志配置。

我们可以进一步扩展范式以帮助解决特定情况。

想象一下,您正在处理一个响应基于用户的请求(例如REST API)的服务器软件。如果您的服务器使用此类别进行日志记录 my.service.api.<apitoken> (其中apitoken特定于给定用户),那么您可以通过允许记录所有 API 日志, my.service.api 或者通过使用更详细的级别和类别进行记录来记录单个行为不端的 API 用户 my.service.api.<bad-user-api-token>。当然,这需要一个系统,您可以在其中动态更改日志记录配置。

4. 写有意义的日志消息

这可能是最重要的最佳实践。假设您对程序内部结构有深入的了解,没有什么比神秘的日志条目更糟糕的了。

在编写日志条目消息时,请始终预见到紧急情况,其中您唯一拥有的是日志文件,您必须从中了解发生了什么。做对了可能是被解雇和升职之间的细微差别。

当开发人员编写日志消息时,它是在要插入日志指令的代码的上下文中。在这些情况下,我们倾向于编写推断当前上下文的消息。不幸的是,当阅读日志本身时,此上下文不存在,并且这些消息可能无法理解。

克服这种情况的一种方法(这在警告或错误级别写入时尤其重要)是将补救信息添加到日志消息中。或者,如果这是不可能的,那么操作的目的和结果是什么。

此外,不要添加依赖于前一条消息内容的日志消息。原因是,如果以前的消息记录在不同的类别或级别,则它们可能不会出现。或者更糟的是,它们可以出现在多线程或异步上下文中的不同位置(或更早)。

5. 用英文写日志消息

这似乎是一个奇怪的建议,尤其是来自一个法国人。首先,我仍然认为英语比法语简洁得多,更适合技术语言。如果消息包含超过 50% 的英文单词,您为什么要登录法语?

撇开这一点不谈,以下是这种做法背后的基本原因:

  • 英语意味着您的消息将以 ASCII 字符记录。这一点尤其重要,因为您无法真正知道日志消息会发生什么,也不知道它在被归档到某个地方之前将跨越哪个软件层或媒体。如果您的消息使用特殊字符集甚至 UTF-8,它可能最终无法正确呈现,但最糟糕的是它可能会在传输过程中损坏并变得无法读取。仍然是记录可能采用不同字符集和/或编码的用户输入的问题。
  • 如果您的程序要被大多数人使用,而您没有完全本地化的资源,那么英语可能是您最好的选择。现在,如果您必须本地化一件事,请本地化更接近最终用户的界面(通常不是日志条目)。
  • 如果您本地化您的日志条目(例如所有警告和错误级别),请确保您在这些条目前加上一个特定的有意义的错误代码。通过这种方式,人们可以进行独立于语言的 Internet 搜索和查找信息。这么棒的方案前阵子用在VMS操作系统上,不得不承认是非常有效的。如果你要设计这样一个方案,你可以采用这个方案:APP-S-CODE 或 APP-S-SUB-CODE,分别为:
    • APP:您的应用名称用 3 个字母表示
    • S:1 个字母的严重性(即 D:调试,I:信息...)
    • SUB:此代码所属的应用程序的子部分
    • CODE:特定于相关错误的数字代码

6. 为您的日志消息添加上下文

所以,没有什么比这种日志消息更糟糕的了:

1

 Transaction failed

或者

1

User operation succeeds

或者在 API 异常的情况下:

1

java.lang.IndexOutOfBoundsException

如果没有适当的上下文,这些消息只是噪音,它们不会增加价值并消耗在故障排除过程中可能有用的空间。

添加上下文的消息更有价值,例如:

1

Transaction 2346432 failed: cc number checksum incorrect

或者

1

User 54543 successfully registered e-mail user@domain.com

或者

1

IndexOutOfBoundsException: index 12 is greater than collection size 10

由于我们在最后一个上下文示例中讨论异常,如果您碰巧向上传播异常,请确保使用适合当前级别的上下文增强它们,以简化故障排除,如这个 java 示例:

上下文传播

1 
2 
3 
4 
5 
6 
7

2 
3 
4 
5 
6 
7
  public void storeUserRank(int userId, int rank, String game) {
    try {
      ... deal database ...
    } catch(DatabaseException de) {
      throw new RankingException("Can't store ranking for user "+userId+" in game "+ game + " because " + de.getMessage() );
    }
  }

所以rank API的上层客户端将能够用足够的上下文信息记录错误。如果上下文成为异常本身的参数而不是消息,那就更好了,这样上层可以在需要时使用补救措施。

保持上下文的一种简单方法是使用一些 Java 日志库实现的 MDC。该MDC 是每线程关联阵列。可以修改记录器配置以始终为每个日志行打印 MDC内容。如果您的程序使用每线程范例,这可以帮助解决保持上下文的问题。例如,这个 Java 示例使用MDC 记录给定请求的每个用户信息:

MDC 示例

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12

2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12
  class UserRequest {
    ...
    public void execute(int userid) {
      MDC.put("user", userid);

      // ... all logged message now will display the user=<userid> for this thread context ...
      log.info("Successful execution of request");

      // user request processing is now finished, no need to log our current user anymore
      MDC.remote("user");
    }
  }

请注意,  MDC 系统不能很好地使用异步日志记录方案,例如 Akka的日志记录系统。因为 MDC 保存在每个线程的存储区域中,并且在异步系统中,您不能保证执行日志写入的线程是具有 MDC 的线程。在这种情况下,您需要使用每个日志语句手动记录上下文。

7.登录机器解析格式

日志条目对人类非常有用,但对机器来说却很差。有时手动读取日志文件是不够的,您需要执行一些自动化处理(例如用于警报或审计)。您希望集中存储日志并能够执行搜索请求。

那么,当您像在这个假设的日志记录语句中那样将日志上下文嵌入字符串中时会发生什么?:

难以解析

1

log.info("User {} plays {} in game {}", userId, card, gameId);

这将产生这样的文本:

难以解析

1

2013-01-12 17:49:37,656 [T1] INFO  c.d.g.UserRequest  User 1334563 plays 4 of spades in game 23425656

现在,如果你想解析这个,你需要以下(未经测试的)正则表达式:

难以解析

1

  /User (\d+) plays (.+?) in game (\d+)$/

嗯,这并不容易,而且很容易出错,只是为了访问您的代码本机已经知道的字符串参数。

那么这个想法呢,我相信 Jordan Sissel在他的 ruby​​-cabin 库中首先引入了:让我们在你的日志条目中以机器可解析的格式添加上下文。我们前面提到的例子可以像这样使用 JSON:

易于解析

1

2013-01-12 17:49:37,656 [T1] INFO  c.d.g.UserRequest  User plays {'user':1334563, 'card':'4 of spade', 'game':23425656}

现在您的日志解析器可以更容易编写,索引变得简单,您可以启用所有日志存储 功能。

8. 但也要使日志可读

毫无疑问,日志文件应该是机器可解析的。但它们也应该是人类可读的。这是为什么?

简单地说,人们会阅读日志条目。而那些可能是(有些)压力过大的开发人员,试图对有缺陷的应用程序进行故障排除。不要通过编写难以阅读的日志条目来使他们的生活变得更加艰难。

好的,但是我们如何实现人类可读的日志呢?好吧,Scalyr 博客有一整篇文章涵盖了这一点,但以下是主要花絮:

  • 使用标准日期和时间格式 (ISO8601)
  • 以 UTC 或本地时间加上偏移量添加时间戳
  • 正确使用日志级别
  • 将不同级别的日志拆分到不同的目标以控制其粒度
  • 记录异常时包括堆栈跟踪
  • 从多线程应用程序登录时包括线程的名称

9. 不要记录太多或太少

这听起来可能很愚蠢,但日志量有一个适当的平衡。

太多的日志,真的很难从中获得任何价值。手动浏览此类日志时,会出现太多混乱,这在凌晨 3 点尝试对生产问题进行故障排除时并不是一件好事。

日志太少,您可能会面临无法解决问题的风险:故障排除就像解决一个难题,您需要为此获得足够的材料。

不幸的是,编码时没有神奇的规则来知道要记录什么。因此,严格遵守前两个最佳实践非常重要,以便在应用程序上线时更容易增加或减少日志详细程度。

解决此问题的一种方法是在开发过程中尽可能多地记录日志(不要将此与为调试程序而添加的日志混淆)。然后当应用程序进入生产时,对产生的日志进行分析,并根据发现的问题相应地减少或增加日志记录语句。尤其是在故障排除期间,请注意您希望拥有更多上下文或日志记录的应用程序部分,并确保将这些日志语句添加到下一个版本中(如果可能,同时修复问题以使问题在内存中保持新鲜)。当然,这需要运维人员和开发人员之间进行大量的沟通。

这可能是一项复杂的任务,但我建议像重构代码一样重构日志记录语句。这个想法是在生产日志和此类日志语句的修改之间建立一个紧密的反馈循环。如果您的组织拥有持续交付流程,则效率会更高,因为重构可以是持续的。

日志语句是某种代码元数据,与代码注释处于同一级别。保持日志语句与代码同步非常重要。在对问题进行故障排除以获取与处理的代码无关的不相关消息时,没有什么比这更糟糕的了。

10.想想你的观众

为什么要向应用程序添加日志记录?

唯一的答案是有人必须在一天或更晚的时候阅读它(或者有什么意义?)。更重要的是,考虑谁会阅读这些行是很有趣的。根据您认为会阅读您将要编写的日志消息的人的不同,日志消息的内容、上下文、类别和级别可能会大不相同。

这些人可以是:

  • 试图解决自己问题的最终用户(想象一下客户端或桌面程序)
  • 系统管理员或操作工程师对生产问题进行故障排除
  • 在开发过程中进行调试或解决生产问题的开发人员

当然,开发人员知道程序的内部结构,因此她的日志消息可能比将日志消息发送给最终用户要复杂得多。因此,根据预期的目标受众调整您的语言,您甚至可以为此专门设置单独的类别。

11. 不要仅出于故障排除目的而登录

正如日志消息可以为不同的受众编写一样,日志消息也可以用于不同的原因。尽管故障排除肯定是日志消息最明显的目标,但您也可以非常有效地将日志消息用于:

  • 审计:这有时是业务需求。这个想法是捕捉对管理层或法律人员很重要的重大事件。这些语句通常描述系统用户正在做什么(例如谁登录,谁编辑,等等)。
  • 分析:由于日志带有时间戳(有时到毫秒级别),它可以成为分析程序部分的好工具,例如通过记录操作的开始和结束,您可以自动(通过解析日志)或在故障排除期间推断一些性能指标,而不将这些指标添加到程序本身。
  • 统计:如果您每次发生特定事件(例如某种错误或事件)时都进行记录,则可以计算有关正在运行的程序(或用户行为)的有趣统计信息。也可以将其连接到一个警报系统,该系统可以连续检测到太多错误。

12. 避免供应商锁定

这个技巧已经部分地被第一个所涵盖,但我认为值得以更明确的方式提及它。

所以,这里的建议很简单:避免被任何特定的供应商锁定。以这样的方式组织您的日志记录策略,以便在需要时将日志库或框架与另一个交换变得简单。

你是怎样做的?

一种方法是通过使用包装器来确保您的应用程序代码没有明确提及第三方工具。相反,创建一个具有适当方法的记录器接口,以及一个实现它的类。然后,将实际调用第三方工具的代码添加到此类中。这样,您就可以保护您的应用程序免受第三方工具的影响。如果您需要将其替换为另一个,只需在整个应用程序中更改一个地方即可。

为了使这种方法更容易,您可以采用日志外观,例如 slf4j,该帖子已经提到过。该项目提供了对多个日志框架的标准化抽象,使得交换一个框架变得非常容易。

13. 不要记录敏感信息

最后,记录安全提示:不要记录敏感信息。首先,明显的位。确保你永远不会登录:

  • 密码
  • 信用卡号码
  • 社会安全号码

现在,你不应该记录那些不太明显的事情。

  • 会话标识符 用户选择退出的信息
  • 授权令牌
  • PII(个人身份信息,例如个人姓名)

此外,您必须确保您不会无意中触犯法律。有些法律法规禁止您记录某些信息。最著名的此类法规可能是GDPR,但它并不是唯一的法规。确保您了解并遵守您所在国家和地区的法律法规。

标签:13,记录,最佳,消息,使用,日志,上下文,级别
来源: https://blog.csdn.net/allway2/article/details/118859750

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有