日志规约

305 阅读3分钟

大家应该还记得前段时间log4j2爆发的漏洞,对整个软件行业造成了影响。实际的项目中我们如何去规避或者降低这种风险呢,下面给大家讲解下开发中我们应该掌握的一些日志规约。

规约1:应用中不可直接使用日志系统(Log4j2、Logback)中的 API,而应该使用门面模式的日志框架SLF4J,有利于维护和统一日志处理。

错误示例

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger = LogManager.getLogger(Test.class);

修改为:

日志工厂

public class LogManager
{
    public static Log getLogger(Class<?> clazz)
    {
        Logger logger = LoggerFactory.getLogger(clazz);
        return new Log(logger);
    }
}

封装类

public class Log
{
    private Logger logger;

    public Log(Logger logger)
    {
        this.logger = logger;
    }

    public boolean isDebugEnabled()
    {
        return logger.isDebugEnabled();
    }

    public void debug(String message)
    {
        this.logger.debug(message);
    }

    public void debug(String message, Object... params)
    {
        this.logger.debug(message, params);
    }

    public void debug(String message, Throwable t)
    {
        this.logger.debug(message, t);
    }

    public void info(String message)
    {
        this.logger.info(message);
    }

    public void info(String message, Object... params)
    {
        this.logger.info(message, params);
    }
    
    public void warn(String message, Object... params)
    {
        this.logger.warn(message, params);
    }

    public void info(String message, Throwable t)
    {
        this.logger.info(message, t);
    }

    public void error(Throwable t)
    {
        this.logger.error(null, t);
    }

    public void error(String message)
    {
        this.logger.error(message);
    }

    public void error(String message, Object... params)
    {
        this.logger.error(message, params);
    }

    public void error(String message, Throwable t)
    {
        this.logger.error(message, t);
    }
}

可能会有些疑问,会什么还需要进行封装?采用门面和工厂模式的封装目的是为了屏蔽底层的API,调用者是无需关注。

Spring Boot日志基本使用SLF4J+Logback或者SLF4J+Log4j这两种方式,如果我们遵循相关的规范,即使log4j爆发漏洞,我们只需要将日志SLF4J+Log4j切换为SLF4J+LogBack,无需修改代码,大大的降低了风险。

规约2:logback.xml日志文件中,logger节点中必须设置additivity=false。避免重复打印日志浪费磁盘空间。

 <logger name="request-access" additivity="false" level="INFO">
       <appender-ref ref="CONSOLE"/>
  </logger>

如果additivity没有设置为false,那么日志即会在定义的logger中打印,也会在root中打印。

规约3:对于trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。

示例:

logger.info("getUserName param with name: " + name+ " user: " + user);

说明: 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 user 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。

正例:(占位符) logger.debug("getUserName param with name: {} user : {} ", name, user);

对于不需要打印的日志可以添加级别判断 if (logger.isDebugEnabled()) { logger.debug("getUserName param with name: " + name+ " user: " + user); }

规约4:异常信息应该打印异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

我发现开发中很多同事打印异常日志时,都将异常的信息给丢失了,导致排查问题无法分析。

错误示例:
   try
   {
      getUserByName();
   }
   catch(BusinessException e)
   {
     logger.error("getUserByName error");
   }
   
正确示例:
   try
   {
      getUserByName();
   }
   catch(BusinessException e)
   {
     logger.error("getUserByName error",ExceptionUtil.getMessage(e));
   }

规约5:日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

 <maxHistory>15</maxHistory>

规约6:谨慎地记录日志,生成环境禁止使用debug日志,有选择性的输出info日志,避免在循环中打印日志。

因为大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。

日志相关规约就应该讲解完了,好的规范能够使代码更加高效。