PHPを使ってXFFの値をアクセスログに出力する方法（Xserver編）

### 概要
レンタルサーバー環境（Xserver等）では、Apacheのアクセスログフォーマットを自由に変更できない場合が多くあります。

クライアントのIPアドレスとしてX-Forwarded-For（XFF）の値を記録したい場合、標準ログの代わりにカスタムログを出力する必要があります。本記事では、PHPを使ってXFFの値を含むカスタムアクセスログを生成する方法を紹介します。

### なぜXFFが重要なのか

WAFやCDNを利用している場合、サーバーが直接受け取るIPアドレス（`REMOTE_ADDR`）はプロキシやCDNのものになることが多く、実際のクライアントIPを取得できません。XFFヘッダーは、そのような環境でクライアントの実際のIPアドレスを取得するために使われます。

### 実装手順

#### 1. logディレクトリの作成

ログファイルを保存するためのディレクトリを作成します。SSHやFTPを使用する方法もありますが、今回はXserverのコントロールパネルから「ファイルマネージャー」を使用します。

![CleanShot 2024-09-16 at 17.21.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943534/41069b19-8c13-f531-da36-3d9fcd49494b.png)


1. Xserverのコントロールパネルにログイン
2. 「ファイル管理」 > 「ファイルマネージャー」を選択
3. `/home/サーバーID/サーバーID.xsrv.jp/` ディレクトリに移動（パスは環境ごとに異なります）
4. `log` ディレクトリを新規作成

![CleanShot 2024-09-16 at 17.19.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943534/c130b7f3-e87c-0789-9d08-97280e8a6d21.png)


#### 2. PHPスクリプトを追加してカスタムログを生成

WordPressを使っている場合、現在使用しているテーマの`functions.php`ファイルを編集して、カスタムログを生成するコードを追加します。

![CleanShot 2024-09-16 at 17.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943534/719be297-ce42-a2ac-2b8e-c191c3de75b1.png)

以下の手順では、`/home/xs999999/xs999999.xsrv.jp/public_html/test/wp-content/themes/twentytwentyfour/functions.php`にコードを追加しています。

1. コントロールパネルの「ファイルマネージャー」を開く
2. `/home/xs999999/xs999999.xsrv.jp/public_html/test/wp-content/themes/your-theme/functions.php` へ移動
3. `functions.php`を選択して「編集」をクリック
4. コードを`functions.php`の先頭に追加

```php
<?php
// WordPress初期化後、カスタムログを生成する
add_action('shutdown', 'custom_log_user_info', 9999); // shutdownフックで最後に実行

// カスタムログを生成し、ファイルに保存する
function custom_log_user_info() {
    // ログインユーザー名を取得（未ログインなら'-'）
    $logname = is_user_logged_in() ? wp_get_current_user()->user_login : '-';

    // リモートアドレスとX-Forwarded-Forの最初のIPを取得
    $remote_addr = $_SERVER['REMOTE_ADDR'];
    $xff_ip = !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0] : $remote_addr;

    // リクエストサイズ（存在しない場合は`-`）
    $request_size = isset($_SERVER['CONTENT_LENGTH']) ? $_SERVER['CONTENT_LENGTH'] : '-';

    // 日本時間に設定し、日時フォーマットを指定
    date_default_timezone_set('Asia/Tokyo');
    $time = '[' . date('d/M/Y:H:i:s O') . ']';

    // HTTPリクエスト情報、ステータスコード、ユーザーエージェント、リファラを取得
    $request = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . ' ' . $_SERVER['SERVER_PROTOCOL'] : '-';
    $status = http_response_code();
    $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '-';
    $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '-';

    // ログエントリを作成（Refererヘッダーも含める）
    $log_entry = sprintf('"%s" %s - %s %s "%s" %s %s "%s" "%s"' . "\n", 
        $xff_ip, 
        $remote_addr, 
        $logname, 
        $time, 
        $request, 
        $status, 
        $request_size, 
        $referer,
        $user_agent
    );

    // ログファイルに書き込む
    $log_file = '/home/xs999999/xs999999.xsrv.jp/log/access_' . date('Ymd') . '.log';
    if ($fp = fopen($log_file, 'a')) {
        if (flock($fp, LOCK_EX)) {
            fwrite($fp, $log_entry);
            fflush($fp);
            flock($fp, LOCK_UN);
        }
        fclose($fp);
    }
}
?>

// 以下、既存コード

```

※ `/home/xs999999/xs999999.xsrv.jp/log/` の箇所は実際の環境に合わせて変更してください

#### 3. テーマの`functions.php`使用について

`functions.php`は、WordPressテーマの中で追加のカスタム機能を定義するために使われるファイルです。`wp-includes/functions.php`のようなコアファイルを直接編集すると、将来的なWordPressのアップデートで変更が上書きされるリスクがありますが、テーマの`functions.php`を使用することで、このリスクを回避できます。

テーマを変更した場合、新しいテーマの`functions.php`に同じカスタムコードを移す必要があります。テーマに依存しない方法としては、カスタムプラグインを作成することも一つの選択肢です。

#### 4. コードの説明

このスクリプトでは、以下の情報を含むログを生成します。

|項目|説明|Apache標準ログとの違い|
|---|---|---|
|X-Forwarded-For|クライアントのIPアドレス|X-Forwarded-最初の値を取得|
|REMOTE_ADDR|接続元IPアドレス|通常はWAFやCDNのIPアドレス|
|識別情報|常に「-」を出力|Apacheログフォーマットへの準拠用|
|認証ユーザー|WordPressユーザー名|WordPressにログインしている場合のみ記録|
|タイムスタンプ|アクセス時間|日本標準時間|
|リクエスト内容|HTTPメソッド、リクエストURI、プロトコル|-|
|ステータス|HTTPレスポンスのステータスコード|-|
|転送量|リクエストのContent-Lengthヘッダー値|Apacheと計測が異なる|
|リファラー|リクエスト元のページURL|-|
|ユーザーエージェント|クライアントのユーザーエージェント|-|

#### 5. ログ出力例

実際にWebアクセスをすると指定したディレクトリにログファイルが出力されます。

![CleanShot 2024-09-16 at 17.22.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943534/1ca96c92-c852-f4ea-05ea-9ef121058092.png)

出力されるログの例は以下の通りです。

```
“203.0.113.5” 203.0.113.1 - user [29/Sep/2024:12:34:56 +0900] “GET /wp-admin/ HTTP/1.1” 200 12345 “https://example.com” “Mozilla/5.0 (Windows NT 10.0; Win64; x64)”
```

このように、クライアントIP（XFF）、プロキシIP（REMOTE_ADDR）、リクエスト情報など必要な情報が記録されます。

### まとめ

Xserverなどのレンタルサーバーではアクセスログのフォーマットをカスタマイズできない場合がありますが、PHPを使って独自のログを生成することで、X-Forwarded-Forヘッダーの値を含む詳細なアクセスログを残すことができます。