一、引言
在日常系统业务开发中,日志记录是不可或缺的一部分,不仅帮助我们调试和排查问题,还能提供系统的运行状态和健康状况。然而,配置使用不当可能会对性能产生负面影响。在我们的项目中,服务耗时优化是提升系统性能的关键任务。在压测过程中,我们发现单机 QPS 存在明显的性能瓶颈,进一步分析表明,平常看似不起眼的日志打印实际上对性能产生了不小的影响。本文将探讨 Logback 日志打印对性能的影响,并提供一些优化建议。
二、日志记录的基本原理
日志消息打印的过程包括以下几个步骤:
2.1 获取 Logger
调用 org.slf4j.LoggerFactory 类的 getLogger 方法,即可获取 Logger 对象
Logger log = LoggerFactory.getLogger("HelloWord");
2.2 调用 logger.info 打印方法
log.info("NewIt");
2.2.1 取得过滤链的判定结果
调用log.info()方法,实际调用的是filterAndLog_1方法,第一步会进行TurboFilter过滤,可以在早期阶段决定是否丢弃日志事件,从而提高性能和减少不必要的日志处理开销。它支持根据日志级别、MDC 数据、标记等多种条件进行灵活的过滤。
2.2.2 检查日志级别
对 Logger 的有效级别与日志请求级别进行比较。如果请求级别数值小于有效级别,本次请求将 被禁用,Logback 会直接跳过本次请求
private void filterAndLog_1(final String localFQCN, final Marker marker, final Level level, final String msg, final Object param, final Throwable t) {
/**
* 这里先经过一串过滤器处理,根据传入的marker满足的条件不同返回不同的结果,
*/
final FilterReply decision = loggerContext.getTurboFilterChainDecision_1(marker, this, level, msg, param, t);
/**
* 过滤后返回NEUTRAL则进一步判断当前想要打印的日志等级
* 如果小于有效日志级别,则表示该日志无效,直接返回,不做具体输出动作。
*/
if (decision == FilterReply.NEUTRAL) {
if (effectiveLevelInt > level.levelInt) {
return;
}
} else if (decision == FilterReply.DENY) {
return;
}
buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);
}
2.2.3 创建 LoggingEvent 对象
如果日志级别允许,Logback 会构建一个 LoggingEvent 对象,该对象包含所有与请求相关的参数,如请求用的 logger、请求级别、日志消息、请求携带的异常、当前时间、当前线程、MDC信息等等。
private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
le.setMarker(marker);
callAppenders(le);
}
2.2.4 调用 appenders 的 doAppend() 方法
在创建 LoggingEvent 对象以后,logback 将调用所有可用 appender 的 doAppend()方法,进行日志输出。
常见的 Appender 包括:
ConsoleAppender:将日志输出到控制台
FileAppender:将日志输出到文件
RollingFileAppender:将日志输出到文件,并支持日志滚动
SMTPAppe