LoginSignup
1
0

More than 1 year has passed since last update.

CodeIgniter4のコアクラスを拡張して、エラーログをSlackに通知する

Last updated at Posted at 2021-12-21

CodeIgniter4のコアクラスの拡張はわかりにくい

CodeIgniter3は簡単にログを拡張できたが、CodeIgniter4のコアクラスの拡張はちょっとわかりにくいので、Slack通知を例に作成してみた。

勉強中のLaravelでやってみようかと思ったが、すでにあったので、CodeIgniter4で記述する。

CodeIgniter4のコアクラスの拡張(基本編+愚痴)

マニュアルはこれ
https://codeigniter4.github.io/userguide/extending/core_classes.html

ルーターの拡張を行っているが、このままでは動かない。なんでやねん。

四苦八苦した結果、以下のように書くと動作した

app/Config/Services.php
public static function routes(bool $getShared = true) {
    if ($getShared) {
        return static::getSharedInstance('routes');
    }
    $locator = Services::locator();
    $moduleConfig = config('Modules');
    return new \App\Libraries\RouteCollection($locator, $moduleConfig);
}

つまり、コンストラクタに設定を渡さなければならない ということ。
もちろん、利用する(拡張する)クラスごとにコンストラクタの内容が変わるので、systemディレクトリを徘徊することになる

これがマニュアルから非常にわかりづらい。
名前空間を使い慣れていればすぐに気づくのかもしれないが、CodeIgniter3から上がってくるユーザーには厳しいのではないか。

ログクラスを拡張してみる

app/Config/Services.php
public static function logger($getShared = true) {
    if ($getShared) {
        return static::getSharedInstance('logger');
    }
    $config = config('Logger');
    return new \App\Libraries\Logger($config);
}
app/Libraries/Logger.php
<?php
namespace App\Libraries;
use CodeIgniter\Log\Logger AS BaseLogger;

class Logger extends BaseLogger {
  public function log($level, $message, array $context = []): bool {
    // 継承元クラスでログの書き込み(成功するとtrueが返る)
    $ret = parent::log($level, $message, $context);
    if ($ret) {
      // Slackが落ちていたりした場合のためにTryにしている。
      try {
        // Slackに通知するためのメッセージ作成
        $request = \Config\Services::request();
        $router = \Config\Services::router();
        $data = [
          "text" => "New Paid Time Off request from Fred Enriquez",
          'blocks' => [
            [
              'type' => 'header',
              'text' => [
                'type' => 'plain_text',
                'text' => 'New Error'
              ]
            ],
            [
              'type' => 'section',
              'fields' => [
                [
                  "type" => "mrkdwn",
                  "text" => "*Class:*" . PHP_EOL . $level
                ],
                [
                  "type" => "mrkdwn",
                  "text" => "*Date:*" . PHP_EOL . date('Y-m-d H:i:s')
                ]
              ]
            ],
            [
              'type' => 'section',
              'fields' => [
                [
                  "type" => "mrkdwn",
                  "text" => "*Router:*" . PHP_EOL . $router->controllerName()
                ],
                [
                  "type" => "mrkdwn",
                  "text" => "*URL:*" . PHP_EOL . current_url()
                ]
              ]
            ],
            [
              'type' => 'section',
              'text' => [
                "type" => "mrkdwn",
                "text" => "*IP / UA:*" . PHP_EOL . $request->getIPAddress() . PHP_EOL . $request->getUserAgent()
              ]
            ],
            [
              'type' => 'section',
              'text' => [
                "type" => "mrkdwn",
                "text" => '```' . PHP_EOL . $message . PHP_EOL . '```'
              ]
            ],
          ],
        ];

        $header = [
          'Content-Type: application/json',
        ];

        // SlackにPOSTする
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, POST_URL_TO_SLACK);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); // post
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); // jsonデータを送信
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // リクエストにヘッダーを含める
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, true);

        $response = curl_exec($curl);
        $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $header = substr($response, 0, $header_size);
        $body = substr($response, $header_size);
        $result = json_decode($body, true);
        curl_close($curl);
      } catch (Exception $ex) {

      }
    }
    return $ret;
  }
}

結果

error.png

trace内容がうまく取れていない(恐らくcontextに入っている)が、メッセージが長くなること請け合いなので、ここまでわかれば解析はかなり楽になるとして妥協。

↓↓ trace取れました ↓↓

trace対応版

やっぱりcontextにいたので対応してみた。
ただし、予想通り、メッセージが長くなってしまうので、対応しなくてもいいかもしれない。
これ以上のことをやろうと思うと、エラーメッセージをテキストファイルにしてアップロード・添付するしかない。

app/Libraries/Logger.php
<?php
namespace App\Libraries;
use CodeIgniter\Log\Logger AS BaseLogger;

class Logger extends BaseLogger {
  public function log($level, $message, array $context = []): bool {
    // 継承元クラスでログの書き込み(成功するとtrueが返る)
    $ret = parent::log($level, $message, $context);
    if ($ret) {
      // Slackが落ちていたりした場合のためにTryにしている。
      try {
        // Slackに通知するためのメッセージ作成
        $request = \Config\Services::request();
        $router = \Config\Services::router();

        $traceMessage = $message;
        if (count($context) > 0) {
          foreach ($context as $key => $data) {
            $slackMessage = str_replace('{' . $key . '}', $data, $traceMessage);
          }
        }

        $data = [
          "text" => "New Paid Time Off request from Fred Enriquez",
          'blocks' => [
            [
              'type' => 'header',
              'text' => [
                'type' => 'plain_text',
                'text' => 'New Error'
              ]
            ],
            [
              'type' => 'section',
              'fields' => [
                [
                  "type" => "mrkdwn",
                  "text" => "*Class:*" . PHP_EOL . $level
                ],
                [
                  "type" => "mrkdwn",
                  "text" => "*Date:*" . PHP_EOL . date('Y-m-d H:i:s')
                ]
              ]
            ],
            [
              'type' => 'section',
              'fields' => [
                [
                  "type" => "mrkdwn",
                  "text" => "*Router:*" . PHP_EOL . $router->controllerName()
                ],
                [
                  "type" => "mrkdwn",
                  "text" => "*URL:*" . PHP_EOL . current_url()
                ]
              ]
            ],
            [
              'type' => 'section',
              'text' => [
                "type" => "mrkdwn",
                "text" => "*IP / UA:*" . PHP_EOL . $request->getIPAddress() . PHP_EOL . $request->getUserAgent()
              ]
            ],
            [
              'type' => 'section',
              'text' => [
                "type" => "mrkdwn",
                "text" => '```' . PHP_EOL . $traceMessage . PHP_EOL . '```'
              ]
            ],
          ],
        ];

        $header = [
          'Content-Type: application/json',
        ];

        // SlackにPOSTする
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, POST_URL_TO_SLACK);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); // post
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); // jsonデータを送信
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // リクエストにヘッダーを含める
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, true);

        $response = curl_exec($curl);
        $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $header = substr($response, 0, $header_size);
        $body = substr($response, $header_size);
        $result = json_decode($body, true);
        curl_close($curl);
      } catch (Exception $ex) {

      }
    }
    return $ret;
  }
}

備考

  • 案件によってはログイン情報やDebugbarの内容なんかを入れてもいいかもしれない。
  • Slackへの通知部分はモデルかライブラリ化をしたほうが良い(エラー通知以外にも使えるので)
    (まぁ、手で書かずにComposer使えばいいんだけど)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0