适用场景:为什么要把访问日志单独治理
个人 Flask 博客上线一段时间后,最容易被忽略的不是功能,而是 Nginx access log。它每天记录搜索引擎抓取、404、后台登录、静态资源命中、慢请求和异常来源;如果只在事故后临时 tail -f,很多信号已经被滚动日志冲掉。Stepnex 这类独立博客在百度网盟审核冲刺期更需要稳定、集中、可解释的站点质量信号:页面能正常访问,404 有人处理,爬虫抓取没有被误伤,慢请求能定位到 URL,后台入口没有异常爆破。
这篇文章不讨论大型日志平台,而是给一台云服务器上的 Flask + Gunicorn + Nginx 博客补一套轻量流程:先定义结构化日志格式,再设置按站点拆分的 access log,之后用脚本每天生成 404、5xx、慢请求、爬虫访问和可疑后台请求清单。它和站内已有的 Flask 博客生产健康检查 配合使用:健康检查回答“现在是否可用”,访问日志回答“过去一天发生了什么”。
技术取舍:先可读,再自动化
小站不要一开始就上复杂链路。第一阶段只保留 Nginx 原生日志、logrotate 和一个 Python 汇总脚本,优点是依赖少、迁移简单、出问题时可以直接回到原始文本。第二阶段再把汇总结果发到邮件、企业微信或管理后台。不要把所有请求都写进数据库,也不要把完整 IP、User-Agent、Referer 长期外发到第三方;日志治理是为了排查和质量复盘,不是为了堆积敏感数据。
日志格式建议用 escape=json,每行一个 JSON 对象,字段控制在排查必需范围:时间、真实 IP、请求方法、URI、状态码、耗时、上游耗时、响应大小、Referer、User-Agent、请求 ID。Nginx 官方文档说明 log_format 支持 JSON 转义,access_log 可以指定格式、缓冲和条件记录;这些能力足够支撑小站的每日检查。
步骤一:配置 Nginx JSON 访问日志
把格式放在 http 块中,站点日志放在对应 server 块中。下面的配置片段保留了排查 Flask 博客最常用的字段,并且把后台、上传、文章页和静态资源都记录到同一个站点文件,便于按 URI 聚合。
http {
log_format stepnex_json escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"real_ip":"$http_x_forwarded_for",'
'"request":"$request",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"bytes":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"referer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
server {
server_name stepnex.cn www.stepnex.cn;
access_log /var/log/nginx/stepnex.access.json.log stepnex_json buffer=64k flush=5s;
error_log /var/log/nginx/stepnex.error.log warn;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
改完后先做语法检查和重载,不要直接重启:
sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 3 /var/log/nginx/stepnex.access.json.log
验证方式很简单:访问首页、任意文章页、一个不存在的地址和后台登录页,再确认日志里出现对应 URI、状态码和 request_time。如果日志没有生成,先看 nginx -T | grep stepnex_json -n,确认配置文件实际被加载。
步骤二:设置滚动和保留周期
访问日志会增长很快,必须配置 logrotate。小站可以保留 14 到 30 天的明细,再把每日汇总结果保留更久。示例配置如下:
/var/log/nginx/stepnex.access.json.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
create 0640 www-data adm
sharedscripts
postrotate
[ -s /run/nginx.pid ] && kill -USR1 `cat /run/nginx.pid`
endscript
}
执行 sudo logrotate -d /etc/logrotate.d/stepnex-nginx 做演练,确认没有权限错误。正式运行后第二天检查 .1 或 .gz 文件是否出现。避坑点是不要用 copytruncate 作为默认方案;Nginx 支持重新打开日志文件,发送 USR1 更符合常规做法。另一个坑是日志目录权限,create 的用户组要匹配 Nginx worker 能写入、运维用户能读取的边界。
步骤三:写每日检查脚本
脚本只读昨天或最近 24 小时日志,输出 Markdown 报告。核心检查包括:状态码分布、Top 404、Top 5xx、超过 1 秒的慢请求、百度和 Google 爬虫访问次数、后台路径访问、上传路径异常请求、Referer 异常来源。
import json
from collections import Counter, defaultdict
from pathlib import Path
log_path = Path("/var/log/nginx/stepnex.access.json.log")
status = Counter()
not_found = Counter()
slow = []
bots = Counter()
admin_hits = Counter()
for line in log_path.read_text(encoding="utf-8", errors="ignore").splitlines():
try:
row = json.loads(line)
except json.JSONDecodeError:
continue
code = int(row.get("status") or 0)
uri = row.get("uri") or "-"
ua = (row.get("user_agent") or "").lower()
rt = float(row.get("request_time") or 0)
status[code] += 1
if code == 404:
not_found[uri] += 1
if code >= 500:
slow.append((rt, code, uri))
if rt >= 1.0:
slow.append((rt, code, uri))
if "baiduspider" in ua:
bots["baidu"] += 1
if "googlebot" in ua:
bots["google"] += 1
if uri.startswith("/admin") or uri.startswith("/api/articles/publish"):
admin_hits[uri] += 1
print("# Nginx daily access log review")
print("status:", status.most_common())
print("bots:", bots.most_common())
print("top_404:", not_found.most_common(20))
print("admin_hits:", admin_hits.most_common(20))
print("slow:", sorted(slow, reverse=True)[:30])
这段代码故意保持朴素,便于 SSH 到服务器后直接运行。等报告稳定后,再把输出写入 /var/www/blog/ops-reports/nginx-daily.md,或通过定时任务发送到邮箱。
步骤四:把日志结果变成可处理工单
日报如果只停留在“看过”,一周后就会失效。建议把每类日志信号映射成明确动作。404 分三类处理:第一类是旧文章 slug、旧分类页、旧标签页,确认目标内容还存在时补 301;第二类是丢失的图片、CSS、JS 或 favicon,回到模板和静态目录修路径;第三类是明显扫描路径,例如 /wp-admin、/.env、/phpmyadmin,保持 404,并在后台安全复盘时观察来源频率。5xx 分两类处理:如果 Nginx error log 同时出现 upstream 错误,优先查 Gunicorn 进程、socket、内存和应用异常;如果只有个别慢请求,先对照 Flask 应用日志和数据库查询日志,不要直接调大超时掩盖问题。
慢请求也需要阈值分层。个人博客可以先把 request_time >= 1.0 作为观察线,把 request_time >= 3.0 作为必须处理线。文章详情页慢,常见原因是 Markdown 渲染、相关文章查询、标签聚合或侧边栏最新文章查询没有缓存;上传接口慢,先看文件大小、Pillow 重编码和磁盘写入;搜索页慢,先限制关键词长度和分页大小。每次只改一个变量,改完用同一组 URL 重放验证,避免把多个变更混在一起。
curl -o /dev/null -s -w '%{http_code} %{time_total}\n' https://stepnex.cn/
curl -o /dev/null -s -w '%{http_code} %{time_total}\n' https://stepnex.cn/sitemap.xml
curl -o /dev/null -s -w '%{http_code} %{time_total}\n' https://stepnex.cn/article/flask-blog-production-health-check-runbook-zh
上面这些命令不是性能测试,只是上线后快速验证。真正定位还要回到 Nginx 日志、Gunicorn 日志、应用日志和数据库慢查询。配置变更时也要记录旧值、新值、重载时间和回滚命令,例如 sudo cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf && sudo nginx -t && sudo systemctl reload nginx。
步骤五:把检查接入 crontab
先手动运行脚本,确认没有编码、权限和路径问题。然后设置每天早上执行:
sudo install -o deployer -g adm -m 0750 review_nginx_access.py /usr/local/bin/review_nginx_access.py
crontab -e
20 8 * * * /usr/bin/python3 /usr/local/bin/review_nginx_access.py > /var/www/blog/ops-reports/nginx-access-$(date +\%F).md 2>&1
验证方式是第二天看报告文件是否生成,再用 grep 查关键段落:
ls -lh /var/www/blog/ops-reports/
grep -E "top_404|slow|admin_hits" /var/www/blog/ops-reports/nginx-access-$(date +%F).md
如果 cron 没有执行,先看 journalctl -u cron 或系统对应的计划任务日志;如果脚本手动可跑、cron 不行,多半是环境变量、工作目录或权限问题。
避坑点:不要把日志治理做成新风险
第一,不要在公开页面暴露日志报告。报告里可能有 IP、Referer、后台路径和异常请求。第二,不要只盯 5xx;大量 404、重复访问旧中文 slug、爬虫抓不到 sitemap,同样会影响站点质量判断。第三,不要把百度爬虫访问次数理解为收录保证,它只能说明抓取行为,不能代表最终索引。第四,慢请求要结合 Gunicorn 和应用日志看,Nginx 的 request_time 包含客户端传输时间,不能直接等同于 Python 函数耗时。
还有一个常见误区是把所有 404 都重定向到首页。这样会让搜索引擎和用户都失去明确信号。正确做法是:真实废弃文章返回 404 或 410;改过 slug 的文章做 301;拼写错误和明显扫描路径保留 404;高频缺失的静态资源补文件或修模板。
验证清单:发布后一小时看什么
sudo nginx -t通过,systemctl reload nginx后站点可访问。- 首页、分类页、文章页、英文文章页各访问一次,日志有对应 200。
- 人为访问一个不存在 URL,日报能统计到 404。
- 访问
/sitemap.xml和/robots.txt,确认状态码是 200。 - 后台登录和发布 API 访问能被单独统计,但报告文件不公开。
- 慢请求清单中能看到 URI、状态码和耗时,足够回到应用日志继续定位。
- logrotate 演练无报错,第二天能看到滚动后的日志文件。
审核期重点观察:少改主题,多修质量信号
在广告联盟审核冲刺期,访问日志复盘要服务于站点质量,而不是制造新的选题分散。每天优先看三件事:第一,最新技术文章和英文对应页是否被正常访问,是否出现模板 500 或移动端静态资源 404;第二,百度蜘蛛访问 sitemap、首页、分类页和新文章时是否拿到 200,是否被后台限流、WAF 或错误的真实 IP 配置误伤;第三,旧亲子内容虽然暂不新增公开发布,但如果日志显示已有文章存在高频缺图、死链或异常 404,仍然应该修复,因为这属于站点维护,不是扩展主题。
处理顺序也要克制。先修真实错误,再补 301,再优化慢页面,最后才考虑改版。不要因为某天爬虫少就频繁修改 robots、sitemap 或 canonical;这些配置应该稳定,只有日志和页面源码共同证明存在问题时才调整。每次调整后保留命令输出、日志片段和验证截图路径,方便下次复盘时知道到底是哪一次改动产生了影响。
复盘清单:每周固定处理
每周选一个固定时间看报告,不要每天看到一点波动就改配置。复盘时先处理真实影响用户和搜索引擎的项:高频 404、5xx、sitemap 异常、文章页慢请求、后台异常请求。处理后在运维记录中写清楚“发现什么、改了哪里、如何验证、是否需要回滚”。如果某个旧链接连续一周出现,可以补 301;如果某个爬虫造成明显压力,再考虑限流或条件日志。这样访问日志就不只是事故材料,而会变成独立博客长期维护的质量仪表盘。