用GoAccess搭一个每天会看的日志看板

一、先承认一件事:大多数站长不看日志

我做站前几年,日志的唯一用途是出问题时被逼着翻。平时不看,因为看不懂,也因为没有时间。等到某个接口突然慢了、某个页面突然 502 了,才一头扎进几十万行日志里找线索,找完就关掉,下次重来一遍。

后来我意识到问题不在日志本身,而在于没有一个每天会去看的地方。命令行里的 tail -f 适合排障,不适合日常。人只有面对一张有数字、有趋势、有红黄绿的看板时,才会真正形成每天扫一眼的习惯。日志看板的价值不在于它能查东西,而在于它让你在问题发生之前就注意到异常。

这篇写的是怎么用 GoAccess 加一点 Nginx 配置,搭一个几乎零成本、每天花三十秒就能扫完的访问日志看板。不用上 Prometheus,不用装 Grafana,一台 1 核 1G 的小机器足够。

二、先把日志格式定死

GoAccess 之所以快,是因为它用 C 写的解析器逐行扫描日志,而不是把日志导进数据库。代价是它必须严格知道每一行日志的格式。所以第一件事不是装 GoAccess,而是确定 Nginx 的 log_format,并且以后不再改。

log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent" '
                '$request_time $upstream_response_time';

最后两个字段 $request_time 和 $upstream_response_time 是很多人不加的,但它们恰恰是看板里最有用的部分:前者是 Nginx 看到的总耗时,后者是后端处理的耗时,两者之差就是网络和排队的时间。没有这两个字段,你看板上永远只看得到访问量,看不到慢在哪里。

还有一条纪律:日志按天切割,文件名里带日期。这是后面做趋势对比的前提。用 logrotate 就可以:

/var/log/nginx/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
    endscript
}

kill -USR1 是 Nginx 重开日志文件的标准信号,别用 reload,那会重建 worker,代价大得多。

三、装 GoAccess,并选对「离线」模式

Debian 和 Ubuntu 直接 apt install goaccess 就行。装完先跑一次离线分析,确认格式能解析:

goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --date-format='%d/%b/%Y' \
  --time-format='%T' \
  -o /tmp/report.html --no-progress

如果格式里有自定义字段(比如我们上面加的 $request_time),就不能再用 COMBINED 这个预设,必须写完整的格式串:

--log-format='%h %^[%d:%t %^] "%r" %s %b "%R" "%u" %T %^'

这里的 %^ 表示「跳过这一列不要」。这是 GoAccess 格式串里最实用的一个符号,因为日志里总有几列你不需要。写完之后的第一步验证不是看报表,而是看终端输出的第一行统计概览,重点看「Failed requests」和「Valid requests」的比值,解析失败率高就说明格式串有问题。

四、实时看板和每日静态报表,要分开做

很多人装 GoAccess 都从 --real-time-html 开始,然后发现要开 WebSocket 端口、要配反向代理、要处理跨域,最后卡在某个环节就放弃了。实际上对个人站来说,两种形态分开做最省事。

实时看板适合排障现场,只在需要的时候开。用 systemd 管一个常驻进程,绑定到本机回环地址,需要时用 SSH 端口转发看,不要暴露到公网:

goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --real-time-html --port=7890 \
  --addr=127.0.0.1 --ws-url=/ws

然后本地 ssh -L 7890:127.0.0.1:7890 user@server,浏览器打开本地的 7890 就能看。这样既不暴露端口,也不需要配 SSL,安全风险为零。

每日静态报表才是养成习惯的那个。用 cron 每天凌晨把前一天的日志生成一份 HTML,放在一个需要登录才能访问的目录里,或者干脆用 nginx auth_basic 保护起来:

0 4 * * * /usr/local/bin/goaccess-daily.sh
#!/bin/bash
D=$(date -d yesterday +%Y%m%d)
goaccess "/var/log/nginx/access.log-$D.gz" \
  --log-format=COMBINED -a \
  -o "/var/www/stats/$D.html" --no-progress
