YOLO813

Nginx日志可视化测试 - GoAccess

    关于GoAccess的损耗,因为网络上的教程基本是基于ELK(Elasticsearch , Logstash, Kibana),有点担心GoAccess影响服务器性能。在GoAccess官网的faq中,刚好有一个问题答疑,题目是

How fast is GoAccess when parsing a log file?

    大致翻译如下:许多因素会影响解析时间,包括处理器,内存,日志等。但是,通常我们可以得出下表: 在基准测试的前提为Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz 16GB RAM 情况下,最少能处理100,502行日志/秒,而400百万的匹配(文件大小74G)可以于内存中在1小时20分钟内处理完成,损耗约12GB RAM。在使用内存泄漏工具Valgrind检测时,GoAccess没有泄漏任何内存。启用完整功能的前提下,解析3,397,814行占用134.1 MiB(内存)。删除-q查询字符串可以大大减少内存消耗,尤其是在带有时间戳的请求上。


    按照官网的步骤如下

    1.下载GoAccess。在配置时,官网推荐选项如下:

./configure --enable-utf8 --enable-geoip=legacy
  •     enable-utf8:具有广泛的字符支持编译。必须 安装Ncursesw。
  •     enable-geoip=legacy:与GeoLocation支持一起编译。必须使用MaxMind的GeoIP。旧版将使用原始的GeoIP数据库。

    几个报错的问题如下,

configure: error:
    *** Missing development files for the GeoIP library

    缺少GeoIP,先配置epel仓库,然后搜寻GeoIP包,安装该包

yum install epel-release

    再安装ncurses-devel。出现如下画面,则说明配置成功。


    2.编译安装之后,可以开始使用。最基本的用法就是在命令行输入,看到如下画面。

goaccess access.log -c

    选择一个格式进入之后,大概是这么个样子,对应的nginx日志我也放在下面(我将nginx日志已经全部清空)

    总请求2,独立访问的用户1个,可以看到我访问了首页,其实是发出了两个请求,自动请求了首页logo(404),下面其实还有一些请求文件(请求方式、协议),未找到的URL,访问者地理位置等内容,不一一介绍了。

    测试中我发现,这个独立访问者有些问题,例如,nginx中实际统计的访问人数是26人,但是上面的独立访客一直显示的是16人,按照我的理解,26个独立IP怎么的也得算26个Unique Visitors了吧,尤其是我去查了官方手册之后

awk '{print $1}' access.log | sort | uniq -c | awk '{print $2}' | wc -l
>>>26

官方手册,其中关于唯一访问者的介绍是

Unique visitors: This panel shows metrics such as hits, unique visitors and cumulative bandwidth per date. HTTP requests containing the same IP, the same date, and the same user agent are considered a unique visitor. By default, it includes web crawlers/spiders.

Optionally, date specificity can be set to the hour level using --date-spec=hr which will display dates such as 05/Jun/2016:16, or to the minute level producing 05/Jun/2016:16:59. This is great if you want to track your daily traffic at the hour or minute level.

蹩脚翻译:包含相同IP,相同日期和相同用户代理的HTTP请求被视为唯一访问者,这句话应该想表达的是只要IP,日期,或者UA不一样,则代表用户是一个独立的访问者,因为同一个局域网络中(公网IP相同)通过安卓机、电脑、平板访问同一个网站他们的UA不同。看完我就更懵了,按照这么说,独立访问者大部分时候应该比独立IP更多呗,这咋还少一些,没有查到相关的资料,我就带着这个疑惑进入了第三步。


    3.生成静态HTML报告。

goaccess access.log -o report.html --log-format=COMBINED

将report.html迁移至nginx访问目录下,我们到前台看下是个什么东西,哟,还蛮酷炫。

在这里面找到Visitor Hostnames and IPs,如下图所示,找到最下面统计那一行,总统计数69,唯一访问者16,后面还有一个数字26,就是访问的去重后的IP数。关于Unique Visitors(感觉翻译成不重复访问者比较好一点,因为IP,UA,DATA任选其一不重复则会计入其中),我的理解是一个独立的IP过来访问,你的页面返回状态码的是200类型,才会被计入到Unique Visitors(所以这就解释了上面第二步的问题)。

    例如上图中的第2个IP:43.254.151.94,在nginx中总共有9条访问记录,其中,状态码为200的只有一条,所以请求9,访问1。

    再例如,上面第3个IP:40.72.109.61,我也去nginx中查了它的访问记录,下图,可以很清楚的看到3条访问记录,均为404,所以请求3,访问者0(因为它一个页面都没有访问到)

    综上,Unique Visitors指的是用户请求网站页面成功次数的总和(或者可以叫做成功访问网站用户数),即Visitors那一列的所有数字相加。


    4.如何生成动态实时的html报告?

    首先需要配置nginx文件的路由,例如我的报告内容均生成在以下目录,名字为index.html。

