大家应该还记得前段时间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日志,避免在循环中打印日志。
因为大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。
日志相关规约就应该讲解完了,好的规范能够使代码更加高效。