nginxのアクセスログを解析する正規表現

nginxのアクセスログ access.log を解析するための正規表現をメモしておきます。

目次

正規表現

nginxのアクセスログの行フォーマットはこんな感じになっています:

xxx.xxx.xxx.xxx - - [29/May/2019:00:57:08 +0900] "GET / HTTP/2.0" 200 34689 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
...

それを解析するための正規表現はこちらになります:

/^(.+) - (.+) \[(.+)\] "([^\"]+)" (\d+) (\d+) "([^\"]+)" "([^\"]+)"$/

実際にまずは一行だけ JavaScript を使って解析してみましょう:

const line = 'xxx.xxx.xxx.xxx - - [29/May/2019:00:57:08 +0900] "GET / HTTP/2.0" 200 34689 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"';

const regex = /^(.+) - (.+) \[(.+)\] "([^\"]+)" (\d+) (\d+) "([^\"]+)" "([^\"]+)"$/;

const [
  src, // 元々の入力データ
  ip, // リモートIPアドレス
  user, // リモートユーザ
  time, // 日時
  page, // HTTPメソッドとアクセスされたパス
  code, // ステータスコード
  size, // データサイズ
  referer, // Referer
  ua, // User-Agent
] = line.match(regex);

console.log("ip:", ip);
console.log("user:", user);
console.log("time:", time);
console.log("page:", page);
console.log("code:", code);
console.log("size:", size);
console.log("referer:", referer);
console.log("ua:", ua);

出力結果:

ip: xxx.xxx.xxx.xxx
user: -
time: 29/May/2019:00:57:08 +0900
page: GET / HTTP/2.0
code: 200
size: 34689
referer: https://www.google.com/
ua: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36

アクセス解析

それでは、実際のアクセスログファイルを Node.js を使って簡単に解析してみましょう。 アクセス回数の多い順にIPアドレスを抽出し、さらにページビューとユニークアクセス数を出力してみます。

access.log (解析対象のアクセスログファイル)

aaa.aaa.aaa.aaa - - [06/Jun/2019:21:01:58 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
bbb.bbb.bbb.bbb - - [06/Jun/2019:23:19:07 +0900] "GET / HTTP/1.1" 200 612 "-" "-"
ccc.ccc.ccc.ccc - - [06/Jun/2019:23:44:27 +0900] "GET /xxx/yyy/zzz HTTP/1.1" 200 10068 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
ddd.ddd.ddd.ddd - - [06/Jun/2019:23:44:30 +0900] "GET / HTTP/1.1" 200 6281 "-" "Mozilla/5.0 zgrab/0.x"
eee.eee.eee.eee - - [07/Jun/2019:00:21:21 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"
fff.fff.fff.fff - - [07/Jun/2019:02:15:50 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
ggg.ggg.ggg.ggg - - [07/Jun/2019:05:46:41 +0900] "GET / HTTP/2.0" 200 6554 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
ggg.ggg.ggg.ggg - - [07/Jun/2019:05:46:42 +0900] "GET /xxx/yyy/zzz HTTP/2.0" 304 234 "https://www.example.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
hhh.hhh.hhh.hhh - - [07/Jun/2019:06:16:02 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

analyze.js (解析用コード)

const logfile = process.argv[2]; // ログファイルの存在チェックは省略
const fs = require("fs");
const arr = fs.readFileSync(logfile).toString().trim().split("\n");
const regex = /^(.+) - (.+) \[(.+)\] "([^\"]+)" (\d+) (\d+) "([^\"]+)" "([^\"]+)"$/;

// 解析
const result = analyze(arr);

// 結果出力
let pv = 0;// ページビュー
const uv = result.length; // ユニークアクセス
for(let len = result.length, i=0;i<len;i++){
    const item = result[i];
    console.log(item.ip + " : " + item.count);
    pv += item.count;
}
console.log(`\nページビュー: ${pv}`);
console.log(`ユニークアクセス: ${uv}`); 

/** ユニークIPアドレス一覧とそのアクセス数を取得 */
function analyze(arr) {
  const ret = [ /* { ip, count } */ ];
  for (let len = arr.length, i = 0; i < len; i++) {
    const line = arr[i].trim();
    const [
      src, // 元々の入力データ
      ip, // リモートIPアドレス
      user, // リモートユーザ
      time, // 日時
      page, // HTTPメソッドとアクセスされたパス
      code, // ステータスコード
      size, // データサイズ
      referer, // Referer
      ua, // User-Agent
    ] = line.match(regex);
    const m = ret.find(x => x.ip === ip);
    if (m) {
      m.count += 1;
    } else {
      ret.push({ ip, count: 1 });
    }
  }
  // アクセス数で降順ソート
  ret.sort((a, b) => {
    return b.count - a.count;
  });
  return ret;
}

では、実行してみましょう:

$ node analyze.js ./access.log

bbb.bbb.bbb.bbb : 6
ggg.ggg.ggg.ggg : 2
aaa.aaa.aaa.aaa : 1
ccc.ccc.ccc.ccc : 1
ddd.ddd.ddd.ddd : 1
eee.eee.eee.eee : 1
fff.fff.fff.fff : 1
hhh.hhh.hhh.hhh : 1

ページビュー: 14
ユニークアクセス: 8

解析結果が表示されました。

NOTE: 今回は横着をしてログファイルを一気にメモリに読み込んでますが、実際に巨大なログファイルを扱う場合は Node.js の Stream API を使うなどし、メモリに配慮したコーディングをした方が良いでしょう。

以上です。

芽萌丸プログラミング部 @programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。