网上很多关于日志规范的文章,这次也根据自己多年的从业经验说下自己的想法。
日志,维基百科的定义是记录服务器等电脑设备或软件的运作。
日志文件提供精确的系统记录,根据日志最终定位到错误详情和根源。日志的特点是,它描述一些离散的(不连续的)事件。例如:应用通过一个滚动的文件输出 INFO 或 ERROR 信息,并通过日志收集系统,存储到一些存储引擎(Elasticsearch)中方便查询。
在上文中我们解释了日志的作用是提供精准的系统记录方便根因分析。那么具体在哪些具体方面它可以发挥作用?
上文说了日志的重要性,那么什么时候需要记录日志。
Slf4j 英文全称为 “ Simple Logging Facade for Java ”,为 Java 提供的简单日志门面。Facade 门面,更底层一点说就是接口。它允许用户以自己的喜好,在工程中通过 Slf4j 接入不同的日志系统。
Logback 是 Slf4j 的原生实现框架,同样也是出自 Log4j 一个人之手,但拥有比 Log4j 更多的优点、特性和更做强的性能,Logback 相对于 Log4j 拥有更快的执行速度。基于我们先前在 Log4j 上的工作,Logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上 10 倍。在保证 Logback 的组件更加快速的同时,同时所需的内存更加少。
log4js是nodejs中已有的较成熟的日志库,功能齐全,性能不错,扩展方便。且在qr-web-pay中已经应用,有大量使用经验。
eggjs中自带了egg-logger插件,功能,性能,扩展都很不错。
日志文件放置于固定的目录中,按照一定的模板进行命名,推荐的日志文件名称:
当前正在写入的日志文件名:<应用名>[-<功能名>].log 如:example-server-book-service-access.log 已经滚入历史的日志文件名:<应用名>[-<功能名>].yyyy-MM-dd-hh.[滚动号].log 如:example-server-book-service-access.2019-12-01-10.1.log
推荐使用 lombok(代码生成器) 注解 @lombok.extern.slf4j.Slf4j 来生成日志变量实例。
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope></dependency> |
---|
代码示例
import lombok.extern.slf4j.Slf4j; @Slf4jpublic class LogTest { public static void main(String[] args) { log.info("this is log test"); }} |
---|
无特殊方式,使用正常nodejs工具要求的引入方式引入。
日志记录采用分级记录,ERROR级别日志与其他级别日志分开,单独记录。日志文件名相对应,其他级别的日志信息记录到一个日志文件中。如有特殊格式日志,如 access log,单独使用一个文件,请注意避免重复打印。
一般有如下分类:
使用参数化形式 {} 占位,[] 进行参数隔离,这样的好处是可读性更高,而且只有真正准备打印的时候才会处理参数。
正确示例
必须使用参数化信息的方式
log.debug("order is paying with userId:[{}] and orderId : [{}]", userId, orderId);
错误示例
不要进行字符串拼接,那样会产生很多 String 对象,占用空间,影响性能。
log.debug("order is paying with userId: " + userId + " and orderId: " + orderId);
使用nodejs自带的参数化标识占位,如 %d、%s、%j,这样的好处是可读性更高,而且只有真正准备打印的时候才会处理参数,所有的可用参数化标识参见 node format args。
正确示例
必须使用参数化信息的方式
log.debug("order is paying with userId:%s and orderId : %s", userId, orderId);
错误示例
不要进行字符串拼接, 不利于阅读,容易出错。
log.debug("order is paying with userId: " + userId + " and orderId: " + orderId);
作为日志产生的日期和时间,这个数据非常重要,一般精确到毫秒。
yyyy-MM-dd HH:mm:ss.SSS
日志的输出都是分级别的,不同的设置不同的场合打印不同的日志。
主要使用如下的四个级别:
DEBUG 级别比 INFO 低,包含调试时更详细的了解系统运行状态的东西,比如变量的值等等,都可以输出到 DEBUG 日志里。INFO 是在线日志默认的输出级别,反馈系统的当前状态给最终用户看的。输出的信息,应该对最终用户具有实际意义的。从功能角度上说,INFO 输出的信息可以看作是软件产品的一部分,所以需要谨慎对待,不可随便输出。如果这条日志会被频繁打印或者大部分时间对于纠错起不到作用,就应当考虑下调为 DEBUG 级别。
当方法或者功能处理过程中产生不符合预期结果或者有框架报错时可以考虑使用,常见问题处理方法包括:
一般来说,ERROR 级别的日志意味着系统中发生了非常严重的问题,必须有人马上处理,比如数据库不可用,系统的关键业务流程走不下去等等。错误的使用反而带来严重的后果,不区分问题的重要程度,只要有问题就error记录下来,其实这样是非常不负责任的,因为对于成熟的系统,都会有一套完整的报错机制,那这个错误信息什么时候需要发出来,很多都是依据单位时间内 ERROR 日志的数量来确定的。
ERROR 级别的日志打印通常伴随报警通知。ERROR的报出应该伴随着业务功能受损,即上面提到的系统中发生了非常严重的问题,必须有人马上处理。
ERROR日志目标是给处理者直接准确的信息:ERROR 信息形成自身闭环。
问题定位:
输出该日志的线程名称,一般在一个应用中一个同步请求由同一线程完成,输出线程名称可以在各个请求产生的日志中进行分类,便于分清当前请求上下文的日志。
在分布式应用中,用户的一个请求会调用若干个服务完成,这些服务可能还是嵌套调用的,因此完成一个请求的日志并不在一个应用的日志文件,而是分散在不同服务器上不同应用节点的日志文件中。该标识是为了串联一个请求在整个系统中的调用日志。
通过搜索 trace id 就可以查到这个 trace id 标识的请求在整个系统中流转(处理)过程中产生的所有日志。
在业务开发中,我们的日志都是和业务相关联的,有时候是需要根据用户或者业务做聚类的,因此一次请求如果可以通过某项标识做聚类的时候,可以将聚类标识打印到日志中。
日志记录器名称一般使用类名,日志文件中可以输出简单的类名即可,看实际情况是否需要使用包名和行号等信息。主要用于看到日志后到哪个类中去找这个日志输出,便于定位问题所在。
java中禁用 System.out.println 和 System.err.println,nodejs中禁用console.log 以及 console 的其他输入方法。
java中输出日志的对象,应在其类中实现快速的 toString 方法,以便于在日志输出时仅输出这个对象类名和 hashCode。
预防空指针:不要在日志中调用对象的方法获取值,除非确保该对象肯定不为 null,否则很有可能会因为日志的问题而导致应用产生空指针异常。
异常堆栈一般会出现在 ERROR 或者 WARN 级别的日志中,异常堆栈含有方法调用链的系统,以及异常产生的根源。异常堆栈的日志属于上一行日志的,在日志收集时需要将其划至上一行中。
日志输出的格式通常的方式为文本格式,即在日志文件中已约定好的格式输出,这样的方式在日志文件中相较于JSON的格式更加好直接读取。但是在后续的日志文件处理时则困难许多。我们认为日志的后续的处理更加重要,且在之后的云原生的架构方式下,我们是不鼓励使用者直接到机器上直接读取日志的,所以日志的输出格式统一为JSON格式。
以JSON的方式输出如下信息:
日志是工程的基础模块之一,上述约定中较多为日志使用层面,这一部分应该由各个开发者学习并遵循。但其中关于opentracing标识的记录及输出,应该由框架团队提供统一的接入方式。在分布式应用越来越广泛的今天,大多数该公司内部或多或少都用到了链路追踪。链路追踪的有效性也依赖各应用方记录日志的规范性,才能在发生问题的时候更好的定位。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。