為何我選擇拋棄Google Analytics

身為一名重視網站效能與隱私的技術人員,玄貓一直在尋找輕量級的網站流量分析方案。多數人會直接選擇Google Analytics或其他商業分析工具,但這些工具通常需要在網站中嵌入JavaScript程式碼,增加頁面載入時間,還可能將使用者資料傳送至第三方伺服器。

我的個人網站是以純HTML與Markdown建構的,完全沒有JavaScript(除了部落格文章底部的GitHub評論功能外)。這個設計決策讓我無法使用標準的分析工具,但卻開啟了一條另類別思路:為何不直接利用已經在使用的NGINX伺服器日誌來分析網站流量?

這個方法的優勢非常明顯:

  • 零額外JavaScript載入
  • 完全掌控自己的資料
  • 不依賴第三方服務
  • 資源消耗極低

純NGINX與BASH的分析方案架構

客製化NGINX日誌格式

實作這套分析系統的第一步是調整NGINX的日誌格式,確保記錄足夠詳細的存取資訊。編輯NGINX設定檔案(通常位於/etc/nginx/nginx.conf/etc/nginx/sites-available/你的網站):

$ 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 -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;
}

這個進階格式記錄了多達15種資訊:

  1. IP位址 ($remote_addr)
  2. 使用者名稱,如果有基本驗證 ($remote_user)
  3. 時間戳記 ($time_local)
  4. 原始請求內容 ($request)
  5. HTTP狀態碼 ($status)
  6. 回應大小 ($body_bytes_sent)
  7. 來源網址 ($http_referer)
  8. 使用者代理 ($http_user_agent)
  9. 請求處理時間 ($request_time)
  10. 上游回應時間 ($upstream_response_time)
  11. 協定類別 ($scheme)
  12. 伺服器名稱 ($server_name)
  13. 請求方法 ($request_method)
  14. SSL協定版本 ($ssl_protocol)
  15. SSL加密方式 ($ssl_cipher)

設定完成後,所有的網站存取記錄會儲存在/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"

使用BASH處理日誌分析

有了詳細的存取日誌,接下來我們需要使用BASH指令來處理這些資料,從中提取有價值的分析結果。

基本存取量統計

以下是一個簡單的BASHScript,用於計算特定URL的存取次數:

#!/bin/bash

# 定義日誌檔案路徑
LOG_FILE="/var/log/nginx/access.log"

# 計算首頁存取次數
HOME_VISITS=$(grep "GET / HTTP" $LOG_FILE | wc -l)
echo "首頁存取次數: $HOME_VISITS"

# 計算特定文章的存取次數
ARTICLE_VISITS=$(grep "GET /blogs/my-awesome-article" $LOG_FILE | wc -l)
echo "特定文章存取次數: $ARTICLE_VISITS"

# 計算所有成功的請求 (HTTP 200)
SUCCESS_REQUESTS=$(grep " 200 " $LOG_FILE | wc -l)
echo "成功請求總數: $SUCCESS_REQUESTS"

# 計算所有錯誤的請求 (HTTP 4xx/5xx)
ERROR_REQUESTS=$(grep -E " (4|5)[0-9]{2} " $LOG_FILE | wc -l)
echo "錯誤請求總數: $ERROR_REQUESTS"

進階存取者分析

若要進行更深入的分析,例如識別獨立存取者、分析存取來源等,可以使用以下Script:

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"

# 統計獨立IP存取者數量
UNIQUE_VISITORS=$(awk '{print $1}' $LOG_FILE | sort | uniq | wc -l)
echo "獨立存取者數量: $UNIQUE_VISITORS"

# 找出存取最頻繁的頁面
echo "存取最頻繁的頁面:"
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -nr | head -5

# 分析使用者代理(瀏覽器類別)
echo "瀏覽器分佈:"
awk -F'"' '{print $6}' $LOG_FILE | grep -Eo "Chrome|Firefox|Safari|Edge|MSIE" | sort | uniq -c | sort -nr

# 統計每小時存取量
echo "每小時存取量分佈:"
awk '{print substr($4, 2, 2)}' $LOG_FILE | sort | uniq -c | sort -k2n

