定位 Tomcat 日志中的慢查询
一 明确日志来源与含义
- 访问日志 AccessLog:默认位于 $CATALINA_HOME/logs/localhost_access_log.YYYY-MM-DD.txt,记录每一次 HTTP 请求。若已启用 AccessLogValve 的 %D(毫秒)或 %T(秒)字段,可直接看到请求耗时,用于定位“慢请求”。未显式配置时,该日志通常不含耗时字段。
- 应用日志:业务代码或框架(如 log4j2、slf4j)若打印了 SQL 执行时间或调用耗时,可在 catalina.out、localhost..log 中检索关键字(如 QTime、sql、execute、elapsed)来发现慢操作。
- 数据库慢查询日志:若耗时主要在数据库,需开启数据库自身的慢查询日志(如 MySQL slow query log),再用 pt-query-digest 等工具分析;Tomcat 侧日志只能间接反映“总耗时”。
- 日志轮转与路径:常见路径为 /var/log/tomcat/ 或 $CATALINA_HOME/logs/;按日期轮转的文件便于分时段排查。
二 命令行快速定位
- 访问日志中筛选“慢请求”(示例阈值 800 ms)
- 字段为 %D(毫秒)时:
- Linux/GNU:
grep ‘QTime=’ catalina.out | awk -F’QTime=’ ‘{n=$2; gsub(/[^0-9.]/,“”,n); if(n+0>800) print}’
- macOS/BSD(含毫秒的十进制秒):
grep ‘QTime=’ catalina.out | awk -F’QTime=’ ‘{n=$2; gsub(/[^0-9.]/,“”,n); if(n+0>0.8) print}’
- 字段为 %T(秒)时:
grep ‘QTime=’ catalina.out | awk -F’QTime=’ ‘{n=$2; gsub(/[^0-9.]/,“”,n); if(n+0>0.8) print}’
- 按时间窗口提取日志(便于聚焦高峰时段)
sed -n ‘/2025-03-07 10:00:00/,/2025-03-07 12:00:00/p’ catalina.out > window.log
- 实时观察新增慢请求
tail -f catalina.out | grep --line-buffered ‘QTime=’ | awk -F’QTime=’ ‘{n=$2; gsub(/[^0-9.]/,“”,n); if(n+0>800) print}’
- 说明
- 上述示例以 QTime 为例;若你的访问日志使用不同字段名(如 rt、elapsed),替换相应关键字即可。
- 若日志中 QTime 与数值间存在空格或引号,请相应调整 awk -F 的分隔符与清洗逻辑。
三 使用可视化与聚合分析
- 集中化与检索:将 catalina.out、localhost_access_log 等接入 ELK(Elasticsearch/Logstash/Kibana) 或 Graylog,用 Kibana 建立以 QTime/rt/elapsed 为指标的直方图与 TopN 统计,快速定位峰值与异常 URL、IP、UA。
- 数据库慢查询:对 MySQL 开启慢查询日志并用 pt-query-digest 生成报告,识别高影响 SQL、缺失索引与执行计划问题;配合 Tomcat 侧日志的 traceId/requestId 可串联全链路耗时。
四 常见排查路径与优化建议
- 关联定位:在 Tomcat 访问日志中定位到“慢 URL + 时间点”,再到应用日志检索同一时间附近的 SQL/调用栈,必要时开启更详细日志(如 FINE)或使用 MDC 输出 traceId 串联。
- 数据库侧:检查 连接池 配置(最大连接、超时)、执行计划与索引;对识别出的慢 SQL 使用 EXPLAIN 优化,必要时通过 pt-query-digest 持续跟踪改进效果。
- 代码与架构:减少阻塞与循环内复杂计算,合理使用 缓存 与 异步 处理,降低单请求在 Tomcat 与数据库两侧的停留时间。
五 一键脚本示例
- 用途:从 catalina.out 中筛选 QTime > 阈值(毫秒) 的访问日志,并高亮耗时与 URL。
- 用法:./find_slow.sh 800(单位:毫秒)
- 脚本内容:
#!/usr/bin/env bash
set -euo pipefail
LOG="${1:-catalina.out}"
THRESHOLD_MS="${2:-800}"
if [[ ! -f "$LOG" ]]; then
echo "Usage: $0 <logfile> [threshold_ms]"
exit 1
fi
echo "=== QTime > ${THRESHOLD_MS}ms in $LOG ==="
grep -E 'QTime[=:][[:space:]]*"?[0-9]+' "$LOG" | while IFS= read -r line; do
ms=$(echo "$line" | sed -E 's/.*QTime[=:][[:space:]]*"?([0-9]+(\.[0-9]+)?).*/\1/')
if awk "BEGIN{exit !($ms < 100)}"; then
ms=$(awk "BEGIN{printf \"%.0f\", $ms * 1000}")
fi
if awk "BEGIN{exit !($ms > $THRESHOLD_MS)}"; then
echo "$ms ms -> $line"
fi
done
- 提示:若你的访问日志使用 %T(秒)或自定义字段名,请替换脚本中的 QTime 与数值提取正则。