日志--打印规范

    科技2025-08-28  23

    日志

    记录应用系统日志主要有三个原因: 记录操作轨迹, 监控系统运行状况, 回溯系统故障。

    记录操作行为及操作轨迹数据 可以数据化地分析用户偏好,有助于优化业务逻辑,为用户提供个性化的服务, 例如: 通过access.log 记录用户的操作频度和跳转链接,有助于分析用户的后续行为。监控系统运行状况 是指对服务器使用状态, 如内存,CPU等使用情况;应用运行情况,如相应时间,QPS 等交互状态; 应用错误信息,如空指针,SQL 异常等的监控。回溯系统故障 当系统发生线上问题时,完整的现场日志有助于工程师快速定位问题。 例如,当系统内存溢出时,如果日志系统记录了问题发生现场的堆信息,就可以通过这个日志分析时什么对象在大量产生并且没有释放内存, 回溯系统故障,从而定位问题。

    日志规范

    推荐的日志文件命名方式为: appName_logType_logName.log 。其中,logType为日志类型,推荐分类有 stats(统计),monitor(监控),visit(访问)等; logName 为日志描述。 这种命名的好处就是通过文件名就可以知道文件属于什么应用,什么类型,什么目的,也有利于归类查找。例如,mppserver 应用中单独监控时区转换异常的日志文件定义为: mppserver_monitor_timeZoneConvert.log 。

    日志的级别

    针对不同的场景,日志被分为五种不同的级别,按照重要程度由低到高排序:

    DEBUG 级别日志记录对调试程序有帮助的信息。INFO 级别日志 用来记录程序运行现场,虽然此处未发生错误,但是对排查其他错误具有指导意义。WARN 级别日志 也可以用来记录程序运行现场, 但是更偏向于表明此处有出现潜在错误的可能。ERROR 级别日志 表明当前程序运行发生了错误,需要被关注,但是当前发生的错误,没有影响系统的继续运行。FATAL 级别日志 表明当前程序运行出现了严重的错误事件,并且将会导致应用程序中断。 预先判断日志级别 对DEBUG ,INFO 级别的日志,必须使用条件输出或者使用占位符的方式打印。该约定综合考虑了程序的运行效率和日志打印需求。 // 使用条件判断形式 if(logger.isDebugEnabled()){ logger.debug("Percessing trade with id: "+ id + "and symbol: "+symbol); } // 使用占位符形式 logger.debug("Processing trade with id: {} and symbol: {}", id, symbol); 避免无效日志打印 生产环境禁止输出DEBUG 日志且有选择地输出 INFO 日志。 使用 INFO ,WARN 级别来记录业务行为信息时,一定要控制日志输出量,以免磁盘空间不足,同时要为日志文件设置合理的生命周期,及时清理过期日志。 避免重复打印,务必在日志配置文件中设置 additivity=false, 示例: <logger name="com.taobao.ecrm.member.config" additivity = "false">

    区别对待错误日志 WARN, ERROR 都是与错误有关的日志级别,但不要一发生错误就笼统的输出ERROR 级别日志. 一些业务异常时可以通过引导重试就能恢复正常的, 例如 用户输入参数错误.在这种情况下,记录日志是为了在用户咨询时可以还原现场,如果输出为ERROR 级别就表示一旦出现就需要人为介入, 这显然是不合理的,所以,ERROR级别只记录系统逻辑错误,异常或者违反重要的业务规则,其他错误都可以归为 WARN 级别.

    保证记录内容完整 日志记录的内容包括现场上下文信息与异常堆栈信息, 所以打印时需要注意以下两点: (1) 记录异常时一定要输出异常堆栈, 例如 logger.error(“xxx”+e.getMessage(),e); (2) 日志中如果输出对象实例,要确保实例类重写了 toString 方法,否则只会输出对象的 hashCode值,没有实际意义.

    综上所述, 记录日志时一定请思考三个问题: ① 日志是否有人看 ② 看到这条日志能做什么 ③ 能不能提升问题排除效率

    日志框架

    log4j, logback , jdk-logging, slf4j, commons-logging等框架; 日志框架分为三大部分,包括日志门面, 日志适配器, 日志库; 利用门面设计模式, 即 Facade 来进行解耦, 使日志使用变得更加简单. 如图所示:

    日志门面 门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式, 类似于JDBC 的设计理念. 它只提供了一套接口规范,自身不负责日志功能的实现, 目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印及具体的使用细节等. 目前用得最为广泛的日志门面有两种: slf4j 和 commons-logging

    日志库 它具体实现了日志的相关功能, 主流的日志库有三个,分别是log4j, log-jdk, logback. 在JDK1.4版引入了一个日志库 java.util.logging.Logger ,简称 log-jdk . logback 是最晚出现的, 它与log4j 出自同一个作者, 是log4j 的升级版且本身就实现了 slf4j的接口

    日志适配器 日志适配器分两种场景: (1) 日志门面适配器, 因为slf4j 规范是后来提出的,在此之前的日志库是没有实现 slf4j 的接口的, 例如 log4j; 所以,在工程里使用 slf4j + log4j 的模式,就额外需要一个适配器( slf4j-log4j12) 来解决接口不兼容的问题 (2) 日志库适配器, 在一些老的工程里, 一开始为了开发简单而直接使用了日志库API 来完成日志打印, 随着时间的推移想将原来直接调用日志库的模式改为业界标准的门面模式( 例如 slf4j + logback 组合),就需要一个适配器来完成从旧日志库的API 到slf4j 的路由

    如果是新工程推荐使用 slf4j + logback 模式, 因为logback 自身实现了slf4j 的接口, 无需额外引入适配器,另外 logback 是log4j 的升级版, 具备比 log4j 更多的优点. 可通过如下配置进行集成

    <dependency> <groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>${slf4j-api.version}</version> </dependency> <dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-core</artifactid> <version>${logback-core.version}</version> </dependency> <dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-classic</artifactid> <version>${logback-classic.version}</version> </dependency>

    完成了日志框架的集成,再加上一个日志配置文件(如 logback.xml , log4j.xml等) , 并在工程启动时加载,然后就可以进行日志打印了.示例代码如下:

    private static final Logger logger = LoggerFactory.getLogger(Abc.class) ;

    logger 被定义为static 变量,是因为这个 logger 与当前类绑定, 避免每次都new 一个新对象, 造成资源浪费,甚至引发 OutOfMemoryError 问题.

    Processed: 0.015, SQL: 8