You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flask中使用AJAX实现模拟tail命令的实时日志查看功能

方案可行性结论

你这套轮询+Range请求拉增量的思路完全可行,内部场景用足够简单稳定,没必要上WebSocket、SSE这类更复杂的技术,现有代码只要补几个逻辑漏洞就能用。

你原来的代码有三个核心问题:

  • 初始byteRead=0会直接加载整个日志文件,不符合“先读末尾N行”的需求
  • 前端自行计算字节偏移容易因为编码、响应截断出问题,也没处理日志轮转(logrotate切分后文件大小重置,偏移量超过文件长度会读不到新内容)
  • Flask默认不会自动处理Range请求返回分段内容,需要后端自己实现对应逻辑
后端Flask实现逻辑

直接按下面的规则写接口就行,核心是支持Range分段请求、初始请求返回末尾指定行数、自动处理日志轮转场景:

from flask import Flask, request, make_response
import os

app = Flask(__name__)
# 替换成你的实际日志文件路径
LOG_FILE_PATH = "/path/to/your/runtime.log"
# 初始加载默认读最后200行,可自行调整
DEFAULT_TAIL_LINE_COUNT = 200

def read_file_tail(file_path, line_num):
    """从后往前读文件,返回最后N行的二进制内容、当前文件总字节数"""
    with open(file_path, "rb") as f:
        f.seek(0, os.SEEK_END)
        file_total_size = f.tell()
        read_block_size = 1024
        block_cache = []
        matched_line_count = 0

        # 逐块从后往前读,直到凑够要求的行数或者读完整个文件
        while matched_line_count < line_num and f.tell() > 0:
            current_pos = max(0, f.tell() - read_block_size)
            f.seek(current_pos)
            block_content = f.read(read_block_size)
            block_cache.append(block_content)
            matched_line_count += block_content.count(b"\n")
            f.seek(current_pos)
        
        full_content = b"".join(reversed(block_cache))
    # 截取最后N行,避免多返回内容
    tail_lines = full_content.splitlines()[-line_num:]
    return b"\n".join(tail_lines) + b"\n", file_total_size

@app.route("/api/log-view")
def get_log_content():
    range_header = request.headers.get("Range", "")
    current_file_size = os.path.getsize(LOG_FILE_PATH)

    if range_header.startswith("bytes="):
        # 增量拉取请求
        start_byte = int(range_header.split("=")[1].split("-")[0])
        # 日志轮转判断:如果请求偏移大于当前文件大小,说明文件被切了,重置偏移
        if start_byte >= current_file_size:
            start_byte = 0
        with open(LOG_FILE_PATH, "rb") as f:
            f.seek(start_byte)
            resp_content = f.read()
        resp = make_response(resp_content)
        resp.status_code = 206
        resp.headers["Content-Range"] = f"bytes {start_byte}-{current_file_size-1}/{current_file_size}"
        resp.headers["X-Next-Read-Byte"] = str(start_byte + len(resp_content))
    else:
        # 首次进入的请求,返回末尾N行
        resp_content, current_file_size = read_file_tail(LOG_FILE_PATH, DEFAULT_TAIL_LINE_COUNT)
        resp = make_response(resp_content)
        resp.headers["X-Next-Read-Byte"] = str(current_file_size)
    
    resp.headers["Content-Type"] = "text/plain; charset=utf-8"
    return resp

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
前端JS调整方案

别在前端自己算字节偏移,直接用后端返回的响应头里的下一次读取偏移,避免算错。页面加载完先拉一次初始内容,之后定时轮询拉增量,加个自动滚动到底部的逻辑就行。
内网环境记得把jQuery下载到本地静态目录引用,不要用公网CDN

<!-- 日志展示容器,固定高度加滚动条 -->
<div id="log-box" style="height: 85vh; overflow-y: auto; background: #111; color: #eee; padding: 12px; font-family: Consolas, monospace; font-size: 13px; line-height: 1.5;"></div>

<!-- 本地引入jQuery -->
<script src="/static/jquery.min.js"></script>
<script>
let nextReadPos = 0;
const logBox = $("#log-box");

// 追加新日志内容
function appendNewLog(text) {
    if (!text) return;
    logBox.append(document.createTextNode(text));
    // 自动滚动到最底部
    logBox.scrollTop(logBox[0].scrollHeight);
}

// 拉取日志
function pullLog() {
    const requestHeaders = {};
    if (nextReadPos > 0) {
        requestHeaders["Range"] = `bytes=${nextReadPos}-`;
    }
    $.ajax({
        type: "GET",
        url: "/api/log-view",
        dataType: "text",
        headers: requestHeaders,
        success: function(res, _, xhr) {
            appendNewLog(res);
            // 从响应头取下一次读取的位置
            const nextPos = xhr.getResponseHeader("X-Next-Read-Byte");
            if (nextPos) {
                nextReadPos = parseInt(nextPos);
            }
        },
        error: function() {
            // 请求失败时延长重试间隔,避免打满服务端
            setTimeout(pullLog, 3000);
            return;
        }
    });
}

// 页面加载完成后初始化
$(function() {
    // 先拉一次初始内容
    pullLog();
    // 之后每秒拉一次增量,可根据日志量调整间隔
    setInterval(pullLog, 1000);
});
</script>
可选优化
  • 轮询间隔按需调整,日志产出频繁可以设成500ms,量小设成2s也没问题
  • 可以加个“暂停滚动”开关,翻历史日志的时候暂停自动滚动和拉取
  • 如果日志带ANSI颜色码,前端加个简单的解析逻辑就能保留彩色日志效果
  • 并发访问量高的话可以给日志接口加个100ms的短缓存,避免重复读文件

内容的提问来源于stack exchange,提问作者y0sh1da

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 04:31:04