ユーザ認証が存在するWebアプリのアクセスログを有効に分析するためには、
・Webサーバからアクセスログを集める
・Webアプリのユーザとアクセスログを紐付ける
の2つを行う必要があります。

この記事では、
・Webサーバ: nginx
・開発フレームワーク: Ruby on Rails (認証プラグインとしてdevise)
・ログ集約: fluentd
・ログの格納先: Amazon S3
という構成のWebアプリケーションで、
「アクセスログの収集と蓄積」を行う流れについて説明します。
サーバOSは、CentOSを利用します。

アクセスログの収集から蓄積までは以下の図の流れになります。

アクセスログの出力:
 ①ユーザの訪問
 ②アプリから会員ID情報を送信
  →RailsアプリからHTTPヘッダに会員IDを付加して返却
   付加したヘッダはnginxでログ出力後に削除(ユーザに付加した情報は見えない)
 ③アクセスログに会員ID・トラッキング用cookie情報を出力
 ④tracking用のcookie情報を送信
  →会員でない場合でもクライアントを特定できるよう
   tracking用のCookie情報を付加

アクセスログの収集・分析:
 ①ログを監視し、条件に合うログを抽出
 ②定期的にS3に転送
  →fluentdでログを監視、定期的にS3に転送

文章だけではわかりにくいかと思ったので、概要についてスライドにもまとめています。

この構成を構築する手順を次の順で説明していきます。

  1. CentOSへのnginx、rubyのインストール
  2. Railsアプリ作成とdeviseによる認証機能の作成
  3. 詳細なログを出力するためのnginxの設定
  4. fluentdの導入とログ収集・S3への転送設定

1. CentOSへのnginx、rubyのインストール

EPELの追加

CentOS標準のリポジトリだとnginxが入っていないので、
以下のように、EPELをリポジトリに追加します。

$ sudo wget http://ftp-srv2.kddilabs.jp/Linux/distributions/fedora/epel/6/x86_64/epel-release-6-5.noarch.rpm
$ sudo rpm -ivh epel-release-6-5.noarch.rpm

参考)CentOS 外部レポジトリの追加(EPEL)
 http://www.tooyama.org/yum-addrepo-epel.html

nginxのインストール

EPELの追加が終わったら、以下のコマンドでnginxをインストールします。

$ sudo yum install nginx

Rubyのインストール

ここでは最新版のRubyを利用したいので、ソースからコンパイルします。

まずはコンパイルに必要なパッケージ群をインストールします。
(おそらく、このくらい入れとけば大丈夫じゃないかなと思います)

$ sudo yum install gcc-c++ patch readline readline-devel zlib zlib-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison sqlite-devel mysql-devel libxslt-devel httpd-devel curl-devel ncurses-devel gdbm-devel
$ sudo yum install libyaml-devel

Rubyのソースをダウンロード、コンパイル&インストールします。

$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p125.tar.gz
$ tar zxf ruby-1.9.3-p125.tar.gz
$ cd ruby-1.9.3-p125
$ ./configure --prefix=/usr
$ make
$ sudo make install

2. Railsアプリ作成とdeviseによる認証機能の作成

# このページの流れの大部分はこの記事と同じです。
# 説明を省いているので、不明なところはこちらの記事も参照してください。
#  http://takemikami.com/technote/archives/653

Railsのインストール

最新のRuby on Railsをインストールします。

$ gem install rails
====
Successfully installed rails-3.2.8
====

Railsアプリの作成

rails newでrailsアプリを作成します。

$ rails new app
create
create  README.rdoc
create  Rakefile
====

Gemfileのtherubyracerのgemを以下のようにコメントアウトして、有効化。

==  ==
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', :platforms => :ruby
==  ==

bundle installして、rails sでサーバを起動します。

$ bundle install
====
Installing therubyracer (0.10.2) with native extensions
====
$ rails s
=> Booting WEBrick
=> Rails 3.2.8 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2012-08-21 11:16:47] INFO  WEBrick 1.3.1

[2012-08-21 11:16:47] INFO WEBrick::HTTPServer#start: pid=1837 port=3000


この状態で、ブラウザから
 http://(ホスト名orIPアドレス):3000/
でアクセスして「Welcome aboard」が表示されることを確認します。
ここまでで、素のrailsアプリ作成が完了。


## deviseによる認証機能の追加

次にdeviseを使って、ユーザの認証機能を追加します。
まずは、Gemfileにdeviseのgemを追記。


