mattintosh note

どこかのエンジニアモドキの備忘録

2024-06-05: 現在ホビー関連の記事を 新しいブログ に移行しています(一部の国、ISP からは閲覧できません)

Xserver(シンレンタルサーバー)のアクセスログのパース

Xserver のアクセスログlltsv で読み取れるように ltsv 形式に変換する。awkprintf() は改行しようとすると制約があるっぽい。

~/.bashrc

apacheparse() {
    awk -f /dev/fd/3 3<<'!' | nkf --url-input
BEGIN {
    OFS="\t"
}
{
    match($0, /^(\S+) (\S+) (\S+) (\S+) (\[.+?\]) "([A-Z]+) (\S+) (\S+)" ([0-9]{3}) ([0-9]+) "([^"]+?)" "([^"]+?)"$/, m)
    split(m[7], uri, "?")
    gsub("\\\\x", "%", m[12])
    print("vhost:" m[1],
           "host:" m[2],
          "ident:" m[3],
           "user:" m[4],
           "time:" m[5],
         "method:" m[6],
           "path:" uri[1],
          "query:" uri[2],
       "protocol:" m[8],
         "status:" m[9],
           "size:" m[10],
        "referer:" m[11],
             "ua:" m[12])
}
!
}

WordPress の場合、パーマリンクに記事タイトルを使用していると 記事タイトル%E8%A8%98%E4%BA%8B%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB のようにエンコードされる。これだけ見ても何の記事かわからないので nkf でデコードする。nkf はシンレンタルサーバーだと /usr/bin/nkf にある(エックスサーバーは未確認だけどほぼ同じなのであると思う)。

gsub("\\\\x", "%", m[12]) に関しては Cloudflare でヘッダーを変換しているとき用に入れているので必須ではない。Cloudflare で ip.src.regionip.src.city を書き出していると例えば「兵庫」の場合に ip.src.region の値は "Hy\xC5\x8Dgo" になってしまう。nkf\xC5\x8D のような 16 進数を変換してくれないので gsub()%C5%8D に置き換える。そうすると Hyōgo として出力してくれるようになる。

わざわざヒアドキュメントを使って AWKスクリプトを生成しているのはちょっと前に上の変換を printf コマンドを使ってやっていたんだけどなんか知らんが match() の中で "" を使うと [0-9]{1,3} のような正規表現がマッチしなくなってしまったためその影響を受けないようにするため。

試してみたけど AWKmatch() には名前付きグループ ?P やグループをキャプチャリングしない ?: が無いっぽい。パスとクエリの分割を match() でやろうとすると正規表現(\/[^\s?]*)(\?(\S*))? みたいなものになるんだろうけど単純に split()? で分割した方が楽。

使い方はログファイルを標準入力で読み込ませる。

gzip -dc ログファイル.gz | apacheparse

パース前のログ。

hobby.mattintosh-note.jp 172.70.43.92 - - [05/Jul/2024:01:01:40 +0900] "GET /tag/%E3%83%96%E3%83%AC%E3%83%9E%E3%83%BC%E3%83%88%E3%83%B3-%E3%82%A2%E3%82%BA%E3%83%BC%E3%83%AB%E3%83%AC%E3%83%BC%E3%83%B3/ HTTP/2.0" 200 132264 "-" "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.175 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

パース後のログ。

vhost:hobby.mattintosh-note.jp  host:172.70.43.92       ident:- user:-  time:[05/Jul/2024:01:01:40 +0900]       method:GET      path:/tag/ブレマートン-アズールレーン/  query:  protocol:HTTP/2.0       status:200      size:132264     referer:-       ua:Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.175 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)

PHP を使う場合はこんな感じだろうか。これも php -r を使用すると ""エスケープ等が面倒になるのでヒアドキュメントを使用する。

~/.bashrc

apacheparse() {
    php -f /dev/fd/3 3<<'!'
<?php

while (($line = fgets(STDIN)) !== false) {
    if (preg_match('/^(\S+) (\S+) (\S+) (\S+) (\[.+?\]) "(\S+) (\S+) (\S+)" (\d{3}) (\d+) "(\S+)" "(.+?)"$/', $line, $matches)) {
        $path    = urldecode(parse_url($matches[7], PHP_URL_PATH));
        $query   = urldecode(parse_url($matches[7], PHP_URL_QUERY));
        $referer = urldecode($matches[11]);
        echo implode("\t", array(
               'vhost:' . $matches[1],
                'host:' . $matches[2],
               'ident:' . $matches[3],
                'user:' . $matches[4],
                'time:' . $matches[5],
              'method:' . $matches[6],
                'path:' . $path,
               'query:' . $query,
            'protocol:' . $matches[8],
              'status:' . $matches[9],
                'size:' . $matches[10],
             'referer:' . $referer,
                  'ua:' . $matches[12],
        )) . PHP_EOL;
    } else {
        echo $line . PHP_EOL;
    }
}
!
}