每日一博 - 关于日志记录的最佳实践

2024-03-31 07:36

本文主要是介绍每日一博 - 关于日志记录的最佳实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 概述
  • 选择合适的日志等级
  • 打印函数的入参、出参
  • 打印日志对象要做判空处理,避免阻断流程
  • 推荐使用 Slf4j
  • 不用e.printStackTrace()打印日志
  • 低级别的日志输出,必须进行日志级别开关判断
  • 不打印重复日志
  • 打印全部的异常信息,方便定位问题
  • 核心业务逻辑,在每个分支首行都打印日志
  • 不打印无意义的日志(不携带上下文、日志链路 id)
  • 日志尽量使用英文

在这里插入图片描述


概述

记录日志是任何应用程序中至关重要的一部分,它可以帮助开发人员了解应用程序的行为、调试问题以及监控系统的健康状态。

  1. 使用日志框架

    • 选择一个成熟、广泛使用且功能丰富的日志框架,如Log4j2、Logback或java.util.logging (JUL)。
    • Log4j2和Logback是目前较为流行的选择,它们提供了丰富的功能和灵活的配置选项。
  2. 使用SLF4J进行日志抽象

    • SLF4J (Simple Logging Facade for Java) 提供了一种日志框架的抽象,可以在运行时绑定到不同的日志框架。
    • 这样可以使得你的应用程序代码与具体的日志框架解耦,方便后期切换日志框架。
  3. 选择合适的日志级别

    • 根据日志信息的重要性选择合适的日志级别,常见的级别包括DEBUG、INFO、WARN、ERROR和FATAL。
    • DEBUG用于调试信息,INFO用于一般的信息记录,WARN用于警告,ERROR用于错误信息,FATAL用于严重的致命错误。
  4. 记录有意义的信息

    • 确保记录的日志信息具有可读性和实用性,包括时间戳、线程信息、异常信息等。
    • 避免记录过于冗长或无用的信息,以免日志文件变得过大。
  5. 避免直接打印日志

    • 避免在代码中直接使用System.out.println()或System.err.println()等方式打印日志,而是应该使用日志框架提供的API来记录日志。
    • 这样可以更好地控制日志的输出格式、级别和目的地。
  6. 使用合适的日志格式

    • 配置日志格式以适应你的应用程序需求,包括时间戳格式、日志级别、线程信息等。
    • 可以考虑使用JSON格式或者结构化日志格式,以便后续的日志分析和处理。
  7. 配置日志输出

    • 配置日志输出目的地,可以输出到控制台、文件、数据库等不同的地方。
    • 针对不同的环境(如开发、测试、生产),可以配置不同的日志输出策略和目的地。
  8. 定期维护日志

    • 定期清理和归档日志文件,以防止日志文件过大影响系统性能和存储空间。
  9. 记录异常信息

    • 在捕获和处理异常时,确保记录足够的信息以便于后续排查问题。
    • 可以记录异常的堆栈跟踪、异常类型、异常发生的位置等信息。
  10. 开启日志异步记录

    • 对于高并发的应用程序,可以考虑开启日志的异步记录,以减少对系统性能的影响。

在这里插入图片描述

选择合适的日志等级

  1. Error:

    • 严重的问题,可能导致系统崩溃或者业务受到重大影响。
    • 应该关注系统的稳定性和安全性,运维团队需要重点监控并及时处理。
    • 例如:数据库连接失败、关键服务无法启动、未处理的异常等。
  2. Warn:

    • 不会导致系统崩溃,但可能会影响系统的正常运行。
    • 开发人员需要关注,可能需要进一步调查和处理,以防问题进一步恶化。
    • 例如:潜在的性能问题、不符合预期的业务流程、资源使用超出预期等。
  3. Info:

    • 关键的系统运行信息,用于保留系统运行的关键指标。
    • 记录重要的业务流程、函数的入参和出参、关键操作的执行情况等。
    • 这些信息可以帮助开发人员了解系统的运行情况,以及后续的故障排查和性能优化。
    • 例如:用户登录、订单创建、支付成功等重要操作的记录。
  4. Debug:

    • 用于开发和调试阶段,记录开发人员在关键处理步骤中的变化情况,便于快速定位问题。
    • 包含详细的调试信息,如对象数据的变化、条件语句的执行结果等。
    • 在生产环境中应该关闭或者限制输出,避免影响系统性能和日志文件大小。
    • 例如:方法的参数值、中间变量的取值、特定条件下的执行路径等。

根据具体情况选择合适的日志级别,以确保日志既能够提供足够的信息用于故障排查和性能分析,又不会造成过多的日志噪音。