內容解密

上述Script中的命令解析:

  1. grep 用於篩選符合特定模式的行,例如尋找特定URL的存取記錄
  2. wc -l 計算行數,對應到存取次數
  3. awk 是強大的文書處理工具,用於提取日誌中的特定欄位
  4. sortuniq 組合使用來統計獨特專案的數量
  5. sort -nr 根據出現次數進行數值逆序排序
  6. head -5 只顯示前5個結果

自動化日誌分析與報表生成

為了持續監控網站流量,我建議設定一個定時任務,自動處理日誌並生成報表。可以使用cron來實作:

#!/bin/bash

# 報表生成Script
LOG_FILE="/var/log/nginx/access.log"
REPORT_DIR="/var/www/analytics"
DATE=$(date +"%Y-%m-%d")

# 建立報表目錄
mkdir -p $REPORT_DIR

# 生成今日報表
echo "網站流量報表 - $DATE" > $REPORT_DIR/report-$DATE.txt
echo "----------------------------" >> $REPORT_DIR/report-$DATE.txt

# 總存取次數
TOTAL_VISITS=$(wc -l < $LOG_FILE)
echo "總存取次數: $TOTAL_VISITS" >> $REPORT_DIR/report-$DATE.txt

# 獨立存取者
UNIQUE_VISITORS=$(awk '{print $1}' $LOG_FILE | sort | uniq | wc -l)
echo "獨立存取者: $UNIQUE_VISITORS" >> $REPORT_DIR/report-$DATE.txt

# 熱門頁面
echo -e "\n熱門頁面:" >> $REPORT_DIR/report-$DATE.txt
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -nr | head -10 >> $REPORT_DIR/report-$DATE.txt

# 每小時流量
echo -e "\n每小時存取量:" >> $REPORT_DIR/report-$DATE.txt
awk '{print substr($4, 14, 2)}' $LOG_FILE | sort | uniq -c | sort -k2n >> $REPORT_DIR/report-$DATE.txt

# 來源網站
echo -e "\n存取來源:" >> $REPORT_DIR/report-$DATE.txt
awk -F'"' '{print $4}' $LOG_FILE | grep -v "^-$" | sort | uniq -c | sort -nr | head -5 >> $REPORT_DIR/report-$DATE.txt

# 輪換日誌以避免檔案過大
if [ -f $LOG_FILE ]; then
    mv $LOG_FILE $LOG_FILE.$DATE
    kill -USR1 $(cat /var/run/nginx.pid)
fi

將此Script設為每日執行:

# 編輯crontab
$ crontab -e

# 增加以下行,每天午夜執行
0 0 * * * /path/to/analytics_report.sh

日誌分析結果視覺化

數字報表雖然有用,但視覺化圖表更直觀。以下是一個使用GNUplot生成簡單存取量圖表的Script:

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"
DATA_FILE="/tmp/visits_data.txt"
PLOT_FILE="/var/www/analytics/visits_plot.png"

# 提取每小時存取資料
awk '{print substr($4, 14, 2)}' $LOG_FILE | sort | uniq -c > $DATA_FILE

# 使用GNUplot生成圖表
gnuplot <<EOF
set terminal png size 800,400
set output "$PLOT_FILE"
set title "網站每小時存取量"
set xlabel "小時"
set ylabel "存取次數"
set xtics rotate by -45
set style fill solid
set boxwidth 0.5
plot "$DATA_FILE" using 2:1 with boxes notitle
EOF

echo "圖表已生成: $PLOT_FILE"

若要使用這個Script,需先安裝GNUplot:

$ sudo apt-get install gnuplot  # Debian/Ubuntu系統
# 或
$ sudo yum install gnuplot      # CentOS/RHEL系統

實用的日誌分析單行命令

以下是一些實用的單行命令,可快速從NGINX日誌中取得特定資訊:

檢視今日存取量

$ grep "$(date +"%d/%b/%Y")" /var/log/nginx/access.log | wc -l

統計HTTP狀態碼分佈

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

