示例说明
tail -f 只跟一个 inode,日志轮转重建文件后就傻等旧句柄;-F 会重新打开同名文件继续跟——生产日志必然轮转,这是硬区别。grep 默认按块缓冲,流式场景必须 –line-buffered:每行即时通过,延迟从分钟级降到毫秒。整个链路 tail | grep | while 组成数据流管道,每段都流式化才不积压。
技术要点
- -F 跟随轮转重建的文件
- 行缓冲让过滤延迟降到毫秒级
- 信号陷阱保证子进程一并停止
代码解读
stream_logs 的三段管道:tail 带五十行历史起步、grep 按扩展正则过滤、while 循环按行分色渲染——ERROR 红、WARN 黄、Traceback 紫,肉眼分级。主循环的 running 标志由 INT TERM 陷阱翻转,kill -0 探测管道存活性,退出时杀掉后台管道并 wait 回收,不留孤儿 tail。
使用建议
多文件跟随直接给 tail 传多个路径,输出带文件名头。复杂过滤换 awk 或落 jq。生产排障建议配合 timestamps 与上下文行选项。
代码示例
#!/usr/bin/env bash
set -euo pipefail
readonly LOG_FILE="${1:-/var/log/app/application.log}"
readonly PATTERNS='ERROR|WARN|Traceback'
running=true
on_term() { running=false; }
trap on_term INT TERM
stream_logs() {
# -F 处理轮转:文件被重建后继续跟随
tail -F --lines=50 "${LOG_FILE}" 2>/dev/null \
| grep --line-buffered -E "${PATTERNS}" \
| while IFS= read -r line; do
case "$line" in
*ERROR*) printf '\033[31m%s\033[0m\n' "$line" ;;
*WARN*) printf '\033[33m%s\033[0m\n' "$line" ;;
*Traceback*) printf '\033[35m%s\033[0m\n' "$line" ;;
esac
done
}
[[ -f "${LOG_FILE}" ]] || { echo "日志文件不存在: ${LOG_FILE}" >&2; exit 1; }
echo "跟随 ${LOG_FILE} 中的 ${PATTERNS},Ctrl-C 退出"
stream_logs &
readonly TAIL_PID=$!
while $running; do
sleep 1
kill -0 "${TAIL_PID}" 2>/dev/null || running=false
done
kill "${TAIL_PID}" 2>/dev/null || true
wait "${TAIL_PID}" 2>/dev/null || true
echo "日志流已停止"