记录日志是任何应用程序中至关重要的一部分,它可以帮助开发人员了解应用程序的行为、调试问题以及监控系统的健康状态。
根据具体情况选择合适的日志级别,以确保日志既能够提供足够的信息用于故障排查和性能分析,又不会造成过多的日志噪音。
在日志记录过程中,关键是确保只记录关键有效的信息,而不是把所有信息都记录下来。过多的无效日志会导致日志文件变得庞大,增加了存储和维护的成本,也会增加后续日志分析的难度。
因此,有效日志应该是日志记录中的杀手锏,它们提供了足够的信息用于故障排查、性能分析和业务监控,而不会造成不必要的负担。
举个例子:
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) 等。函数出口打印返回值及时间等
log.debug()
记录函数的入参时,将整个请求对象req
作为参数传入,确保了记录了函数的所有入参信息。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是一种使用门面模式的日志框架,它提供了统一的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()
来打印异常信息。这种方式打印的日志包含了完整的堆栈信息,使得日志不够规整,增加了定位问题的难度。同时,如果使用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
}
}
通过这样的日志记录方式,可以清晰地了解到程序的执行流程,便于后续的排查和定位异常。
在编写日志时,确保日志携带有意义的业务信息,这样可以帮助快速定位问题原因。
看个反例: 日志并没有携带任何业务信息,因此对故障排查没有太大的帮助。
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);
通过使用英文打印日志,可以确保日志在不同环境中都能正常显示,有利于排查和解决问题。