打印函数的入参、出参

在日志记录过程中,关键是确保只记录关键有效的信息,而不是把所有信息都记录下来。过多的无效日志会导致日志文件变得庞大,增加了存储和维护的成本,也会增加后续日志分析的难度。

因此,有效日志应该是日志记录中的杀手锏,它们提供了足够的信息用于故障排查、性能分析和业务监控,而不会造成不必要的负担。

举个例子:

public String doBiz(Request req, Integer id){// 记录函数入参log.debug("Entering GetName method. Request: {}", req);// 在打印日志时避免直接打印敏感信息如 uid、traceId,可以考虑在日志配置中做处理,或者在代码中做脱敏处理// 执行业务逻辑String name = "artisan";// 记录函数出参及执行时间log.debug("Exiting GetName method. Result: {}, Execution time: {}ms", name, System.currentTimeMillis());return name;
}

有效日志-------》比如函数的入口处,打印入参,还包括用户唯一标识 (uid)、链路标识 (traceId) 等。函数出口打印返回值及时间等

  1. 函数入参记录

    • 使用log.debug()记录函数的入参时,将整个请求对象req作为参数传入,确保了记录了函数的所有入参信息。
  2. 函数出参及执行时间记录

    • 使用log.debug()记录函数的出参时,打印了方法的返回值name和执行时间。
    • 打印了方法的执行时间,以便于后续性能分析。

这样做的好处是保留了关键有效的日志信息,同时避免了记录过多的日志导致日志文件过大。


打印日志对象要做判空处理,避免阻断流程

通过在日志记录之前进行null检查,可以避免空指针异常的发生,同时在日志中记录了警告信息,表明接收到了空的book对象。这样既确保了程序的健壮性,又不会因为一行简单的日志记录而引发异常。

为了避免这种情况,可以先检查对象是否为null,然后再进行日志记录。

public void doSome(Book book){if (book != null) {log.info("do do and print log: {}", book.getName());} else {log.warn("Received null book object.");}// do something......
}

推荐使用 Slf4j

Slf4j是一种使用门面模式的日志框架,它提供了统一的API接口,可以在不修改代码的情况下,灵活地切换底层的日志实现。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;private static final Logger logger = LoggerFactory.getLogger(Artisan.class);
  • LoggerFactory.getLogger(JavaPub.class)会返回一个与Artisan类相关联的Logger对象,通过这个Logger对象,我们可以记录日志。

通过这种方式,我们可以利用Slf4j的门面模式来记录日志,而无需关心具体的日志实现,从而实现了日志框架的解耦。


不用e.printStackTrace()打印日志

在日志记录中,应避免使用e.printStackTrace()来打印异常信息。这种方式打印的日志包含了完整的堆栈信息,使得日志不够规整,增加了定位问题的难度。同时,如果使用ELK等日志分析工具,处理这种格式的日志也会非常困难。

看个错误的例子

