从 Nginx 日志到访问分析:轻量化网站监测服务的架构实践
技术主题:日志采集、Python 服务、SQLite/MySQL、Linux 部署
目前系统服务已开源。本文围绕源码中的采集、存储、查询与部署机制展开,说明设计取舍及适用边界。
一、网站统计为什么还需要服务器日志

一个网站的真实请求并不都来自浏览器中的普通用户。搜索引擎抓取、命令行工具、失效链接、接口请求和路径扫描都会经过服务器。页面脚本擅长记录交互行为,服务器访问日志则能覆盖已经到达入口层的请求。两类数据观察的对象不同,不能简单互相替代。
对于自建官网、内容站和文档站,先把现有 Nginx 日志整理成可查询的数据,往往就能回答一批实际问题:哪些页面访问较多,流量从哪里进入,某个 IP 为什么持续返回错误,以及统计服务本身是否还在正常采集。
本系统选择单进程服务架构:一个 Python 服务负责日志读取、查询 API、管理页面和地区库更新,默认使用 SQLite,也可接入 MySQL。目标是减少部署组件,使规模较小的站点能够在可理解、可维护的前提下建立访问分析能力。
二、划清入口、采集与查询的职责
网站访问 → Nginx → JSON 日志
↓
增量采集与字段整理
↓
SQLite / MySQL
↓
查询 API 与缓存
↓
访问分析页面 / 地球大屏
Nginx 负责处理网站请求和记录日志,监测进程读取落盘结果。统计查询不进入网站业务请求的同步链路,因此采集延迟不会直接变成用户打开网页时的等待时间。不过,两者仍共享服务器资源,必须通过并发、内存、磁盘和日志保留限制避免相互争抢。
公网入口继续由 Nginx 提供 HTTPS。监测服务默认监听本机 4188 端口,通过 /monitor/ 路径反向代理访问。这样可以复用已有证书和域名,不需要再开放一个独立公网应用端口。
三、先规范日志,再谈统计准确性
日志解析最怕格式漂移。将一行文本按空格拆分,看起来容易,但遇到带引号的客户端标识、空来源、特殊字符和不同时间格式时就会变得脆弱。JSON 日志让字段边界明确,解析失败也更容易定位。
下面是精简的 Nginx 日志配置示例,实际部署应使用安装包中的完整模板。log_format 位于 http 配置层,access_log 则放入需要统计的网站 server 配置中。
log_format access_json escape=json
'{"time":"$time_iso8601","remote_addr":"$remote_addr",'
'"request_id":"$request_id","method":"$request_method",'
'"uri":"$request_uri","status":$status,'
'"referer":"$http_referer","user_agent":"$http_user_agent"}';
access_log /var/log/nginx/access.json access_json;
如果网站经过 CDN 或反向代理,必须先在可信代理范围内恢复真实客户端 IP。不能无条件相信任意请求传来的转发头,否则统计中的访客 IP 可能被伪造。时间字段同样重要:原始记录要保留时区,统计日期使用配置的业务时区,避免跨午夜的请求被归到错误日期。
四、增量读取与重复控制

日志持续增长,采集器不应每轮重新扫描全部历史内容。本系统保存文件读取位置,从上次处理结束处继续;对还没有写完整的一行,等待后续内容完成后再解析。重启后可以利用保存的进度继续工作。
当前实现对普通文件每轮最多处理 5,000 个完整行,每轮扫描最多 128 个匹配文件,按 500 条分批写入。这样的限制把单轮工作量控制在可预期范围内;当新增日志速度超过处理速度时,应关注积压和采集延迟,而不是只观察进程是否存活。
请求标识用于抑制重复导入。需要注意,偏移续读加去重并不自动等于严格的"恰好一次"交付。日志轮转、截断、进程中断和磁盘错误都应纳入验证,尤其要检查轮转前后的记录是否丢失或重复。压缩历史日志默认不导入,避免首次启动突然承担大量解压工作。

五、SQLite 与 MySQL 如何选择
SQLite 的优势是组件少,应用可以直接管理本地数据库文件。对于单实例、有限写入量的日志分析服务,它能显著降低安装和日常维护成本。备份时应使用数据库支持的备份流程,不能简单复制一个仍在写入的主文件并假定数据完整。
MySQL 适合已经具备数据库账户、备份、监控和容量管理流程的团队。本系统通过 PyMySQL 适配 MySQL 8,但切换数据库后端不会自动搬迁历史数据。应使用空库重新导入保留日志,或者设计并验证专门的数据迁移流程。
两种模式都要求每个数据库只运行一个采集进程。当前版本没有多采集节点协调机制,不应通过随意增加实例来扩容。更高吞吐量场景需要重新评估队列、写入分区和汇总表设计。
六、查询接口与前端数据消费
管理页面通过 JSON API 获取汇总、趋势、来源和请求明细。默认配置使用 4 个请求工作线程及 30 秒查询缓存,并限制缓存条目数量。缓存能减少重复查询,但意味着页面显示与最新日志之间可能存在短暂时间差。
# 使用实际域名和管理员账号,密码由 curl 交互输入
curl --fail --user YOUR_ADMIN_USER \
'https://YOUR_DOMAIN/monitor/api/trend?days=14'
# 请求明细采用分页参数
curl --fail --user YOUR_ADMIN_USER \
'https://YOUR_DOMAIN/monitor/api/requests?limit=100&offset=0'
管理员认证由应用负责,公网必须使用 HTTPS。独立 IP 不能被解释为独立访客,全部请求也不能被解释为页面浏览。来源缺失可能由收藏夹、二维码、应用跳转或浏览器隐私策略造成,因此"直接访问"并不意味着用户一定手动输入了网址。
七、Linux 部署与验收
原生部署使用 Python 3.11+ 与 systemd,容器部署使用 Docker Compose。前端资源随应用提供,不依赖运行时 Node.js 构建。默认使用本机监听地址,通过已有 Nginx 转发请求;systemd 环境文件或 Compose 配置负责注入账号、日志路径和数据库参数。
YMING_BIND=127.0.0.1
YMING_PORT=4188
YMING_BASE_PATH=/monitor/
YMING_DB_BACKEND=sqlite
YMING_RETENTION_DAYS=30
YMING_WORKERS=4
YMING_CACHE_SECONDS=30
验收应包含四个环节:发出已知测试请求并核对记录;验证认证失败时接口不会返回统计数据;模拟日志轮转并核对续读;恢复一份备份并验证可查询。只有界面能打开,不能证明采集链路和恢复流程已经可用。
八、当前验证范围与后续演进
当前候选版本已通过 22 项 SQLite 与地区库测试、6 项隔离 MySQL 集成测试,以及脚本语法检查。Linux 整机安装、Docker 运行和 ARM64 尚未完成端到端验证,不能据此宣称支持全部发行版或任意流量规模。
后续扩展可以围绕查询耗时、采集积压和恢复时间展开。先用真实日志识别瓶颈,再决定是否引入预聚合、独立采集队列或数据库分区,比单纯增加部署组件更容易维护。一个可靠的访问分析系统,既需要展示统计结果,也需要让运维人员判断这些结果是否完整、及时和可追溯。