識別異常流量(每分鐘超過100次請求)

$ awk '{print substr($4, 2, 17)}' /var/log/nginx/access.log | uniq -c | awk '$1 > 100 {print $0}'

找出最慢的請求

$ awk '$10 > 1 {print $7, $10}' /var/log/nginx/access.log | sort -k2 -nr | head -10

檢測可能的攻擊嘗試

$ grep -E '(\.php|\.asp|\.exe|\.pl|\.cgi|\.scgi)' /var/log/nginx/access.log

進階:結合AWK與SED處理更複雜的分析

對於更複雜的分析需求,可以結合AWK與SED等工具處理日誌:

生成存取者地理分佈資料

我們可以使用第三方地理IP資料函式庫AWK處理:

#!/bin/bash

LOG_FILE="/var/log/nginx/access.log"
GEO_DB="/path/to/GeoLiteCity

Bash:系統管理者的最佳夥伴

為什麼我熱愛 Bash?答案其實很簡單。無論你使用哪種 Linux 發行版,Bash 都已經內建在系統中,這意味著你不需要額外安裝 gccPython 或任何其他工具就能開始工作。在伺服器管理領域,這種「開箱即用」的特性讓 Bash 成為我日常工作中不可或缺的工具。

在這篇文章中,玄貓將帶領大家從零開始,開發一個完整的 Nginx 日誌分析系統,逐步解析從程式碼到建立系統服務的全過程。這個實用工具將幫助你從 Nginx 日誌中提取關鍵指標,並輸出為 JSON 格式,方便後續整合到監控儀錶板或其他分析工具中。

從原始碼開始:Nginx 分析Script

首先,讓我們看核心Script的內容。這個Script會處理 Nginx 的存取日誌,提取重要指標,並將結果輸出為結構化的 JSON 格式。

#!/usr/bin/env bash
# Nginx-analytics.sh - 收集Nginx日誌指標並輸出JSON分析。

LOG_FILE="/var/log/nginx/access.log"
OUTPUT_FILE="/var/www/html/nginx-analytics.json"

# 只是安全檢查:確保 LOG_FILE 存在
if [[ ! -f "$LOG_FILE" ]]; then
  echo "Error: Nginx log file not found at $LOG_FILE"
  exit 1
fi

# 1)按網址存取(前5名)
# - 使用「awk」從「$request」欄位中提取URL(假設它為5美元)。
# - 然後將請求字串「METHOD /some/url HTTP/1.x」拆分為令牌,
#並保留第二個令牌(實際路徑/some/url)。
visits_by_url=$(
  awk '{
    # $5 might be something like: "GET /index.html HTTP/1.1"
    # So we split on space.
    split($5, req_parts, " ")
    url=req_parts[2]
    if (url != "") {
      urls[url]++
    }
  } END {
    # Sort by frequency and pick top 5
    for (u in urls) {
      print urls[u] " " u
    }
  }' "$LOG_FILE" \
  | sort -nr \
  | head -n 5
)

#2)頂級IP位址(頂級5)
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) 頂級HTTP狀態程式(前5名),假設是欄位#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」 -> {「值」:「/home」,「計數」: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}')
    # IMPORTANT NOTE:
    # 如果「值」有空格,請小心處理。 (在最簡單的情況下,它不會。)
    # 如果你有更復雜的解析,你會做更強大的拆分。
    if [ "$first" -eq 1 ]; then
      first=0
    else
      result="$result,"
    fi
    result="$result {\"value\":\"$value\",\"count\":$count}"
  done <<< "$input"
  result="$result]"
  echo "$result"
}

# 存取_by_url -> JSON-陣列
visits_by_url_json=$(lines_to_json_array "$visits_by_url")

# Top_ips -> JSON陣列
top_ips_json=$(lines_to_json_array "$top_ips")

# Top_status_codes -> JSON-陣列
top_status_codes_json=$(lines_to_json_array "$top_status_codes")

#ET VOILA,我們的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(例如,用於網路儀錶板或進一步處理)
echo "$json_output" > "$OUTPUT_FILE"

