:访问日志,记录所有请求
  • error.log:错误日志,记录错误
  • sudo tail -20:看最后 20 行
  • sudo tail -f:实时跟踪
  • 三、access.log 的格式:每一列是什么

    “你现在看这一行。”老周指着屏幕:

    192.168.1.100 - - [13/Sep/2026:10:05:23 +0800] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0 ..."
    

    “每一段是啥意思?”

    小林摇头。

    老周拿笔在纸上画:

    192.168.1.100    -    -    [13/Sep/2026:10:05:23 +0800]    "GET / HTTP/1.1"    200    1234    "-"    "Mozilla/5.0 ..."
        ①          ②    ③              ④                            ⑤              ⑥     ⑦      ⑧          ⑨
    

    “一个一个说。”

    • ① 192.168.1.100:客户端 IP。谁访问的。
    • ② -:远程用户,通常为空。
    • ③ -:认证用户,通常为空。
    • ④ [13/Sep/2026:10:05:23 +0800]:访问时间,包含时区。
    • ⑤ "GET / HTTP/1.1":请求方法和路径。GET 是方法,/ 是路径,HTTP/1.1 是协议。
    • ⑥ 200:HTTP 状态码。200 成功,404 找不到,500 服务器错误,502 网关错误。
    • ⑦ 1234:响应大小,单位字节。
    • ⑧ "-":Referer,从哪个页面跳过来的。为空表示直接访问。
    • ⑨ "Mozilla/5.0 ...":User-Agent,客户端浏览器信息。

    “你以后分析日志,最常用的就是 ①、④、⑤、⑥、⑦ 这五列。”老周说,“IP、时间、请求、状态码、大小。记住这五个。”

    “那 awk 里对应的字段号是啥?”

    “好问题。”老周说,“Nginx 默认日志用空格分隔,所以 awk 里:

    • $1:IP
    • $4:时间(带 [,需要处理)
    • $6:请求方法
    • $7:请求路径
    • $9:状态码
    • $10:响应大小”

    “等等,$9 是状态码?不是 $6 吗?”

    “你数一下。”老周说,“$1 是 IP,$2 和 $3 是两个 -,$4 是时间,$5 是 "GET,$6 是 /,$7 是 HTTP/1.1",$8 是 " 或者状态码……”

    小林数晕了。

    “所以,日志格式的字段位置,取决于具体的日志格式。”老周说,“你别死记,用 awk 的时候先 print 出来看看。”

    sudo tail -1 /var/log/nginx/access.log | awk '{for(i=1;i<=NF;i++) print i": "$i}'
    

    输出:

    1: 192.168.1.100
    2: -
    3: -
    4: [13/Sep/2026:10:05:23
    5: +0800]
    6: "GET
    7: /
    8: HTTP/1.1"
    9: 200
    10: 1234
    11: "-"
    12: "Mozilla/5.0
    13: ..."
    

    “看到了吧?$9 是状态码。”老周说,“但这是默认格式。如果你改了 Nginx 的 log_format,字段位置会变。所以,先 awk 打印字段,再写统计命令。”

    小林在笔记本上写下:

    • access.log 默认格式字段:$1 IP,$4 时间,$7 路径,$9 状态码,$10 大小
    • 但不要死记,先用 awk '{for(i=1;i<=NF;i++) print i": "$i}' 打印字段

    四、用 grep 过滤状态码

    “现在开始实战。”老周说,“你先统计一下,博客上线以来,有多少请求是 404。”

    “怎么统计?”

    “grep 过滤 404,wc -l 统计行数。”

    sudo grep " 404 " /var/log/nginx/access.log | wc -l
    

    输出:

    15
    

    “15 个 404。”小林说。

    “为啥 grep 写 " 404 " 而不是 "404"?”

    “因为 404 可能出现在其他地方,比如 IP 里、时间里、响应大小里。加空格是精确定位状态码字段。”

    注:这里的IP是指IPv6的IP, 如果是IPv4则不可能有404的存在

    “对。”老周说,“但这不是最严谨的写法。最严谨的是用 awk 取 $9。”

    sudo awk '$9 == 404' /var/log/nginx/access.log | wc -l
    

    输出:

    15
    

    “结果一样,但 awk 更精确。”老周说,“$9 就是状态码字段,不管日志格式怎么变,只要状态码在 $9,就一定能取到。”

    “那 grep 还有啥用?”

    “grep 适合快速过滤文本。”老周说,“比如你想找所有包含 /posts/ 的请求:”

    sudo grep "/posts/" /var/log/nginx/access.log | head -10
    

    “或者找某个 IP 的请求:”

    sudo grep "^192.168.1.100" /var/log/nginx/access.log | head -10
    

    “^ 表示行首,^192.168.1.100 表示这一行以这个 IP 开头。”

    小林记下:

    • grep " 404 ":快速过滤状态码
    • awk '$9 == 404':精确按字段过滤
    • grep "^IP":按 IP 过滤
    • grep "/posts/":按路径过滤

    五、用 awk 统计 Top IP 和 Top URL

    “现在统计访问量最高的 IP。”老周说。

    “怎么写?”

    “awk 取 $1,计数,排序。”

    sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
    

    输出:

         50 192.168.1.100
         30 192.168.1.101
         20 192.168.1.102
         ...
    

    “这一串管道,每一步干啥?”老周问。

    小林试着解释:

    • awk '{print $1}':提取第一列,IP
    • sort:排序,让相同 IP 挨在一起
    • uniq -c:去重并计数
    • sort -rn:按数字倒序排列
    • head -10:取前 10

    “完全正确。”老周说,“这就是日志分析的经典管道。你以后统计 Top URL、Top 状态码、Top User-Agent,都是这个套路。”

    “那 Top URL 呢?”

    sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
    

    输出:

         30 /posts/my-first-post/
         20 /
         15 /tags/hugo/
         10 /about/
          5 /images/hugo-logo.png
    

    “看到了吗?你的文章访问量最高,首页第二。”老周说,“这就是数据。”

    “那状态码分布呢?”

    sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
    

    输出:

        200 200
         15 404
          3 500
    

    “200 是成功,404 是找不到,500 是服务器错误。”老周说,“你有 3 个 500,得看看 error.log。”

    小林记下:

    • 统计 Top IP:awk '{print $1}' | sort | uniq -c | sort -rn | head
    • 统计 Top URL:awk '{print $7}' | sort | uniq -c | sort -rn | head
    • 统计状态码分布:awk '{print $9}' | sort | uniq -c | sort -rn

    六、error.log:错误日志

    “你刚才看到 3 个 500,现在看 error.log。”老周说。

    sudo tail -20 /var/log/nginx/error.log
    

    输出:

    2026/09/13 10:10:23 [error] 1234#1234: *1 open() "/var/www/blog/favicon.ico" failed (2: No such file or directory)
    2026/09/13 10:15:45 [error] 1234#1234: *5 open() "/var/www/blog/posts/old-post/" failed (2: No such file or directory)
    2026/09/13 10:20:12 [error] 1234#1234: *8 open() "/var/www/blog/images/missing.png" failed (2: No such file or directory)
    

    “看到了吗?”老周说,“favicon.ico 不存在,old-post 不存在,missing.png 不存在。这三个就是 404 的原因。”

    “那 500 呢?”

    “你再看。”

    小林翻了翻:

    2026/09/13 10:25:00 [error] 1234#1234: *12 rewrite or internal redirection cycle while internally redirecting to "/index.html"
    

    “这个就是我昨天搞的 try_files 循环。”小林说,“我已经改回去了。”

    “对。”老周说,“error.log 会告诉你每个错误的文件、路径、原因。你以后排错,先看这里。”

    小林记下:

    • error.log 格式:时间 [级别] 进程号: *连接号 错误信息
    • 常见级别:error、warn、crit
    • 常见错误:No such file or directory、Permission denied、rewrite or internal redirection cycle

    七、排错挑战:大量 404

    下午,小林收到老周的消息:“我看你博客访问日志里有大量 404,你看看怎么回事。”

    小林打开日志:

    sudo awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
    

    输出:

         10 /posts/old-post/
          5 /images/missing.png
    

    “old-post 是我不小心删掉的文章,missing.png 是图片路径写错了。”小林说。

    他打开文章,找到引用 old-post 的地方,改了链接。又找到 missing.png 的引用,改成正确的图片名。

    “但改完文章,hugo 重新生成,Nginx 还是返回旧文件。”小林说。

    “因为 public/ 更新了,但 /var/www/blog/ 没更新。”老周说,“你每次 hugo 之后,要同步到 /var/www/blog/。”

    “怎么同步?”

    “rsync。”老周说,“rsync 是同步工具,比 cp 快,还能删除目标目录里多余的文件。”

    hugo -d /var/www/blog
    sudo chown -R www-data:www-data /var/www/blog
    

    “或者,你先 hugo 生成到 public/,再 rsync 同步:”

    hugo
    sudo rsync -av --delete public/ /var/www/blog/
    sudo chown -R www-data:www-data /var/www/blog
    

    “-a 是归档模式,保留权限和时间。-v 是显示详情。--delete 是删除目标目录里源目录没有的文件。--delete 要小心,别写错路径,不然删错东西。”

    小林试了一下,同步成功。

    再查 404:

    sudo awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
    

    输出:

          1 /favicon.ico
    

    “只剩一个 favicon.ico 了。”小林说。

    “正常。”老周说,“浏览器默认请求 favicon.ico,你没有这个文件。加一个就行。”

    排错思路总结:

    1. 大量 404,先用 awk '$9 == 404 {print $7}' | sort | uniq -c | sort -rn 找出哪些 URL 404。
    2. 检查这些 URL 对应的文件是否存在。
    3. 如果是文章链接错误,改 Markdown。
    4. 如果是图片路径错误,改引用。
    5. 改完后 hugo 重新生成,rsync 同步到 /var/www/blog/。
    6. 最后加 favicon.ico,消除最后一个 404。

    “记住,”老周说,“日志不是给你看的,是给你‘找线索’的。 你先用 awk 统计,锁定问题,再去改。别一行一行看,那是浪费时间。”

    八、练习

    1. 练习一:用 sudo tail -20 /var/log/nginx/access.log 查看最近 20 条日志。用 awk '{for(i=1;i<=NF;i++) print i": "$i}' 打印一行日志的所有字段,确认 $9 是状态码。
    2. 练习二:统计 access.log 中状态码为 200、404、500 的请求各有多少条。用 awk '$9 == 404' | wc -l 的格式,分别统计。
    3. 练习三:用 awk 统计访问量最高的前 5 个 IP,以及访问量最高的前 5 个 URL。然后用 grep "/posts/" /var/log/nginx/access.log | head -5 查看具体请求。

    参考答案

    练习一:

    sudo tail -20 /var/log/nginx/access.log
    sudo tail -1 /var/log/nginx/access.log | awk '{for(i=1;i<=NF;i++) print i": "$i}'
    

    输出中 9: 200 或 9: 404,说明 $9 是状态码。

    练习二:

    sudo awk '$9 == 200' /var/log/nginx/access.log | wc -l
    sudo awk '$9 == 404' /var/log/nginx/access.log | wc -l
    sudo awk '$9 == 500' /var/log/nginx/access.log | wc -l
    

    练习三:

    sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5
    sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5
    sudo grep "/posts/" /var/log/nginx/access.log | head -5
    

    九、下集预告

    日志看懂了,404 和 500 也排查了。老周说:“博客上线了,但你不知道它什么时候会挂。”小林问:“那怎么办?”老周说:“你得让它自己告诉你。”下一集,老周带小林进入监控的世界。

    下一集:《博客上线了,但故事才刚刚开始》