ls -1t /var/www/stats/*.html | tail -n +31 | xargs -r rm -f

注意 GoAccess 可以直接读 .gz 文件,不需要先解压,这一点很少有人知道,但能省掉不少磁盘和步骤。最后一行是保留最近 30 天,防止报表文件无限堆积。

五、看板上真正该看的五个数字

报表生成出来会有一大堆面板,一开始很容易迷失。我自己的习惯是只固定看五处,每一处对应一个具体的问题:

  • 404 的 Top 10:每一行都是一条真实存在的、指错了地方的链接。这是最容易转化成收益的面板,因为每修一个,就少一个访客跑掉。
  • 请求耗时的分布:不是看平均值,而是看那个「超过 1 秒的比例」。平均值会被大量静态请求稀释得毫无意义。
  • 被访问最多的页面,和跳出率最高的页面:对比这两张表,找「进来得多但待不住」的页面,那些才是内容真正要改的地方。
  • 状态码 4xx 与 5xx 的比值:4xx 高说明内链或者外链有问题,5xx 有稳定比例说明后端有隐患,哪怕量不大也要查。
  • 来源主机 / User-Agent 里的异常项:那些请求量极大、速度极快、UA 一模一样的来源,基本都是爬虫或者扫描器,可以直接在国内段做限制。

这五个面板三十秒就能扫完。扫的时候只问一句:「和昨天比有什么不同?」有不同才点进去看,没有就关掉。看板的正确用法是发现异常,而不是分析一切。

六、日志本身的两件事:别丢,也别撑爆磁盘

看板依赖日志,所以日志的保留和大小必须管好。个人站最常见的两个事故,一是日志把磁盘写满了导致整站挂掉,二是出问题要查的时候发现日志只留了三天。

前者的防线是 rotate 30 加 compress,再加上磁盘监控。后者的防线是保留期够长,通常 30 天够用,因为排障大多发生在问题出现的一两周内。如果站点的访问量使得 30 天日志太大,可以只对 access.log 保留 30 天,对 error.log 保留 90 天,因为 error 日志量小但价值高。

还有一件容易被忽略的事:GoAccess 解析的是文本,如果你的站点在 Nginx 前面还有 CDN,那 $remote_addr 记录的全是 CDN 的 IP,所有地理位置、来源统计都会失真。必须在 Nginx 里配置 real_ip 模块,把真实访客 IP 还原出来。这一步不做的看板,看似漂亮,实际上分析价值损失大半。

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 172.64.0.0/13;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;

七、为什么不做微服务那套

一定有人会问:为什么不直接上 Prometheus 加 Grafana?答案是取舍。那套东西能做的事多得多——它可以把 Nginx、MySQL、Docker、系统指标放在一张大盘上,还能做告警。但它需要 Exporter、需要时序数据库、需要定期清理数据、需要单独维护一套访问控制。

对一个人维护的站点来说,日志分析的需求其实非常单一:看今天谁来了、看了什么、哪些链接坏了、哪个页面慢。GoAccess 一个二进制文件、一份配置、一条 cron 就全解决了,而且产物是一个静态 HTML,你可以直接发给同事看,不需要给对方账号。它慢的部分(全量历史)你没有;它快的部分(逐行扫描、秒级出结果)正好是你需要的。

真正需要升级的信号是:开始需要按小时粒度的趋势、需要设阈值告警、需要跨多个服务关联。到那一步再上 Prometheus 也不晚,而且那时你已经在日志里摸清了「什么指标值得告警」,配出来的规则才不会是一堆噪声。

八、把它变成可以连续看的趋势

单日的报表有一个天然的缺陷:它只能告诉你「今天怎么样」,不能告诉你「是不是在变坏」。访问量从 800 掉到 600 这件事,看一张单日报表是完全无法判断严重性的——可能只是周二本来就低,也可能是昨天上线的一个改动把搜索引擎的抓取挡住了。所以报表体系里必须有第二层:把每天的汇总数字抽出来,累积成一张只有几列的小表。

做法不需要任何数据库,每天在生成报表的同时往一个 CSV 里追加一行就行。字段不要多,五个以内足够,多了就没人看了:

D=$(date -d yesterday +%Y-%m-%d)
L="/var/log/nginx/access.log-$D.gz"
total=$(zcat "$L" | wc -l)
u404=$(zcat "$L" | awk '$9==404' | wc -l)
u5xx=$(zcat "$L" | awk '$9>=500' | wc -l)
slow=$(zcat "$L" | awk '$NF+0>1' | wc -l)
echo "$D,$total,$u404,$u5xx,$slow" >> /var/www/stats/trend.csv

有了这张表,很多原来发现不了的问题会自己浮出来。404 的数量某天突然翻倍,几乎一定是模板改版之后内链错了;500 的数量从 0 变成每天几十,说明后端有个偶发错误正在积累;慢请求数量每天缓慢上升,通常是数据库表变大但索引没跟上。这三个信号在单日报表里都只表现为「一个数字」,在趋势里却是一根明显上扬的线。

更进一步,可以把这个 CSV 变成一张极简的趋势页。不需要前端框架,一段 Python 生成内联 SVG 的折线图,把文件放进同一个受保护的目录即可。这样你打开的就不再是「昨天的报表」,而是「最近三十天的走向」,顺手还能看到上周同一天的对比。这一步的成本大约半小时,收益是它把「每天扫一眼」真正变成了一件有意义的事。

九、报表目录本身的安全

访问日志里包含完整的访客 IP、User-Agent、来源页面和每一个被请求的 URL。这份数据一旦公开,等于把你站点的访客名单和内部路径结构一起送出去,尤其是如果日志里还带上了后台地址、测试页面、临时上传文件名,那就成了给扫描器的地图。

所以报表目录必须做最基本的保护,三件事缺一不可。第一,用 auth_basic 加密码,用户名密码随手设一个就够,只要不是空密码;第二,加 noindex,通过响应头或者 robots.txt 禁止收录,因为一旦被搜索引擎抓到,密码保护之外还会留下快照;第三,报表文件名里的日期不要用连续的 guessable 路径,虽然有了密码保护不是必须,但这是纵深防御的老习惯。

location /stats/ {
    auth_basic "stats";
    auth_basic_user_file /etc/nginx/.htpasswd;
    add_header X-Robots-Tag "noindex, nofollow" always;
    autoindex off;
}

这里 autoindex off 不能省。默认情况下 Nginx 对目录请求会返回 403,但如果你曾经为了图方便打开过目录列表,那所有历史报表的文件名就都暴露了。检查方式很简单:直接访问 /stats/ 这个目录本身,看到列表就是有问题的。

十、小结

日志看板这件事,做与不做的差别远大于做得好与做坏。定死日志格式、加上耗时字段、每天自动生成一份静态报表、只盯五个面板、用 logrotate 管住磁盘、别忘了 real_ip —— 这几步加起来不超过一小时,之后每天三十秒。真正改变的不是故障时的排查速度,而是你开始能在故障发生之前看见异常。这才是一个个人站长最缺的东西。

Last modification:October 30th, 2026 at 07:25 pm

Leave a Comment