Nginxのアクセスログを加工集計する


ちょっと必要に迫られて調べていたので簡単にまとめました。なお、Nginxのログフォーマットはデフォルトのままにしており、こんな感じのログが記録されています。

```
xxx.xxx.xx.xxx - - [01/Sep/2015:00:00:00 +0900] "GET /xxxx/xxxx HTTP/1.1" 200 12700 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
```

結果だけ知りたい方のために、どうやったのか先に書いておきます

```sh
awk '($7 !~ /(jpg|woff|svg|png|css|js|gif|eot|php|ico|ttf)/)' access.log | awk '{print $1,",",$12,$13,$14,$15,$16,$17,",",$7 }' | sort| uniq -c | sort -rn | head -n 25
```

## やりたかったこと

- 各ページへのアクセスがだいたいどの程度あるのか
- 可能なら各ページｘIPアドレスという形で集計したい

Nginx自体にそれほど精通してない＆こういうログ集計でよく利用されるUnixコマンド（awkとか）の使い方をよく知らないので以下を参考にしながら作業していきました

- [Parsing access.log and error.logs using linux commands](https://rtcamp.com/tutorials/nginx/log-parsing/)
    - 先頭から何番目のフィールドがどういう値を保持するのかというのがわからなかったので参考になった
- [Processing Apache and Nginx Access Logs](http://eddmann.com/posts/processing-apache-and-nginx-access-logs/)

## 試行錯誤した結果を覚えてる範囲でまとめる


### 最初におこなった作業

まず、http statusが200のものを抽出しつつIPアドレス別にアクセス数をまとめるために以下のようにしてみました

```sh
cat access.log | awk '($9 ~ /200/)' | awk '{ print $1,$7 }' | sort | uniq -c | sort -rn | head -n 25
```

上記のコマンドですが、こんな感じの意味になるかと思います

- アクセスログを読み込み、Unixのパイプで次に処理をうつす
- 読み込んだログでhttp statusが200に該当する箇所をawkで正規表現使って抽出し、Unixのパイプで次に処理をうつす
    - $9という所。半角スペースで句切られるから先頭から9番目という感じの理解をしてますがあまりここ自信がない
- IPアドレス（$1）と実際にアクセスがあったパス（$7）を出力しUnixのパイプで次に処理をうつす
- sortで並べ替えてUnixのパイプで次に処理をうつす
- [uniqコマンドを用いて重複した行の数をカウントし](http://kappa-bioinformatics.blogspot.jp/2011/11/uniq.html)Unixのパイプで次に処理をうつす
- sortのrnオプションを付けて数値として逆順でソートしUnixのパイプで次に処理をうつす
- headコマンドでトップ25件のみ表示する

実際の出力結果はこんな感じでした

```sh
  28 100.200.10.200 /
  86 49.212.144.52 /
  47 61.114.52.30 //uploads/117763.jpg
  44 61.114.52.30 //uploads/115879.jpg
  # 以下省略
```

### 静的ファイルなどへのアクセスを除外して加工する

上記実行したら画像ファイルへのアクセスばっかりになってしまったので、画像などの静的ファイル＋α（拡張子PHPへのアクセスが一部あったので）を除外する方法を調べてみました

最初に書いたやつで

```sh
awk '($9 ~ /200/)'
```

というのが正規表現を駆使した箇所だったのでこれを応用してまずは

```sh
awk '($7 !~ /(jpg|woff|svg|png|css|js|gif|eot|php|ico|ttf)/)' access.log | head -n 10
```

という感じで意図した値が取得できそうか確認してみたところ、静的ファイルなどへのアクセスは除外された形でうまく値が取得できていました



### 最終形

ここまでの作業内容をふまえて

```sh
awk '($7 !~ /(jpg|woff|svg|png|css|js|gif|eot|php|ico|ttf)/)' access.log
```

という結果に対して

- アクセス元のIPアドレス、UserAgent、アクセス先のパスの情報をカンマ区切りで出力してUnixのパイプで次に処理をうつす
- sortで並べ替えてUnixのパイプで次に処理をうつす
- uniqコマンドを用いて重複した行の数をカウントしUnixのパイプで次に処理をうつす
- sortのrnオプションを付けて数値として逆順でソートしUnixのパイプで次に処理をうつす
- headコマンドでトップ25件のみ表示する

ということをするために

```sh
awk '($7 !~ /(jpg|woff|svg|png|css|js|gif|eot|php|ico|ttf)/)' access.log | awk '{print $1,",",$12,$13,$14,$15,$16,$17,",",$7 }' | sort| uniq -c | sort -rn | head -n 25
```

という感じにしあげて、意図した結果が得られました


このデーターをGoogle スプレッドシートに読み込んで・・と思って

```sh
awk '($7 !~ /(jpg|woff|svg|png|css|js|gif|eot|php|ico|ttf)/)' access.log | awk '{print $1,",",$12,$13,$14,$15,$16,$17,",",$7 }' | sort| uniq -c | sort -rn > /Users/xxxxx/Desktop/output.csv
```

としたのですが、日付が

> 01/Sep/2015:00:00:00 +0900

という形式でこのままだと日付として認識してくれないんですよね・・・・

なので

- 01/Sep/2015のSepの所をテキストエディタなどで数値に置き換える
- 置き換えた後に、以下のようなRubyで書いたスクリプトを通じて日付型に変換

```ruby
CSV.open("/Users/xxxxx/Desktop/replace.csv", "wb") do |csv|
  CSV.foreach("/Users/xxxxx/Desktop/output.csv",{ :encoding => "UTF-8", :col_sep => ","}) do |row|
    csv << [ row[0], DateTime.strptime(row[1],'%d/%m/%Y:%H:%M:%S').strftime("%Y/%m/%d %H:%M:%S"), row[2], row[3]]
  end
end
```

という感じで日付の変換をしてあげるとうまくいくかと思います。