从零构建生产级日志分析系统:Flask + Docker + Nginx 完整实战
又到周末了,使用 claude 做点东西的心思又来了!那就找找工作中的痛点问题。
我们有个Java+Tomcat的 老项目,平时查日志只能去服务器上用命令来看,一点也不方便(后面新做的项目,很多框架里都自带了日志功能。还有的项目是用的 ElasticSearch)。
那就趁着周末,动手做一个吧!
我有一个Tomcat日志文件,平时用命令行查起来很不方便,想着可以做一个可视化的系统
如果不用ElasticSearch,自己用AI写一个可视化的,你觉得靠谱吗
我服务器是CentOS,上面有docker,最新日志是
/home/tomcatlog/jkwh_app_out.log,今天之前的日志会自动按日期生成目录,归纳到/home/tomcatlog/backuplog
下,如/logs_20251017/jkwh_app_out.log.1
和CHATGPT几轮沟通后,一个基本的方案就出来了!
🧩 一、你当前日志结构说明
当前正在写入的实时日志 └── backuplog/├── logs_20251017/jkwh_app_out.log.1├── logs_20251016/jkwh_app_out.log.1├── logs_20251015/jkwh_app_out.log.1└── ... (按日期归档) ```✅ 非常标准,实时日志 + 历史归档。---## 🧠 二、目标系统设计我们做一个 Docker 应用,支持两个功能:| 模块 | 功能描述 | | :----- | :----------------------------------------------------- | | 实时日志监控 | 通过 WebSocket 实时读取 `/home/tomcatlog/jkwh_app_out.log` | | 历史日志浏览 | 自动列出 `/home/tomcatlog/backuplog` 下的日期目录,点击即可加载对应日志文件内容 |---## ⚙️ 三、目录映射设计容器内目录映射:| 主机路径 | 容器内路径 | 作用 | | :--------------------------------- | :---------------------- | :----- | | `/home/tomcatlog/jkwh_app_out.log` | `/app/logs/current.log` | 实时日志 | | `/home/tomcatlog/backuplog` | `/app/backuplog` | 历史日志目录 |---## 🧩 四、项目结构(完整方案)```log-visualizer/ ├─ app.py # Flask + SocketIO 后端 ├─ templates/ │ └─ index.html # 前端页面 ├─ requirements.txt └─ Dockerfile ```---### 🧾 requirements.txt```flask flask-socketio watchdog ```---### ⚙️ app.py```python from flask import Flask, render_template, jsonify, send_file, request from flask_socketio import SocketIO import os, time, threadingapp = Flask(__name__) socketio = SocketIO(app, cors_allowed_origins="*")CURRENT_LOG = "/app/logs/current.log" BACKUP_DIR = "/app/backuplog"def tail_current_log():"""持续读取实时日志"""if not os.path.exists(CURRENT_LOG):print(f"Log file not found: {CURRENT_LOG}")returnwith open(CURRENT_LOG, "r", encoding="utf-8", errors="ignore") as f:f.seek(0, os.SEEK_END)while True:line = f.readline()if line:socketio.emit("log_line", {"data": line})else:time.sleep(0.2)@app.route("/") def index():return render_template("index.html")@app.route("/api/backups") def list_backups():"""列出所有日期目录"""dirs = sorted(os.listdir(BACKUP_DIR), reverse=True)return jsonify([d for d in dirs if d.startswith("logs_")])@app.route("/api/view_log") def view_log():"""查看指定日期的日志"""date_dir = request.args.get("date")log_path = os.path.join(BACKUP_DIR, date_dir, "jkwh_app_out.log.1")if not os.path.exists(log_path):return "Log not found", 404return send_file(log_path, mimetype="text/plain")if __name__ == "__main__":threading.Thread(target=tail_current_log, daemon=True).start()socketio.run(app, host="0.0.0.0", port=8080) ```---### 💻 templates/index.html```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Tomcat 日志监控系统</title> <script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script> <style>body { font-family: monospace; background: #111; color: #ddd; margin: 0; }#log { height: 80vh; overflow-y: scroll; padding: 10px; white-space: pre-wrap; background: #000; }.toolbar { background: #222; padding: 10px; }select { padding: 5px; } </style> </head> <body> <div class="toolbar"><label>查看历史日志:</label><select id="dateSelect"></select><button onclick="loadHistory()">加载</button> </div> <pre id="log"></pre> <script>const log = document.getElementById("log");const socket = io();socket.on("log_line", msg => {log.textContent += msg.data;log.scrollTop = log.scrollHeight;});async function loadHistory() {const date = document.getElementById("dateSelect").value;const res = await fetch("/api/view_log?date=" + date);log.textContent = await res.text();}async function loadDates() {const res = await fetch("/api/backups");const dates = await res.json();const sel = document.getElementById("dateSelect");dates.forEach(d => {const opt = document.createElement("option");opt.value = d; opt.textContent = d;sel.appendChild(opt);});}loadDates(); </script> </body> </html> ```---### 🐳 Dockerfile```dockerfile FROM python:3.10-slim WORKDIR /app COPY . . RUN pip install -r requirements.txt EXPOSE 8080 CMD ["python", "app.py"] ```---## 🚀 五、部署命令```bash cd /opt/log-visualizer docker build -t log-viewer . docker run -d \ --name log-viewer \ -p 8080:8080 \ -v /home/tomcatlog/jkwh_app_out.log:/app/logs/current.log \ -v /home/tomcatlog/backuplog:/app/backuplog \ --restart always \ log-viewer ```---## 🌐 六、访问系统浏览器访问:```http://<服务器IP>:8080 ```✅ 页面上自动显示实时日志 ✅ 可以选择历史日期查看归档日志内容
md 文件直接让 claude 去读取!于是就有了本文!
本文将带你从零构建一个功能完整的 Web 日志分析系统,支持实时监控、高级搜索、上下文查看、数据导出等专业功能。
技术栈: Python Flask + SocketIO + Docker + Nginx + JavaScript
适用场景: Tomcat 日志、应用日志、系统日志等各类文本日志分析
🎯 项目需求分析
业务需求
- 实时监控 - 类似
tail -f
,自动推送最新日志 - 历史查询 - 查询归档日志,支持按日期检索
- 高级搜索 - 关键词、正则、日志级别、时间范围过滤
- 上下文查看 - 点击日志行,查看前后 15 行上下文
- 数据导出 - 导出搜索结果为 TXT/CSV
- 快速过滤 - 一键提取异常、IP、SQL、敏感信息
- 双版本 - 标准版(简洁)+ 专业版(功能全)
非功能需求
- 安全性 - IP 白名单访问控制
- 性能 - 支持大文件(GB 级)日志分析
- 易部署 - Docker 一键部署
- 可扩展 - 支持 Nginx 反向代理、子路径部署
🏗️ 系统架构设计
整体架构
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ 浏览器 │──────│ Nginx 反向 │──────│ Docker │
│ │ HTTP │ 代理 │ 8080 │ Container │
└─────────────┘ └──────────────┘ └─────────────┘│┌───────┴────────┐│ Flask App ││ + SocketIO │└───────┬────────┘│┌───────┴────────┐│ 日志文件 ││ (只读挂载) │└────────────────┘
技术选型理由
组件 | 技术选型 | 理由 |
---|---|---|
后端框架 | Flask | 轻量级、易扩展、文档完善 |
实时推送 | Flask-SocketIO | 支持 WebSocket,实时性好 |
前端 | 原生 JS | 无需打包构建,部署简单 |
容器化 | Docker | 环境隔离,一键部署 |
反向代理 | Nginx | 高性能、支持 HTTPS、负载均衡 |
💻 核心功能实现
1. 实时日志推送(WebSocket)
后端实现
from flask_socketio import SocketIOapp = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')def tail_current_log():"""后台线程:监控日志文件变化,实时推送"""with open(CURRENT_LOG, 'r', encoding='utf-8', errors='ignore') as f:f.seek(0, 2) # 移动到文件末尾while True:line = f.readline()if line:parsed = parse_log_line(line)socketio.emit('log_line', {'data': line.rstrip('\n'),'parsed': parsed})else:time.sleep(0.1)# 启动后台线程
threading.Thread(target=tail_current_log, daemon=True).start()
前端实现
const socket = io(APP_PREFIX || '/');socket.on("log_line", msg => {if (currentMode === 'realtime') {appendLogLine(msg.parsed, msg.data);if (autoScroll) {log.scrollTop = log.scrollHeight; // 自动滚动到底部}}
});
关键技术点:
f.seek(0, 2)
移动到文件末尾,只读取新增内容daemon=True
设置为守护线程,主程序退出时自动关闭- 前端自动滚动到底部,保持
tail -f
体验
2. 高级搜索功能
支持的搜索条件
def search_in_file(file_path, keyword=None, level=None,start_time=None, end_time=None,regex=False, max_lines=1000, context_lines=0):"""多条件组合搜索"""results = []with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:for idx, line in enume