如何仅使用NGINX和纯BASH追踪网站分析数据
今天,我要和大家分享一个小技巧,仅使用bash
和nginx
来实现类似Google Analytics的网站追踪服务。
你可能会问...“为什么要这么做?”;你看,很久以前,我决定只用C语言来制作我的个人网站。
没错,就是你现在正在浏览和阅读的这个网站;我使用了很多现有的“markdown -> html”C代码,并加入了自己的调味料(虽然有点乱,但我很喜欢)。
查看仓库
这真的是一个非常非常非常糟糕的主意....我说“非常”够了吗?
为什么?嗯...基本上,我的大部分博客都是markdown格式的,为了符合“现代浏览互联网的方式”,我编写了一个lib.c
来控制网站的构建(这篇文章不会深入讨论这个)。
但是,嘿,犯错让你成长...对吧?....对吧?
无论如何,构建一个只有html/markdown的东西的问题是,你没有涉及到JS...说实话,这是第一个原因(不是因为我讨厌现代JS 向我的JS朋友们眨眼);
因此,对我来说,Google Analytics是“不可能的”...
值得吗?绝对不值得,我还会再这么做吗?哦,当然会!
无论如何...让我们开始吧,好吗?
正式介绍
由于我只是在网站/博客构建后返回基本的html,没有“严肃的JS(除了博客底部的GitHub评论)”,我无法追踪谁、有多少人访问了这个页面或那个页面。
但是,我(仍然)使用nginx来处理请求部分,我问自己,如果这个服务基本上记录了访问日志,为什么不直接使用它来收集指标呢?(很合理,对吧?)。
因此,基于Nginx添加一个“分析系统”通常涉及配置Nginx以基本上只记录请求(带有足够的详细信息),处理这些日志以计算浏览量、IP、时间,并可能可视化数据。
如何“实际”做到这一点
在NGINX中启用日志记录
你知道,nginx默认记录请求,但你可能需要自定义日志格式以跟踪相关细节,例如URL或IP地址。
编辑你的Nginx配置文件,通常位于/etc/nginx/nginx.conf
或/etc/nginx/sites-available/<your-site>
。
$ vim /etc/nginx/nginx.conf
# 或者使用'nano'作为编辑器,如果你像我一样是个新手。
然后在http
部分,
# 简单的浏览量示例
http {
log_format views '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$host" "$request_uri"';
access_log /var/log/nginx/access.log views;
}
$remote_addr
: 追踪客户端IP。$host
和$request_uri
: 捕获域名和URL。
重新加载Nginx以应用更改:
$ sudo systemctl reload nginx
可选地,你还可以检查你的nginx配置是否仍然正常:
$ nginx -t
下面是一个捕获更多跟踪信息的示例:
# 最详细的示例,比以前捕获更多信息
http {
log_format analytics '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$scheme $server_name $request_method '
'$ssl_protocol $ssl_cipher';
access_log /var/log/nginx/access.log analytics;
}
- IP地址 :
$remote_addr
- 用户名(如果使用基本认证):
$remote_user
- 时间戳 :
$time_local
- 原始请求 :
$request
(包括URL、查询、HTTP版本) - HTTP状态 :
$status
- 响应大小 :
$body_bytes_sent
- 引用来源 :
$http_referer
- 用户代理 :
$http_user_agent
- 请求时间 :
$request_time
- 上游时间 :
$upstream_response_time
- 方案 :
$scheme
(http/https) - 服务器名称 :
$server_name
- 请求方法 :
$request_method
(GET/POST, 等) - SSL协议 :
$ssl_protocol
(例如,TLSv1.3) - SSL加密 :
$ssl_cipher
(例如,AES128-GCM-SHA256)
“瞧”,最重要的部分完成了,你现在所有的请求日志都会记录到配置中指定的路径,在我们的例子中是_/var/log/nginx/access.log_,它看起来像这样:
$ cat /var/log/nginx/access.log
18.220.122.122 - - [12/Jan/2025:17:15:19 +0000] "GET / HTTP/1.1" 400 666 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/126.0.0.0 Safari/537.36" "147.182.205.116" "/"$
54.36.148.9 - - [12/Jan/2025:17:16:15 +0000] "GET /robots.txt HTTP/1.1" 200 719 "-" "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)" "sanixdk.xyz" "/robots.txt"$
51.222.253.18 - - [12/Jan/2025:17:16:16 +0000] "GET /blogs/how-to-make-a-password-generator-using-brainfuck-part-1-3 HTTP/1.1" 200 7569 "-" "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)" "sanixdk.xyz" "/blogs/how-to-make-a-password-generator-using-brainfuck-part-1-3"$
124.44.90.81 - - [12/Jan/2025:17:22:41 +0000] "GET / HTTP/1.0" 404 162 "-" "curl/7.88.1" "147.182.205.116" "/"$
95.214.55.226 - - [12/Jan/2025:17:26:19 +0000] "GET / HTTP/1.1" 404 162 "-" "-" "147.182.205.116" "/"$
现在,我们有了这些日志,让我们来读取、解析、理解并利用它们。
现在,让我们做一些编程
你知道我喜欢bash...我是说,我热爱bash,为什么?
嗯,现在应该很明显了,但无论你使用什么Linux发行版...或者你刚刚安装的,它已经在那里了,不需要安装gcc
,或python
或任何东西...
因为它是shell,所以我们将使用它。
让我们分解每个步骤,从源代码到收集我们所需所有指标的服务。
源代码:
每条指令都尽可能详细地记录,如有问题请在评论区提问。
#!/usr/bin/env bash
#
# nginx-analytics.sh - 收集Nginx日志指标并输出JSON分析数据。
#
# 作者:dk (https://github.com/sanix-darker)
LOG_FILE="/var/log/nginx/access.log"
OUTPUT_FILE="/var/www/html/nginx-analytics.json"
# 安全检查:确保LOG_FILE存在
if [[ ! -f "$LOG_FILE" ]]; then
echo "错误:在$LOG_FILE找不到Nginx日志文件"
exit 1
fi
# 1) 按URL的访问量(前5)
# - 使用'awk'从"$request"字段(假设它在$5)中提取URL。
# - 然后将请求字符串“METHOD /some/url HTTP/1.x”拆分为令牌,
# 并保留第二个令牌(实际路径/some/url)。
visits_by_url=$(
awk '{
# $5可能是这样的:"GET /index.html HTTP/1.1"
# 所以我们按空格拆分。
split($5, req_parts, " ")
url=req_parts[2]
if (url != "") {
urls[url]++
}
} END {
# 按频率排序并选择前5
for (u in urls) {
print urls[u] " " u
}
}' "$LOG_FILE" \
| sort -nr \
| head -n 5
)
# 2) 前5个IP地址
top_ips=$(
awk '{
ips[$1]++
} END {
for (ip in ips) {
print ips[ip] " " ip
}
}' "$LOG_FILE" \
| sort -nr \
| head -n 5
)
# 3) 平均请求时间($request_time),假设它是日志格式中的第10个字段
avg_request_time=$(
awk '{
sum+=$10; count++
} END {
if (count > 0) {
printf("%.5f", sum/count)
} else {
print "0"
}
}' "$LOG_FILE"
)
# 4) 平均上游响应时间($upstream_response_time),假设它是日志格式中的第11个字段
avg_upstream_time=$(
awk '{
sum+=$11; count++
} END {
if (count > 0) {
printf("%.5f", sum/count)
} else {
print "0"
}
}' "$LOG_FILE"
)
# 5) 前5个HTTP状态码,假设它是日志格式中的第6个字段
top_status_codes=$(
awk '{
status[$6]++
} END {
for (s in status) {
print status[s] " " s
}
}' "$LOG_FILE" \
| sort -nr \
| head -n 5
)
# ------------------------------------------------------------
# 现在,不那么痛苦的部分,
# 将上述变量转换为可理解的JSON。
# 我们将进行最少的解析以生成有效的JSON数组/对象。
# ------------------------------------------------------------
# 'lines_to_json_array'将“计数 值”行转换为JSON数组条目
# 例如,“123 /home” -> { "value": "/home", "count": 123 }
function lines_to_json_array() {
local input="$1"
local result="["
local first=1
while IFS= read -r line; do
# 这里我们只是将行拆分为“计数”和“值”
count=$(echo "$line" | awk '{print $1}')
value=$(echo "$line" | awk '{print $2}')
# 重要提示:
# 如果“值”有空格,请小心处理。(在简单情况下,不会有。)
# 如果你有更复杂的解析,你会进行更健壮的拆分。
if [ "$first" -eq 1 ]; then
first=0
else
result="$result,"
fi
result="$result {\"value\":\"$value\",\"count\":$count}"
done <<< "$input"
result="$result]"
echo "$result"
}
# visits_by_url -> JSON-array
visits_by_url_json=$(lines_to_json_array "$visits_by_url")
# top_ips -> JSON-array
top_ips_json=$(lines_to_json_array "$top_ips")
# top_status_codes -> JSON-array
top_status_codes_json=$(lines_to_json_array "$top_status_codes")
# 瞧,我们的json准备好了
json_output=$(
cat <<EOF
{
"visits_by_url": $visits_by_url_json,
"top_ips": $top_ips_json,
"avg_request_time": "$avg_request_time",
"avg_upstream_time": "$avg_upstream_time",
"top_status_codes": $top_status_codes_json
}
EOF
)
# 将JSON写入OUTPUT_FILE(例如,用于Web仪表板或进一步处理)
echo "$json_output" > "$OUTPUT_FILE"
# 或者简单地输出到stdout,如果你喜欢:
# echo "$json_output"
exit 0 # 你可以选择退出其他内容,你的代码,你选择 :wink:
将以下内容保存到/usr/local/bin/nginx-analytics.sh
(根据需要调整路径)
**注意:**别忘了让它可执行
$ sudo chmod +x /usr/local/bin/nginx-analytics.sh
创建一个SYSTEMD单元
现在我们有了脚本,我们需要让它作为一个“服务”运行,这样如果服务器启动,我们仍然可以在后台启动它,所以下面是一个示例systemd服务文件,用于将此脚本作为一次性服务运行。创建一个文件/etc/systemd/system/nginx-analytics.service
:
[Unit]
Description=收集Nginx日志指标并输出JSON
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/nginx-analytics.sh
[Install]
WantedBy=multi-user.target
现在,_重新加载systemd_以注册新服务:
sudo systemctl daemon-reload
然后,手动运行服务(按需):
sudo systemctl start nginx-analytics.service
最后_启用启动时运行_(如果需要):
sudo systemctl enable nginx-analytics.service
(可选...但我强烈推荐)设置一个SYSTEMD计时器
如果你希望此脚本定期运行(例如,每5分钟或每小时,或者每秒钟为那些疯狂的读者),创建一个匹配的.timer
单元。例如,/etc/systemd/system/nginx-analytics.timer
:
[Unit]
Description=定期运行nginx-analytics服务
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
然后:
sudo systemctl daemon-reload
sudo systemctl enable --now nginx-analytics.timer
这将每小时运行一次nginx-analytics.service
(从而运行nginx-analytics.sh
),并在启动后5分钟运行。
想看结果吗???
每次运行后,你