CS-Cart: エラーをログに出したり、Slackに通知したりする


CS-Cartはアーキテクチャがしっかりしていないというか、10年くらい前のアーキテクチャというか、とにかくフレームワークの作りがゆるいので、改修していると今時当たり前の仕組みが無かったりする。

その1つが、エラーの通知。CS-CartでFatalエラーやExceptionが発生しても、開発者は知るすべがない。致命的なエラーになると、ユーザには下のような画面が表示され、HTMLのソースコードにコメントでエラー情報が出力されるが、顧客から連絡がないと発見することができず、後手後手になってしまう。

CS-Cartはエラーをどうハンドリングしているか

CS-Cartがエラーをどう処理しているかだが、DEVELOPMENT環境かどうかで挙動が違う。まず、DEVELOPMENT環境ではない いわゆる本番環境では、次の処理をしている

  • register_shutdown_functionで、Fatalエラーになっても最悪画面が真っ白にならないようにエラー処理をしている
  • ログは取っていない

DEVELOPMENT環境では、これに加えて、

  • set_error_handlerで開発者用エラー画面を出すのと、error_logを実行する

くらいはやっている。

ソースコードをgrepしてみたが、exception handlerは使っていないので、Exceptionが発生してcatchされないと画面が真っ白になる。

CS-Cartでエラーをログに出す方法

CS-Cartならhookを使ってエラーハンドリングしたいところだが、エラー処理部分には残念ながらフックがない。したがって、エラーを捉える処理を実装しないとならない。

と言っても難しいことはなく、set_error_handler, set_exception_handler,
register_shutdown_functionでエラーハンドラをバインドしておけばいい。どこに、エラーログの処理を書くか悩ましいが、とりあえずlocal_conf.phpに書いておけばよいかと思う。

local_conf.php
$reportError = function (Exception $exception) {
    // ここでログをとる
};

set_error_handler(function ($severity, $message, $file, $line) use ($reportError) {
    if (error_reporting() & $severity) {
        $reportError(new ErrorException($message, 0, $severity, $file, $line));
    }
});

set_exception_handler(function (Exception $exception) use ($reportError) {
    $reportError($exception);
});

register_shutdown_function(function () use ($reportError) {
    $error = error_get_last();
    if (isset($error['type']) and in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING])) {
        $reportError(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
    }
});

set_error_handlerは1プロセスに1個しか登録できないのがPHPの仕様だが、CS-CartはDEVELOPMENT環境でないと、set_exception_handlerを使わないので気にせず登録してしまってOK。

register_shutdown_functionはPHPの仕様上、複数登録できるので問題ない。set_exception_handlerはそもそも使われていないので、登録してしまって構わない。

Slackにエラーを通知する方法

PHP: Slackにメッセージを投稿するクラス - Qiitaを使って、Slackにエラーを投げるようにする。

エラーをログにとるなら、エラーの文脈もあるとデバッグしやすいので、つけておく。

require_once __DIR__ . '/app/SlackNotifier.php';
$webhookUrl = 'https://hooks.slack.com/services/...';
$slackNotifier = new SlackNotifier($webhookUrl);
$reportError = function (Exception $exception) use ($slackNotifier) {
    $user = \Tygh\Registry::get('user_info');
    $slackNotifier->exception($exception, [
        'HTTP_HOST'      => @$_SERVER['HTTP_HOST'],
        'REMOTE_ADDR'    => @$_SERVER['REMOTE_ADDR'],
        'REQUEST_METHOD' => @$_SERVER['REQUEST_METHOD'],
        'url'            => @"http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]",
        'user_id'        => @$user['user_id'],
        'HTTP_REFERER'   => @$_SERVER['HTTP_REFERER'],
        'ajax?'          => defined('AJAX_REQUEST') ? 'yes' : 'no',
    ]);
};