# 或者,如果您願意,只需回聲到stdout:
# 回聲「$json_output」

exit 0 # you can should to exit with something else, your code, you choose :wink:

內容解密

這個Script看似複雜,但讓我逐一解析其中的關鍵部分:

  1. 設定檔案路徑:Script開始定義了輸入的 Nginx 日誌檔案位置和輸出 JSON 檔案位置。

  2. 安全檢查:確認日誌檔案存在,這是個好習慣,避免後續處理時出錯。

  3. URL 存取統計:使用 awk 從日誌中提取 URL 路徑,計算每個 URL 的存取次數,然後透過管道 (|) 排序並取前 5 名。這裡的關鍵是 awk 中的 split() 函式,它將請求字串分解為部分,以取得實際的 URL 路徑。

  4. IP 地址統計:類別似地,統計最常連線的 IP 地址。

  5. 平均請求時間:計算所有請求的平均處理時間,這是評估網站效能的重要指標。

  6. 平均上游回應時間:計算 Nginx 從上游伺服器取得回應的平均時間。

  7. HTTP 狀態碼統計:分析哪些 HTTP 狀態碼最常出現,有助於識別可能的問題(如 404 錯誤或 500 伺服器錯誤)。

  8. JSON 轉換:最後一部分是將收集的資料轉換為結構化的 JSON 格式。lines_to_json_array() 函式是精髓,它將文字行轉換為 JSON 陣列。

當我在某金融科技公司建立監控系統時,發現這種直接處理日誌的方法比依賴複雜的第三方工具更為可靠和高效,尤其在資源受限的環境中。

將此Script儲存到 /usr/local/bin/nginx-analytics.sh,然後別忘了設定執行許可權:

$ sudo chmod +x /usr/local/bin/nginx-analytics.sh

建立系統服務:讓Script自動執行

擁有Script後,下一步是將它整合到系統中,以便可靠地自動執行。在現代 Linux 系統中,systemd 是管理服務的標準方法。

建立 systemd 服務單元

首先,我們需要建立一個 systemd 服務單元檔案,讓系統知道如何執行我們的Script。在 /etc/systemd/system/nginx-analytics.service 建立以下內容:

[Unit]
Description=Collect Nginx Log Metrics and Output JSON
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nginx-analytics.sh

[Install]
WantedBy=multi-user.target

內容解密

這個服務檔案包含三個主要區段:

  • [Unit]:提供服務描述和依賴關係。After=network.target 表示此服務應在網路可用後啟動。

  • [Service]:定義服務類別和執行命令。Type=oneshot 表示這是一次性執行的服務,執行完就結束,適合我們的日誌分析Script。

  • [Install]:指定服務應在哪個目標(target)下啟用。multi-user.target 是標準的多使用者運作層級。

建立檔案後,需要讓 systemd 重新載入設定以識別新服務:

sudo systemctl daemon-reload

現在可以手動啟動服務來測試:

sudo systemctl start nginx-analytics.service

如果希望系統啟動時自動執行此服務,可以啟用它:

sudo systemctl enable nginx-analytics.service

設定時執行:讓分析保持最新

雖然可以手動執行服務或在系統啟動時執行一次,但對於日誌分析這類別任務,我們通常希望定期自動執行。systemd 提供了計時器(timer)功能,類別似於傳統的 cron 工作但更為整合。

建立 systemd 計時器

/etc/systemd/system/nginx-analytics.timer 建立以下內容:

[Unit]
Description=Run nginx-analytics service periodically

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

[Install]
WantedBy=timers.target

內容解密

這個計時器設定包含:

  • [Timer] 區段中的兩個關鍵設定:
    • OnBootSec=5min:系統啟動後 5 分鐘執行第一次
    • OnUnitActiveSec=1h:之後每小時執行一次

這種設定非常適合日誌分析,因為它提供了定期更新的指標,而不會對系統造成過大負擔。

啟用計時器的命令如下:

sudo systemctl daemon-reload
sudo systemctl enable --now nginx-analytics.timer

enable --now 引數同時啟用計時器並立即啟動它,這樣不必等到下次系統重啟。

