トラストバンクテックブログ

株式会社トラストバンクのプロダクト系メンバーによるブログです

【chiica】FuelPHPのLogクラスを改修してみた

はじめに

chiica統括部の湊です。 chiicaでは現在EC2環境からECS環境への移行対応を実施中です。

上記に伴い内部で使用しているFuelPHPフレームワークのログ出力をファイル出力から標準出力(stdout)へ移行を予定してます。

本記事ではFuelPHPを使用した環境下でのログ出力へ移行した際の手順とあわせ、既存ログに対し調査時に役立つ情報をログの出力を記載している既存のソースコードを一切触ることなく出力するための対応も記載。

Logのファイルが複数あったりどれを使用するかというのが長年ルール化されていなかったりしたため長年負債となっていたものをやっと解消できたのでこの場にて共有。

改修の軌跡

Phase.1

調査をより円滑に行えるよう、必要情報を網羅したフォーマットで出力するUtil_Logクラスを作成。

調査頻度の高いソースコードに対し、Util_Logを使用するように明示的に記載。

また、新規作成されたソースコードにおいてもUtil_Logを使用する。

アーキテクチャ

/app
└─ classes
   └─ util
      └─ log.php // Fuel\Core\Logの拡張クラス。調査チケットに対応するため出力するログのフォーマットを拡張。
/share
├─ fuelphp
│  └─ fuel
│     └─ core
│        └─ classes
│           └─ log.php // FuelPHPオリジナルのLogクラス
└─ fuelphpex
   └─ app
      └─ classes
         └─ log.php // Fuel\Core\Logの拡張クラス(ログ出力先のディレクトリ未作成時にディレクトリを作成)

Util_Log 解説

詳細な処理は割愛いたしますが以下のような調査に必要となる情報を出力するようにフォーマットを調整してます。

app_pid

どのアプリのどの処理からのリクエストかを記載。 現状は社内のどのマイクロサービスからのアクセスであるかを記載。

pid

プロセスID。 後述のidと併用することで同一ユーザーから同時にリクエストが発生した場合にもプロセスごとの検索を可能 getmypid(): + $_SERVER['REQUEST_TIME']を使用してユニーク性を担保

ip_address

接続元のIPアドレス

endpoint

どのエンドポイント(コントローラーやタスク)へのリクエストに対するログか。 コントローラー(タスク)内で呼び出し、継承している外部ソースのログにも同じ値を記載することでエンドポイント起因の調査を円滑にする。

stack

対象ログがどのソースの何行目で出力されたか。 エラーログ等の根本原因を特定するために設定。

使用例

<?php

use Util_Log as Log;

/**
 *  カード会員専用の残高照会コントローラ
 */