```ruby  
== 略 ==  
gem 'devise'  
== 略 ==  

その後、bundle install, devise:installしてdeviseをインストールします。 ユーザ用のモデルとビューを生成します。 ここでは「user」というモデルを作っています。

$ bundle install  
====  
Installing orm_adapter (0.4.0)  
Installing warden (1.2.1)  
Installing devise (2.1.2)  
====  
$ rails generate devise:install  
create  config/initializers/devise.rb  
create  config/locales/devise.en.yml  
====  
$ rails generate devise:views  
invoke  Devise::Generators::SharedViewsGenerator  
create    app/views/devise/shared  
create    app/views/devise/shared/_links.erb  
====  
$ rails generate devise user  
invoke  active_record  
create    db/migrate/20120821023255_devise_create_users.rb  
create    app/models/user.rb  
====  
$ rake db:migrate  
==  DeviseCreateUsers: migrating ==============================================  
-- create_table(:users)  
-> 0.0059s  
-- add_index(:users, :email, {:unique=>true})  
-> 0.0138s  
-- add_index(:users, :reset_password_token, {:unique=>true})  
-> 0.0010s  
==  DeviseCreateUsers: migrated (0.0210s) =====================================  

各ページにdeviseに関するアラートを表示するように、 「app/views/layouts/application.html.erb」に 以下のように、notice, alert表示の2行を追記します。

<body>  
<p class="notice"><%= notice %></p>  
<p class="alert"><%= alert %></p>  
<%= yield %>  
</body>  

ログイン状態に応じて、 ログイン・ログアウトボタンを表示するトップページを作成します。

$ rails g controller home index  
create  app/controllers/home_controller.rb  
route  get "home/index"  
====  

「app/views/home/index.html.erb」を以下のように編集し、 ログイン・ログアウトボタンを設置します。

<% if user_signed_in? %>  
<%= link_to "ログアウト", destroy_user_session_path, :method => 'delete' %>  
<%= link_to "ユーザ情報の編集", edit_user_registration_path %>  
<% else %>  
<%= link_to "ログイン", new_user_session_path %>  
<%= link_to "登録", new_user_registration_path %>  
<% end %>  

「config/route.rb」に以下の行を追加して、ルートページを「/home/index」にします。

root :to => "home#index"  

デフォルトのルートページは、リネームしておきます。

$ mv public/index.html public/index.html.bak  

ここまで生成したら、再度「rails s」でrailsを起動。 以下のURLにアクセスすると ログイン・登録と表示されたルーとページが参照できます。  http://(ホスト名orIPアドレス):3000/ 登録から進み、メールアドレスとパスワードを入力して会員登録します。

会員登録後、ログイン・ログアウトができることを確認します。

HTTPヘッダにユーザIDを追加

railsアプリからnginxに対してユーザIDの情報を引き渡すため HTTPヘッダーにユーザIDの情報を付加します。

# ここで追加したヘッダーはnginxで削除してブラウザに返却するので、
# ユーザには見えません(インターネット上の経路には流出しません)。

「app/controllers/application_controller.rb」を以下のように編集します。

class ApplicationController < ActionController::Base  
protect_from_forgery  
after_filter :add_userid_toheader  
def add_userid_toheader  
response.headers["X-App-Userid"] = "#{user_signed_in? ? session['warden.user.user.key'][1][0] : nil}"  
end  
end  

以下のコマンドを入力し、「X-App-Userid」ヘッダが返却されることを確認します。

$ curl -I http://(ホスト名orIPアドレス):3000/  
HTTP/1.1 200 OK  
X-App-Userid:  
Content-Type: text/html; charset=utf-8  
X-Ua-Compatible: IE=Edge  
====  

3. 詳細なログを出力するためのnginxの設定

railsアプリのフロントにnginxを設置

「/etc/nginx/conf.d/default.conf」を以下のとおり編集し、 railsアプリのフロントにnginxを設置します。

\#  
\# The default server  
\#  
upstream rails_app {  
server 127.0.0.1:3000;  
}  
server {  
listen       80;  
server_name  _;  

location / {  
proxy_pass http://rails_app/;  
\#        root   /usr/share/nginx/html;  
\#        index  index.html index.htm;  
}  

error_page  404              /404.html;  
location = /404.html {  
root   /usr/share/nginx/html;  
}  

\# redirect server error pages to the static page /50x.html  
\#  
error_page   500 502 503 504  /50x.html;  
location = /50x.html {  
root   /usr/share/nginx/html;  
}  
}  

nginxを再起動して設定変更を有効にします。

$ service nginx restart  

この状態で、ブラウザから  http://(ホスト名orIPアドレス)/ でアクセスしてrailsアプリのルートが表示されることを確認します。

nginxに詳細ログの出力設定を追加

「/etc/nginx/conf.d/default.conf」を以下のとおり編集し、 nginxで詳細ログを出力するように設定します。 この設定ファイルで、次の設定を行っています。 ①tracking用のCookie情報を付加  →userid on;   userid_name uid;   userid_path /;   userid_expires 365d; ②Railsアプリで付加した会員ID情報を削除  →proxy_hide_header X-App-Userid; ③詳細ログを出力  →log_format detail_accesslog …..   access_log /var/log/nginx/detail_access.log detail_accesslog;

\#  
\# The default server  
\#  
upstream rails_app {  
server 127.0.0.1:3000;  
}  
server {  
listen       80;  
server_name  _;  

location / {  
proxy_pass http://rails_app/;  
\#        root   /usr/share/nginx/html;  
\#        index  index.html index.htm;  
}  

error_page  404              /404.html;  
location = /404.html {  
root   /usr/share/nginx/html;  
}  

\# redirect server error pages to the static page /50x.html  
\#  
error_page   500 502 503 504  /50x.html;  
location = /50x.html {  
root   /usr/share/nginx/html;  
}  

userid on;  
userid_name uid;  
userid_path /;  
userid_expires 365d;  
proxy_hide_header X-App-Userid;  

log_format detail_accesslog '"$time_local","$remote_addr","$uid_got","$upstream_http_x_app_userid","$remote_user","$request","$status","$body_bytes_sent","$http_referer","$http_user_agent","$http_x_forwarded_for","$gzip_ratio"';  
access_log /var/log/nginx/detail_access.log detail_accesslog;  
}  

nginxを再起動して設定変更を有効にします。

$ service nginx restart  

この状態で、ブラウザから  http://(ホスト名orIPアドレス)/ でアクセスして、次のようなログが出力されることを確認します。

“21/Aug/2012:14:10:09 +0900”,“192.168.199.1”,“uid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,“2”,"-",“GET / HTTP/1.1”,“304”,“0”,"-",“Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.79 Safari/537.1”,"-","-"

注目する出力値: ・“uid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX” ← trackingのcookie情報 ・“2” ← 会員ID

ここまでで、nginxからのログの出力設定は完了です。

4. fluentdの導入とログ収集・S3への転送設定

fluentdのインストール

fluentd及びS3用プラグインをインストールします。

$ gem install fluentd  
==  
Successfully installed fluentd-0.10.25  
==  
$ gem install fluent-plugin-s3  
==  
Successfully installed fluent-plugin-s3-0.2.2  
==  

fluentdへのログ収集・転送設定

「/etc/fluent/fluent.conf」を以下の通り編集し、 ログの収集とS3への転送を設定します。 ・source部で、収集対象となるログを設定  ・pathに対象ファイルを指定  ・formatで収集する行を正規表現で指定 ・match部で、収集したログの転送先を指定  ・aws_key_id, aws_sec_key, s3_bucket, s3_endpointで転送先bucketを指定  ・path, buffer_path, time_slice_formatで転送先パス・ファイル名を指定

<source>  
type tail  
format /^"(?<time_local>[^"]*)","(?<remote_addr>[^"]*)","(?<uid_got>[^"]*)","(?<upstream_http_x_app_userid>[^"]*)","(?<remote_user>[^"]*)","(?<request>[^"]*)","(?<status>[^"]*)","(?<body_bytes_sent>[^"]*)","(?<http_referer>[^"]*)","(?<http_user_agent>[^"]*)","(?<http_x_forwarded_for>[^"]*)","(?<gzip_ratio>[^"]*)"$/  
path /var/log/nginx/detail_access.log  
pos_file /tmp/td-agent/apache-web.pos  
tag nginx.access  
</source>  

<match nginx.access>  
type s3  

aws_key_id <ここにaccess_key_idを入れる>  
aws_sec_key <ここにsecret_access_keyを入れる>  
s3_bucket <ここにbucket名を入れる>  
s3_endpoint s3-ap-northeast-1.amazonaws.com  
path log/  
buffer_path /var/log/fluent/s3  

time_slice_format /nginx/%Y%m/detail_access.www-web.log.%Y%m%d%H  
time_slice_wait 10m  
</match>  

以下のコマンドで、 posファイルの保存用にテンポラリディレクトリを作ります。

$ mkdir /tmp/td-agent/  

fluentdの起動

以下のコマンドを入力しfluentdを起動します。

$ fluentd  
2012-08-21 16:54:05 +0900: starting fluentd-0.10.25  
2012-08-21 16:54:05 +0900: reading config file path="/etc/fluent/fluent.conf"  
==  

この状態で、ブラウザから以下のURLにアクセスします。  http://(ホスト名orIPアドレス)/ アクセスしたログが、 「/var/log/fluent/s3」以下にコピーされていることを確認します。

ここにコピーされたファイルが一時間ごとに、 指定したS3のbucketにコピーされます(最大1時間待って確認します)。