實用擴充套件與最佳實踐

在實際佈署這個解決方案時,我發現了幾個值得分享的改進點:

1. 日誌輪轉考量

如果你的系統使用 logrotate 管理 Nginx 日誌,Script可能需要處理多個日誌檔案。修改Script以處理壓縮或輪轉的日誌可以大幅提升其實用性:

# 處理今天和昨天的日誌(如果存在)
LOG_FILES=("/var/log/nginx/access.log" "/var/log/nginx/access.log.1")

2. 效能最佳化

對於高流量網站,日誌檔案可能非常大。在這種情況下,可以考慮使用 tail 命令只分析最近的日誌條目:

# 只分析最近 10000 條日誌
tail -n 10000 "$LOG_FILE" | awk '{ ... }'

3. 輸出格式擴充套件

除了 JSON 外,你可能還需要其他格式的輸出。例如,我曾為某客戶擴充套件這個Script,同時輸出 CSV 格式以便直接匯入到 Excel 中進行進一步分析:

# 建立 CSV 輸出
echo "URL,Count" > "$CSV_OUTPUT_FILE"
echo "$visits_by_url" | awk '{print $2","$1}' >> "$CSV_OUTPUT_FILE"

4. 監控整合

將這個Script的輸出與 Grafana 或其他監控工具整合是非常有價值的。我建議在 JSON 輸出中加入時間戳記:

json_output=$(
  cat <<EOF
{
  "timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
  "visits_by_url": $visits_by_url_json,
  ...
}
EOF
)

這種簡單的擴充套件使得追蹤指標隨時間變化變得更加容易。

超越基本功能

使用這種方法分析 Nginx 日誌只是

為何需要自製Nginx分析工具

在維護網站與Web應用程式的過程中,瞭解使用者行為與伺服器效能是至關重要的。大型網站通常會佈署如Google Analytics或Matomo等專業分析工具,但對於小型專案或個人網站,這些解決方案可能過於複雜或成本過高。

多年來,玄貓在管理小型專案時發現,有時我們只需要一些基本的流量統計資料,如熱門頁面、訪客IP分佈、伺服器回應時間等。這些資訊在Nginx的存取日誌中其實都有記錄,只是需要一種簡便的方式來提取和呈現。

讓我分享一個輕量級的解決方案,只需使用ShellScript就能從Nginx日誌中提取有價值的分析資料,並以JSON格式輸出,方便進一步整合到各種呈現介面中。

核心功能設計

這個分析工具的核心是一個ShellScript,主要實作以下功能:

  1. 分析Nginx存取日誌,提取關鍵資訊
  2. 統計最常存取的URL路徑
  3. 識別最活躍的訪客IP
  4. 計算平均請求處理時間與上游回應時間
  5. 統計HTTP狀態碼分佈
  6. 將分析結果輸出為結構化JSON

這些功能雖然簡單,但足以提供網站流量的基本洞察,而與實作成本極低。

開發分析Script

首先,讓我們來看完整的ShellScript:

#!/bin/bash

# 設定變數
LOG_FILE=${1:-/var/log/nginx/access.log}
OUTPUT_FILE=${2:-/var/www/html/nginx-analytics.json}
TOP_LIMIT=${3:-10}

# 檢查日誌檔案是否存在
if [ ! -f "$LOG_FILE" ]; then
    echo "錯誤: 找不到日誌檔案 $LOG_FILE"
    exit 1
fi