public void doBiz(){try{// 业务代码...} catch (Exception e){e.printStackTrace();}
}

另外,e.printStackTrace()产生的字符串记录的是堆栈信息,如果信息过长过多,会导致字符串常量池所在的内存块溢出,从而使系统请求被阻塞。

推荐做法:

public void doBiz(){try{// 业务逻辑...} catch (Exception e){log.error("程序异常 failed", e);}
}

建议使用日志框架提供的相应方法来记录异常信息,如log.error("程序异常 failed", e)。这样可以将异常信息记录在日志中,方便查看和分析,同时保持日志的规整性和可读性。

在这里插入图片描述


低级别的日志输出,必须进行日志级别开关判断

在低级别的日志输出(如trace、debug)中,必须进行日志级别开关的判断,以避免不必要的资源浪费。这样的开关判断逻辑通常放在日志工具类中。

示例代码:

public void doSomething(){User user = new User(1, "aritsan", "log practice");if (logger.isDebugEnabled()) {logger.debug("print debug log. name is {}", user.getName());}
}

我们通过判断日志级别是否为DEBUG,来决定是否记录DEBUG级别的日志。这样做可以避免在日志级别不符合条件时,执行字符串拼接操作或者执行对象的toString()方法,从而避免不必要的资源浪费。

public void doSth(){String name = "artisan";logger.trace("print debug log" + name);logger.debug("print debug log" + name);logger.info("print info log" + name);// 业务逻辑...
}

这个栗子中没有进行日志级别开关的判断,即使日志级别为WARN时,仍然会执行字符串拼接操作,可能会浪费系统资源。因此,建议在低级别的日志输出中加上日志级别开关判断,以提高系统的性能和效率。

在这里插入图片描述


不打印重复日志

在嵌套逻辑代码中重复打印日志会增加系统资源消耗,因此应避免这种情况的发生。

对于重复的日志,可以直接删除或者将其级别设置为debug,这样就不会在生产环境中打印出这些冗余的信息。

举个例子:

public void doSomething(String s){log.info("do something and print log: {}", s);doSubSomething(s);
}private void doSubSomething(String s){log.debug("do sub something and print log: {}", s);// 写点业务逻辑...
}

这样做可以减少不必要的日志输出,提高系统的性能和效率。


打印全部的异常信息,方便定位问题

在异常处理中,应该打印完整的异常信息,以便更好地定位问题。

看个错误的示例:

public void doBiz(){try{// 业务逻辑...} catch (Exception e){log.error("发生了一个异常");}
}

反例中的代码没有打印具体的异常信息e,这样就无法准确地了解到底发生了什么类型的异常。

建议修改为:

public void doSth(){try{// 业务逻辑...} catch (Exception e){log.error("发生了一个异常", e);}
}

这样做可以打印出完整的异常信息,包括异常类型、异常消息和堆栈信息,有助于更快地定位和解决问题。


核心业务逻辑,在每个分支首行都打印日志

在编写核心业务逻辑代码时,在行首打印日志可以帮助快速排查和定位异常。

public void doBiz(User user){if(user.isVip()){log.info("User is a JavaPub member. Id: {}. Starting processing for member logic.", user.getUserId());// TODO: Member logic}else{log.info("User is not a member. Id: {}. Starting processing for non-member logic.", user.getUserId());// TODO: Non-member logic}
}

通过这样的日志记录方式,可以清晰地了解到程序的执行流程,便于后续的排查和定位异常。


不打印无意义的日志(不携带上下文、日志链路 id)

在编写日志时,确保日志携带有意义的业务信息,这样可以帮助快速定位问题原因。

看个反例: 日志并没有携带任何业务信息,因此对故障排查没有太大的帮助。

public void doBiz(Request req, User user){log.info("do something and print log.  ");// TODO 业务逻辑...
}

正例中的日志携带了业务相关的信息,如用户ID和日志链路ID,这样可以在出现异常时更容易地定位到具体的业务场景,有利于快速解决问题。

public void doBiz(Request req, User user){log.info("do something and print log, id={}, trace_id={}", user.getId(), req.getTraceId());// TODO 业务逻辑...
}

通过在日志中打印关键信息,可以让程序运行过程更加透明,有利于快速定位问题,提高系统的可维护性和可靠性。


日志尽量使用英文

建议在打印日志时尽量使用英文,以避免中文编码与终端不一致导致打印出现乱码,从而影响排查故障的效率。

比如:

log.info("Start processing...");
log.debug("Processing data: {}", data);
log.error("An error occurred while processing data: {}", error);

通过使用英文打印日志,可以确保日志在不同环境中都能正常显示,有利于排查和解决问题。

在这里插入图片描述

这篇关于每日一博 - 关于日志记录的最佳实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/863768

相关文章

grom设置全局日志实现执行并打印sql语句

《grom设置全局日志实现执行并打印sql语句》本文主要介绍了grom设置全局日志实现执行并打印sql语句,包括设置日志级别、实现自定义Logger接口以及如何使用GORM的默认logger,通过这些... 目录gorm中的自定义日志gorm中日志的其他操作日志级别Debug自定义 Loggergorm中的

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

mac安装nvm(node.js)多版本管理实践步骤

《mac安装nvm(node.js)多版本管理实践步骤》:本文主要介绍mac安装nvm(node.js)多版本管理的相关资料,NVM是一个用于管理多个Node.js版本的命令行工具,它允许开发者在... 目录NVM功能简介MAC安装实践一、下载nvm二、安装nvm三、安装node.js总结NVM功能简介N

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

Spring Boot 3 整合 Spring Cloud Gateway实践过程

《SpringBoot3整合SpringCloudGateway实践过程》本文介绍了如何使用SpringCloudAlibaba2023.0.0.0版本构建一个微服务网关,包括统一路由、限... 目录引子为什么需要微服务网关实践1.统一路由2.限流防刷3.登录鉴权小结引子当前微服务架构已成为中大型系统的标

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

Spring Boot整合log4j2日志配置的详细教程

《SpringBoot整合log4j2日志配置的详细教程》:本文主要介绍SpringBoot项目中整合Log4j2日志框架的步骤和配置,包括常用日志框架的比较、配置参数介绍、Log4j2配置详解... 目录前言一、常用日志框架二、配置参数介绍1. 日志级别2. 输出形式3. 日志格式3.1 PatternL