nginxのログを解析する

nginxのログ分析

業務でnginxのログを分析する必要が出てきたので、調査してみました。同じようなことは誰かやっていそうなのですが、あまり情報が出てきません…なのでRubyで簡単に扱えるようなものを作ろうと思いました。

nginxのログフォーマットの確認

まずはnginxのログフォーマットを確認します。nginx.confを確認します。

1
2
3
log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$upstream_response_time" "$request_time"';

この形式はデフォルトなんですかね…

CSVクラスの利用

どのようにして処理をしようかと考えて、CSVクラスを利用することにしました。CSVクラスはセパレータをcol_sepで指定できますし、ダブルクォートで囲まれたセパレータは無視してくれます。

時刻のセパレータを変換

1点困った点がありました。時刻のカラムの処理です。時刻のカラムは

1
[12/Feb/2021:03:20:08 +0900]

と言ったように、[]で囲まれ、+0900の前には空白があります。これをうまく一つのカラムとして処理しなければなりません。log_formatを適切に設定しておけばよかったかもしれませんが、現状こうなってしまっているのでなんとかしなければいけません。

そこで[]""に変換すれば時刻もダブルクォートで囲うことができます。そのように変換するようにします。

クラスの作成

今回のメインの目的はremote_addrやx_fowarded_forの取得だったので、以下のようなクラスを作成しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require 'csv'

class NginxLog
def initialize(raw)
tmp = raw.gsub(' [', ' "').gsub('] ', '" ')
@log = CSV.parse(tmp, col_sep: ' ', liberal_parsing: true).flatten
end

def remote_addr
@log[0]
end

def x_forwarded_for_ips
ips = @log[9].split(',').map &:strip
ips.reject { |ip| ['-', 'unknown'].include? ip }
end

def remote_ips
x_forwarded_for_ips << remote_addr
end
end

コンストラクタの引数に生のログを1行いれてもらえれば、必要な情報を取得できるようにします。

1
2
3
4
log = '1.1.1.1 - - [12/Feb/2021:23:48:48 +0900] "GET / HTTP/1.1" 200 38485 "https://book-reviews.blog/" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Mobile/15E148 Safari/604.1" "2.2.2.2" "0.403" "0.404"'
nginx_log = NginxLog(log)
puts nginx_log.remote_ips
=> ["1.1.1.1", "2.2.2.2"]

まとめ

今回はCSVクラスを使ってnginxのログを解析してみました。CSVクラスはCSVだけでなく、いろんな形式のファイルに利用できそうです。

本来であればログ解析基盤にログを集約し、いつでも分析できるような状況を整えておくのがよいかと思います。