# 分析URL存取次數
echo "正在分析URL存取次數..."
url_visits=$(awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

# 分析最常見的IP位址
echo "正在分析訪客IP..."
top_ips=$(awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

# 計算平均請求時間
echo "正在計算平均處理時間..."
avg_request_time=$(awk -F '"' '{print $4}' "$LOG_FILE" | awk '{sum+=$1; count++} END {printf "%.5f", sum/count}')

# 計算平均上游回應時間
echo "正在計算平均上游回應時間..."
avg_upstream_time=$(awk -F '"' '{print $6}' "$LOG_FILE" | awk '{if($1!="") {sum+=$1; count++}} END {printf "%.5f", sum/count}')

# 分析HTTP狀態碼
echo "正在統計HTTP狀態碼..."
status_codes=$(awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

# 輸出JSON
echo "正在生成JSON報告..."
cat > "$OUTPUT_FILE" << EOF
{
  "visits_by_url": [
    $url_visits
  ],
  "top_ips": [
    $top_ips
  ],
  "avg_request_time": "$avg_request_time",
  "avg_upstream_time": "$avg_upstream_time",
  "top_status_codes": [
    $status_codes
  ]
}
EOF

echo "分析完成! 結果已儲存至 $OUTPUT_FILE"

Script解析

讓我們逐步解析這個Script的運作原理:

引數設定

Script開始部分設定了三個關鍵引數:

  • LOG_FILE:Nginx存取日誌的位置,預設為標準位置
  • OUTPUT_FILE:分析結果的輸出JSON檔案位置
  • TOP_LIMIT:各項統計的前N名數量
LOG_FILE=${1:-/var/log/nginx/access.log}
OUTPUT_FILE=${2:-/var/www/html/nginx-analytics.json}
TOP_LIMIT=${3:-10}

這裡使用了Bash的引數預設值語法,允許使用者在執行時覆寫這些設定,提高了Script的靈活性。

URL存取分析

這部分程式碼分析了哪些URL路徑被存取最多:

url_visits=$(awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

這行指令使用awk從每行日誌中提取URL路徑(通常是第7個欄位),然後排序、計數、再次排序(依數量降序),最後取前N名並格式化為JSON物件陣列的元素。

IP位址分析

類別似地,此部分統計了最常造訪網站的IP位址:

top_ips=$(awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

此處我們關注日誌的第一個欄位,它通常是訪客的IP位址。

平均時間計算

這兩部分計算了平均請求處理時間和上游回應時間:

avg_request_time=$(awk -F '"' '{print $4}' "$LOG_FILE" | awk '{sum+=$1; count++} END {printf "%.5f", sum/count}')
avg_upstream_time=$(awk -F '"' '{print $6}' "$LOG_FILE" | awk '{if($1!="") {sum+=$1; count++}} END {printf "%.5f", sum/count}')

這裡我們假設Nginx日誌使用了標準格式,其中請求時間和上游回應時間分別在雙引號分隔的第4和第6個欄位。

HTTP狀態碼分析

此部分統計了各HTTP狀態碼的分佈:

status_codes=$(awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n "$TOP_LIMIT" | awk '{print "{\"value\":\"" $2 "\",\"count\":" $1 "}"}' | paste -sd "," -)

HTTP狀態碼通常在日誌的第9個欄位,這對於監控網站錯誤和異常情況非常有價值。

生成JSON輸出

最後,Script將所有分析結果組裝成一個結構化的JSON檔案:

cat > "$OUTPUT_FILE" << EOF
{
  "visits_by_url": [
    $url_visits
  ],
  "top_ips": [
    $top_ips
  ],
  "avg_request_time": "$avg_request_time",
  "avg_upstream_time": "$avg_upstream_time",
  "top_status_codes": [
    $status_codes
  ]
}
EOF

這種結構化格式使得分析結果可以輕鬆整合到網頁介面或其他監控系統中。

自動化與整合

要使此分析工具真正實用,我們可以將其設定為定時執行:

# 編輯crontab
crontab -e

# 每小時執行一次分析
0 * * * * /path/to/nginx-analytics.sh

這樣一來,我們就能每小時獲得一次更新的網站流量分析。

視覺化分析結果

有了JSON格式的分析資料,我們可以輕鬆建立一個簡單的HTML頁面來視覺化這些資訊。以下是一個基本的HTML+CSS+JavaScript實作:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>NGINX-ANALYTICS</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 20px; background: #f7f7f7; color: #333; } 
    h1, h2, h3 { margin-top: 1em; margin-bottom: 0.5em; } 
    .stats-container { display: flex; flex-wrap: wrap; gap: 2rem; } 
    .stats-block { background: #fff; padding: 1rem; border-radius: 6px; box-shadow: 0 2px 5px rgba(0,0,0,0.15); flex: 1 1 350px; max-width: 500px; } 
    table { width: 100%; border-collapse: collapse; margin-bottom: 1em; } 
    th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; } 
    th { background-color: #fafafa; } 
    .bar-chart { position: relative; height: 20px; background: #eee; border-radius: 4px; overflow: hidden; } 
    .bar { position: absolute; left: 0; top: 0; bottom: 0; background: #007acc; text-align: right; color: #fff; padding-right: 4px; font-size: 13px; line-height: 20px; } 
    .numeric { font-weight: bold; color: #007acc; display: inline-block; margin-left: 0.3em; } 
    @media (max-width: 600px) { .stats-container { flex-direction: column; } }
  </style>
</head>
<body>
  <h1>NGINX-ANALYTICS 日誌視覺化</h1>
  <p>此頁面取得 <code>nginx-analytics.json</code> 並呈現以下指標。</p>
  <small>by 玄貓(BlackCat)</small>

  <div class="stats-container">
    <!-- URL區塊 -->
    <div class="stats-block">
      <h2>熱門URL</h2>
      <table id="table-urls">
        <thead>
          <tr>
            <th>URL路徑</th>
            <th>存取次數</th>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>

    <!-- IP區塊 -->
    <div class="stats-block">
      <h2>訪客IP</h2>
      <table id="table-ips">
        <thead>
          <tr>
            <th>IP位址</th>
            <th>存取次數</th>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>

    <!-- 狀態碼區塊 -->
    <div class="stats-block">
      <h2>HTTP狀態碼</h2>
      <table id="table-status-codes">
        <thead>
          <tr>
            <th>狀態碼</th>
            <th>次數</th>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>

    <!-- 時間區塊 -->
    <div class="stats-block">
      <h2>處理時間</h2>
      <p>平均請求時間: <span id="avg-request-time" class="numeric">--</span></p>
      <p>平均上游時間: <span id="avg-upstream-time" class="numeric">--</span></p>
    </div>
  </div>

  <script>
    function populateTable(tableBody, data) {
      if (!data || data.length === 0) {
        const row = document.createElement('tr');
        row.innerHTML = '<td colspan="2">無資料</td>';
        tableBody.appendChild(row);
        return;
      }
      
      // 找出最大計數以計算比例
      const maxCount = Math.max(...data.map(item => item.count));
      
      data.forEach(item => {
        const row = document.createElement('tr');
        const valueCell = document.createElement('td');
        const countCell = document.createElement('td');
        
        valueCell.textContent = item.value;
        
        // 建立長條圖
        const barContainer = document.createElement('div');
        barContainer.className = 'bar-chart';
        
        const bar = document.createElement('div');
        bar.className = 'bar';
        bar.style.width = `${(item.count / maxCount) * 100}%`;
        bar.textContent = item.count;
        
        barContainer.appendChild(bar);
        countCell.appendChild(barContainer);
        
        row.appendChild(valueCell);
        row.appendChild(countCell);
        tableBody.appendChild(row);
      });
    }

    fetch('/nginx-analytics.json')
      .then(response => {
        if (!response.ok) {
          throw new Error('無法取得nginx-analytics.json');
        }
        return response.json();
      })
      .then(data => {
        // 1) 填充熱門URL
        const urlTableBody = document.querySelector('#table-urls

在管理自己的網站時,瞭解訪客行為是最佳化內容與設計的關鍵。市面上雖不乏現成的分析工具,但它們往往龐大複雜、需要加入追蹤程式碼,甚至收集過多的訪客資料。作為注重隱私的開發者,我一直在尋找更簡潔的解決方案,直到發現 NGINX 的存取日誌本身就是寶貴的資料來源。 過去幾年間,我在多個專案中嘗試了從 Google Analytics 到各種開放原始碼工具的解決方案,但總覺得它們過於複雜。若只需要基本的頁面存取統計,為什麼不直接利用已經存在的伺服器日誌呢?這個想法促使我開發了一套純 NGINX 日誌分析系統。