AWS
mysql5.6
cakephp3
PHP7

アクセスログをシンプルにDB出力する

サーバー構成

AWSのELBに複数台EC2がぶら下がっており、そのEC2上でApache+PHP環境のアプリケーションが動作している

アクセスログ用テーブルの作成

sql
CREATE TABLE `access_logs` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
    `request_time` DATETIME NOT NULL COMMENT 'リクエスト日時',
    `session_id` VARCHAR(30) NOT NULL COMMENT 'セッションID',
    `request_method` VARCHAR(10) NOT NULL COMMENT 'リクエスト方式',
    `request_uri` VARCHAR(2083) NOT NULL COMMENT 'リクエストURI',
    `action_name` VARCHAR(50) NOT NULL COMMENT 'アクション名',
    `controller_name` VARCHAR(50) NOT NULL COMMENT 'コントローラ名',
    `x_forwarded_for` VARCHAR(255) NULL DEFAULT NULL COMMENT 'リクエスト元IP',
    `server_addr` VARCHAR(10) NULL DEFAULT NULL COMMENT 'サーバーIP',
    `user_agent` VARCHAR(255) NULL DEFAULT NULL COMMENT 'ユーザーエージェント',
    `request_query` TEXT NULL COMMENT 'リクエストクエリ',
    `request_data` TEXT NULL COMMENT 'リクエストデータ',
    `referer` VARCHAR(255) NULL DEFAULT NULL COMMENT 'リファラー',
    PRIMARY KEY (`id`)
)
COMMENT='アクセスログ'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

Model

Bakeで
https://book.cakephp.org/3.0/ja/bake/usage.html

AppController

beforeFilter

リクエスト内容を簡潔に取りたいのでbeforeFilterだけにしています。
結果まで取るのでしたらafterFilterも併用するといいかもしれません。

AppControllerクラスbeforeFilterメソッド
use Cake\Chronos\Chronos;

public function beforeFilter(Event $event) {
    try{
        // リクエストクエリとデータを文字列に変換&password等は出力しない
        $requestQuery = $this->unsetSecret($this->request->query);
        $requestData = $this->unsetSecret($this->request->data);

        $accessLogParameters = [
            'request_time' => (Chronos::createFromTimestamp(time()))->toDateTimeString(),
            'session_id' => $this->request->session()->id(),
            'request_method' => $this->request->env('REQUEST_METHOD'),
            'request_uri' => $this->request->env('REQUEST_URI'),
            'action_name' => $this->request->action,
            'controller_name' => $this->name,
            'x_forwarded_for' => $this->request->env('HTTP_X_FORWARDED_FOR'),
            'server_addr' => $this->request->env('SERVER_ADDR'),
            'user_agent' => $this->request->env('HTTP_USER_AGENT'),
            'request_query' => json_encode($requestQuery),
            'request_data' => json_encode($requestData),
            'referer' => $this->request->env('HTTP_REFERER')
            ];

        $accessLogModel = TableRegistry::get('AccessLogs');
        $accessLogEntity = $accessLogModel->newEntity($accessLogParameters);
        $accessLogModel->save($accessLogEntity);
    }
    catch (\Throwable $throwable){
    }
}

アクセスログに残すべきでない情報の消去

パスワード等取っておいてはいけない情報を消します。

AppController内privateメソッド
/**
 * パスワード等のログ出力してはいけない項目をマスクする。
 * @param Array $params
 * @return Array
 */
private function unsetSecret($params){

    // unset対象の文字列
    $keywords = [
        'password',
    ];

    // unset処理
    $paramKeys = array_keys($params);
    foreach($keywords as $maskKeyword){
        $masks = preg_grep("/{$maskKeyword}/i", $paramKeys);
        foreach($masks as $mask){
            $params[$mask] = "***";
        }
    }
    return $params;
}

参考

AWS ELBが付与するHTTPヘッダ
http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/x-forwarded-headers.html