読者です 読者をやめる 読者になる 読者になる

ハウテレビジョン開発者ブログ

『外資就活ドットコム』を日夜開発している技術陣がプログラミングネタ・業務改善ネタ・よしなしごとについて記していきます。

nginxのアクセスログにユーザーIDを記録する方法

こんにちは。来週末のPyConが待ち遠しくてたまらない祖山です。

以前、Fluentdを使ってElasticsearchやBigQueryにnginxのアクセスログを流す方法をご紹介しました。

fluentdでnginxのログをElasticsearchとBigQueryに保存するお話 - ハウテレビジョン開発者ブログ

しかしながら、本格的にユーザーの行動解析を行うにはnginxのデフォルトのログでは不十分です(IPアドレスのみから「このアクセスとこのアクセスは同一のユーザーである」かどうか判断するのは無理がありますね…)。

そのため、nginxのアクセスログに誰からのアクセスかを一意に識別できる情報を入れる必要があります。 真っ先に思い浮かぶのは(ログインして使うサイトであれば)ユーザーのIDでしょう。

もちろん、デフォルトのnginxではそんなことはやってくれないので、アプリケーションから何らかの方法でnginxに情報を渡し、ログの一部として出力させる設定をしなければなりません。 もっとも簡便なのは、HTTPのレスポンスヘッダを使う方法です。

ということで、今回はCakePHPでレスポンスヘッダにユーザーIDを埋め込み、それをnginxで受け取ってログの一部として出力する方法をご紹介します。 (外資就活で採用しているWebフレームワークがCakePHPのため、CakePHPでの設定内容をご紹介しますが、他のWebフレームワークでも問題なく応用できる内容かと思います)

やりたいこと

  • nginxのアクセスログにユーザーIDを記録する

やりかた

  1. CakePHPでユーザーIDをレスポンスヘッダに追加する
  2. そのヘッダの中身をアクセスログに記録するよう、nginxのログの形式を指定する
  3. 追加したヘッダを削除した上でレスポンスを返す

3つ目の項目は必須ではありません。 ユーザーID単体では個人を特定することはできないため、個人情報には当たりません。 そのためレスポンスの一部としてそのまま返してしまっても問題はないのですが、 やはり丸見えになってしまうのは不格好で、あまり気持ちのよいものではないでしょう。

そのため、可能であればCakePHPで追加したヘッダーを、nginxを経由する部分で削除してやりたいですね。

CakePHPの設定

もちろん、全ページへのアクセスについて処理を追加する必要があります。 こういった共通の処理は、AppControllerのbeforeFilterに書きましょう (今回の用途に限って言えば、別にafterFilterでもbeforeRenderでも問題ないですがw)。

beforeFilter内で何らかの方法でユーザーIDを取得した上で、 $this->response->header()を使ってその値をヘッダーにセットしてやります。

<?php
// app/Controller/AppController.php
class AppController extends Controller {
  // 略
    public function beforeFilter() {
        $user_id = $this->Auth->user('id'); // ユーザーIDを取得
        if ($user_id) {
            $this->response->header('X-User-ID', $user_id);
        }
    }
  // 略
}

nginxの設定

CakePHPでセットしたヘッダの中身をアクセスログに記録するためのフォーマットの指定と、 そのヘッダを消した上でレスポンスを送る処理の2つが必要です。

ログフォーマット

upstream_http_のプレフィックスを付けて(+すべて小文字、ハイフンをアンダースコアに変換)formatに指定することで、 任意のHTTPヘッダの中身をログに出力することができます。

log_format myformat '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$upstream_http_x_user_id';    # <-ここ!

指定のヘッダを削除する

リバースプロキシを使っている場合は設定が楽です。 serverセクション内のproxy_hide_headerディレクティブにヘッダ名を設定することで、そのヘッダを削除した上でレスポンスを返すことができます。

# /etc/nginx/sites-available/reverse-proxy
server {
    listen 80;
    server_name mysite.com;

    proxy_hide_header X-User-ID;    # <-ここ!

    access_log  /var/log/nginx/access.log myformat;

// 後略
}

リバースプロキシを使っていない場合は、新たにモジュールを入れる必要があるようです。 つまりはnginxをソースからコンパイルする必要がありますね…

HttpHeadersMoreModule - Nginx Community

more_clear_headersというディレクティブを使えば良いようです。

# /etc/nginx/sites-available/xxxxx
server {
    listen 80;
    server_name mysite.com;

    more_clear_headers X-User-ID;    # <-ここ!

    access_log  /var/log/nginx/access.log myformat;

// 後略
}

まとめ

上記の手順により、ユーザーに見られることなくユーザーIDをログとして出力することができます。

今回はCakePHP + nginxの場合の設定についてご紹介しましたが、

  • 「全てのコントローラに共通の処理」が記述できること(AppController)
  • コントローラアクションの前または後に何かを処理するメソッドが存在すること(beforeFilter)
  • コントローラからHTTPのレスポンスヘッダを操作できること

以上の3条件さえ満たしていれば、他のWebフレームワークでも実現可能です(普通のWebフレームワークならまず満たしている機能でしょう)。

ログにさえ出力してしまえば、あとはFluentdで好きなところに流して分析し放題ですね!