class Controller_Sample 
{
    public function sample()
    {
        // use にてUtil_LogのエイリアスをLogにしてるのでメソッド内のクラス名の変更は不要。
        Log::info('sample'); 
    {

}

【Results

既存ソースであっても1行の修正で済む。

【Assignment

既存ソースによって use Fuel\Core\Loguse Fuelex\Log; 、そもそもuseを使用していないといったカオスな状態となっており、 use Util_Log の置換(追記)が容易ではない。

また置換(追記)をする場合、ほぼすべてのソースコードが対象となるため複数名で開発してる場合のコンフリクトが発生するリスクがある。

(これはPhase.1.5の結果を調査してるときに気が付きましたが)コアクラス等、開発者が意図的に触ることのないソースコードに書かれているログに対しては対応できない。


Phase.1.5

chiica ECS化対応の一環としてログ出力をファイル出力から標準出力(stdout)へ変更。

上記対応のためPhase.1で触れた「コアクラス等、開発者が意図的に触ることのないソースコードを含めたすべてのログ」を標準出力されるように変更する必要が出てきた。

なお、FuelPHPはLaravelと違いconfigの設定による標準出力をサポートしていません。

本件について、社内SREチーム杉岡さんが先行して調査していただき、こちらの記事を共有してもらい、仮実装してもらいました。 仕事が早い。

アーキテクチャ

/app
├─ classes
│  └─ log.php // New! Fuel\Core\Logクラスを標準出力対応に拡張したクラス
└─ bootstrap.php // 既存クラスを今回用意した各「New!」クラスへオーバライドさせる設定を追加

/share
├─ fuelphp
│  └─ fuel
│     └─ core
│        └─ classes
│           └─ log.php
└─ fuelphpex
   └─ app
      └─ classes
         └─ log.php

app/bootstrap.php

Autoloader::add_classes([
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    // Logクラスの向き先を標準出力用クラスへ変更
    'Log' => APPPATH . '/classes/log.php',
]);

app/classes/log.php

<?php

class Log extends \Fuel\Core\Log
{
    public static function initialize()
    {
        $stream = new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::DEBUG);
        $formatter = new \Monolog\Formatter\LineFormatter("%level_name% - %datetime% --> %message%".PHP_EOL, "Y-m-d H:i:s");
        $stream->setFormatter($formatter);
        static::$monolog->pushHandler($stream);
    }
}

【Results

use Fuel\Core\Loguse Fuelex\Loguse Util_Log を記載していないソースコードのログは標準出力がされる。

【Assignment

use Fuel\Core\Loguse Fuelex\Loguse Util_Log を明示的に記載しているソースコード上のログはファイル出力されるため追加対応が必要。


Phase.2(最終版)

ソースコード上でuse Fuel\Core\Loguse Fuelex\Loguse Util_Log を使用またはそもそもuseを使用していない場合等、すべてのログを標準出力(stdout)へ変更。

また、ECS移行対応の過程で現行環境(EC2を使用)とECS環境が並行稼働する期間がある。

そのため現行環境(EC2を使用)はファイル出力。ECS環境は標準出力されるように処理の切り分けを行う

ログを出力している環境が現行環境かECS環境かの判定には杉岡さんより ECS_CONTAINER_METADATA_URI_V4 の共有がありましたのでそちらを採用。圧倒的感謝!

【参考】

Amazon ECS 環境変数 - Amazon Elastic Container Service

アーキテクチャ

/app
├─ classes
│  ├─ fuellog.php // New! オリジナルのFuel\Core\Logクラスのコピー
│  ├─ log.php // New! Fuel\Core\Logクラスを標準出力対応に拡張したクラス(合わせてUtil\Logクラスで行っていた詳細ログの処理もこちらへ移動)
│  ├─ fuel
│  │  └─ core
│  │     └─ log.php // New! オリジナルのFuel\Core\Logクラスをオーバーライドするために用意
│  ├─ fuelex
│  │  └─ log.php // New! オリジナルのFuelex\Logクラスをオーバライド
│  └─ util
│     └─ log.php // Update! Logクラスを継承。詳細処理をLogクラスへ移動
└─ bootstrap.php // Update! 既存クラスを今回用意した各「New!」クラスへオーバライドさせる設定を追加

/share
├─ fuelphp
│  └─ fuel
│     └─ core
│        └─ classes
│           └─ log.php
└─ fuelphpex
   └─ app
      └─ classes
         └─ log.php

app/bootstrap.php

Autoloader::add_classes([
    // Add classes you want to override here
    // Example: 'View' => APPPATH.'classes/view.php',
    // Logクラスの向き先を標準出力用クラスへ変更
    'Log' => APPPATH . '/classes/log.php',
    'Fuel\\Core\\Log' => APPPATH . '/classes/fuel/core/log.php',
    'Fuelex\\Log' => APPPATH . '/classes/fuelex/log.php',
    'Fuellog' => APPPATH . '/classes/fuellog.php',
]);

app/classes/util/log.php

※処理内容を app/classes/log.php へ移動。

<?php

use Log as CoreLog;

/**
 * デフォルトでプロセスIDやコントローラー、メソッド情報をログ出力させるクラス
 * 汎用クラスのためUtilに配置。
 * Class Util_Log
 */
class Util_Log extends CoreLog
{
    /**
     * @return void
     */
    public static function _init(): void
    {
        parent::_init();
    }
}

app/classes/log.php

<?php

use Fuel\Core\Config;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;

class Log extends Fuellog
{
    public static function initialize()
    {
        Config::load('authorization', true);
        // ECS環境以外ではログをファイル出力する
        if (getenv('ECS_CONTAINER_METADATA_URI_V4') === false) {
            parent::initialize();
            return;
        }
        $stream = new StreamHandler('php://stdout', Logger::DEBUG);
        $formatter = new LineFormatter("%level_name% - %datetime% --> %message%" . PHP_EOL, Config::get('log_date_format', 'Y-m-d H:i:s'));
        $stream->setFormatter($formatter);
        static::$monolog->pushHandler($stream);
    }
    
    
    
     // app/classes/util/log.php で行っていたフォーマット処理については割愛
    
    
    
}

app/classes/fuellog.php

<?php

/**
 * Class Fuellog
 *
 * @author kenichirou minato
 * オリジナルとなる Fuel\Core\Logクラスのコピー
 * 標準出力に対応するため別名で定義
 * オリジナルとの差異ができないようこのソースコードは加筆、修正しないでください。
 * 処理の変更が必要な場合は継承先である「app/classes/log.php」を改修してください。
 */
class Fuellog
{
    // 内部処理はまま Fuel\Core\Log のため割愛
}

app/classes/fuel/core/log.php

<?php

namespace Fuel\Core;

use Log as CoreLog;

class Log extends CoreLog
{
}

app/classes/fuelex/log.php

<?php

namespace Fuelex;

use Log as CoreLog;

class Log extends CoreLog
{
}

【Results

ソースコード上でuse Fuel\Core\Loguse Fuelex\Loguse Util_Log を使用またはそもそもuseを使用していない場合等、すべてのログが標準出力される。

合わせてPhase.1での課題であったuse Util_Log を使用していない既存ソース、コアクラス内のログの粒度を use Util_Log の内容に踏襲できた。

まとめ

今回の改修にて長年負債となっていたログ周りの改修が一旦落ち着きそうです。

(実は本番反映はこれからですが。。。

弊社トラストバンクでは様々な職種で絶賛採用中です! 気になった方、是非お気軽にご連絡ください!

www.wantedly.com