/usr/local/nginx/html/report

    在后台运行goaccess,命令如下,port是指定WSS通过9870端口来传输数据(我的防火墙已经放行了该端口),如果不设置,则默认为7890端口;exclude-ip指在统计页面中不统计这个IP(我的公网IP),当然, Total Requests中还是会计入 这个IP的访问,但是在Visitor Hostnames and IPs一栏中就可以看到我的IP统计全部消失了。

goaccess access.log -o /usr/local/nginx/html/report/index.html --real-time-html --port=9870  --exclude-ip=150.40.04.1

    配置nginx文件,新建一个虚拟主机,监听8080端口

server {
  listen 8080;
  location /report {
    alias /usr/local/nginx/html/report;
  }
}

在访问8080端口report目录时,返回index.html,需要注意的是,该页面只是一个静态页面,其数据变化其实是依赖于WebSocket,所以一旦在后台停止了goaccess,那么这个页面立刻就会恢复到刚生成的静态页面。

    当然,这个页面不可能每个人都能访问,所以还需要在该页面设置一个登录验证。利用nginx也很容易实现,参考Nginx作为web服务器的功能以及虚拟主机的作用。

dnf install -y httpd-tools
# 配置nginx.conf,虚拟主机中加入以下代码
auth_basic "zhang";
auth_basic_user_file /usr/local/nginx/pwd/htpasswd;
# 创建文件夹
mkdir /usr/local/nginx/pwd
# 以命令行形式添加密码,并自动创建密码文件htpasswd
htpasswd -b -c /usr/local/nginx/pwd/htpasswd zhangxiaofei 123456
>Adding password for user zhangxiaofe

前端页面必须通过账户密码验证才可访问。

    5.如果不想每次人工去运行goaccess,也可以获取实时的报告,有两种方案,一种是写一个crontab脚本定时运行,每次重新输出一个静态页面,因为其实这个前端页面访问的是nginx的html目录下的某个文件,这种方法适合服务器资源较紧张,对日志实时性要求不高的用户,例如

touch nginx_logs.sh
chmod o+x nginx_logs.sh
# nginx_logs.sh脚本内存如下
#!/bin/bash
# 显示中文界面,似乎无效
LANG="zh_CN.UTF-8"
counts=$(lsof -i :7890  | wc -l)
if [ $counts -ne 0 ]
then
        killall goaccess;
        /usr/local/bin/goaccess /usr/local/nginx/logs/access.log -o /usr/local/nginx/html/report/index.html -p /usr/local/etc/goaccess/goaccess.conf
else
        /usr/local/bin/goaccess /usr/local/nginx/logs/access.log -o /usr/local/nginx/html/report/index.html -p /usr/local/etc/goaccess/goaccess.conf
fi
# 定时脚本
crontab -e
*/1 * * * * /bin/bash /usr/local/nginx/logs/nginx_logs.sh

脚本的含义是先判断goaccess启动了没,如果7890端口被占用,我判断它已经启动,并kill掉重新输出一个html文件,如果7890端口没被占用,那么启动goaccess即可,这样前台访问的静态页面也是每分钟就会刷新一次。

    另外一种方法就是加入--daemonize参数以守护进程启动。需要注意下面的路径都要使用绝对路径!

goaccess /usr/local/nginx/logs/access.log -o /usr/local/nginx/html/report/index.html --real-time-html --daemonize
# 查看是否启动
Daemonized GoAccess: 36248
ps aux | grep goaccess

    可以看到已经顺利启动。


    6.关于goaccess日志格式,解释如下

  • %t  匹配time-format格式的时间字段
  • %d  匹配date-format格式的日期字段
  • %h  host(客户端ip地址,包括ipv4和ipv6)
  • %r  来自客户端的请求行
  • %m  请求的方法
  • %U  URL路径
  • %H  请求协议
  • %s  服务器响应的状态码
  • %b  服务器返回的内容大小
  • %R  HTTP请求头的referer字段
  • %u  用户代理的HTTP请求报头
  • %D  请求所花费的时间,单位微秒
  • %T  请求所花费的时间,单位秒
  • %^  忽略这一字段

我来贴一段nginx的日志以及nginx配置文件中的log_format

119.36.11.83 - zhangxiaofei [31/Jan/2021:06:26:42 +0000]
 "GET /report/ HTTP/1.1" 200 382732 "-"
 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0" "-"

# 配置文件nginx.conf

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

然后再看下/usr/local/etc/goaccess/goaccess.conf文件,就能够明白这个日志格式是怎么对应的了。

# NCSA Combined Log Format
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"

%h对应的$remote_addr,第二个%^参数忽略,第三个[%d:%t %^]对应的是nginx[$time_local]的31/Jan/2021和06:26:42,+0000省略,第四个"%r"对应的"$request",第五个%s对应的$status,以此类推。


    7.如果要通过TLS / SSL连接输出实时数据,需要使用--ssl-cert = <cert.crt>和--ssl-key = <priv.key>,这些也可以在goaccess.conf配置文件中直接修改,当然,在源码配置之前需要加入--with-openssl。

参考资料:

#goaccess
https://goaccess.io/get-started
https://goaccess.io/man
#I find the value of "unique visitors" is not right!
https://github.com/allinurl/goaccess/issues/1205