CodeIgniterのログをMonologを使って出力する

## 概要

今までログの出力はlog4phpを利用していましたが、Slackへの通知を見据えてMonologに置き換えてみました。

## 環境

CodeIgniter 3.1.10
PHP 7.2

## Monologをインストールする

``` console
$ composer require monolog/monolog
```

## ログコアクラスを独自拡張する

CodeIgniterではapplication/coreにパイルを配置することによって、デフォルトのコアクラスを独自拡張したものに置き換えることができます。
独自拡張したコアクラスのクラス名やファイル名にはプレフィックスとして「MY_」を付ける必要があります。
今回はログコアクラスを拡張しますのでクラス名はMY_Logとなります。

``` php:application/core/MY_Log.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Formatter\LineFormatter;

class MY_Log extends CI_Log
{
    protected $logger;

    public function __construct()
    {
        parent::__construct();

        // Monologのチャンネルを作成
        $this->logger = new Logger('log');

        // 親クラスの設定を引き継ぎログファイル名を指定
        $filename = $this->_log_path.'log.'.$this->_file_ext;

        // ログローテーションしてくれるハンドラーを使用
        $rotate_handler = new RotatingFileHandler($filename);
        $rotate_handler->setFormatter(new LineFormatter("%datetime% [%level_name%] %message%\n"));
        $this->logger->pushHandler($rotate_handler);
    }

    /**
     * ログ出力
     * @param string $level エラーレベル
     * @param string $msg メッセージ
     */
    public function write_log($level, $msg)
    {
        // エラーレベルが「debug」「info」「error」であることを前提に実装
        $this->logger->{$level}($msg);
    }
}

```

write_logに渡される引数$levelは「debug」「info」「error」のいずれかとなるので、そのままメソッド名として使用しています。

## エラーのときはSlackにも通知されるようにする

さらにエラーレベルが「error」の場合はSlackにも通知させてかったので、追記しました。
Slackに通知するためのハンドラーはいくつか用意されていますが、今回はWebhookを使って通知するSlackWebhookHandlerを使用しています。

### SlackのWebhook URLを取得

以下のURLからSlackと連携するためのWebhook URLを取得します。

https://[通知したいワークスペース名].slack.com/apps/A0F7XDUAZ--incoming-webhook-?next_id=0

### configファイルに設定を追加

取得したWebhook URL等を設定ファイルに追記します。
開発環境と本番環境で通知先を変えるのであれば、developmentとproductionのサブディレクトリにconfig.phpを配置して記述した方がいいのですが、今回はその点については省略します。

``` php:application/config/config.php
$config['endpoint'] = 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/xxxxxXXxxXxXXxxXXXXXxx';
$config['username'] = 'エラー通知';
$config['icon'] = ':ghost:';
$config['channels'] = '#notice-error';
```

### Slackにも通知されるように変更

``` php:application/core/MY_Log.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\LineFormatter;

class MY_Log extends CI_Log
{
    protected $logger;

    public function __construct()
    {
        parent::__construct();

        // Monologのチャンネルを作成
        $this->logger = new Logger('log');

        // 親クラスの設定を引き継ぎログファイル名を指定
        $filename = $this->_log_path.'log.'.$this->_file_ext;

        // ログローテーションしてくれるハンドラーを使用
        $rotate_handler = new RotatingFileHandler($filename);
        $rotate_handler->setFormatter(new LineFormatter("%datetime% [%level_name%] %message%\n"));
        $this->logger->pushHandler($rotate_handler);

        // 設定を取得
        $config =& get_config();

        // エラーの場合Slackにも通知する
        if (isset($config['slack_endpoint']) && '' !== $config['slack_endpoint']) {
            $slack_handler = new SlackWebhookHandler(
                $config['slack_endpoint'],
                isset($config['slack_channels']) ? $config['slack_channels'] : null,
                isset($config['slack_username']) ? $config['slack_username'] : null,
                false,
                isset($config['slack_icon']) ? $config['slack_icon'] : null,
                false,
                false,
                \Monolog\Logger::ERROR
            );
            $slack_handler->setFormatter(new LineFormatter("%message%"));
            $this->logger->pushHandler($slack_handler);
        }
    }

    /**
     * ログ出力
     * @param string $level エラーレベル
     * @param string $msg メッセージ
     */
    public function write_log($level, $msg)
    {
        // エラーレベルが「debug」「info」「error」であることを前提に実装
        $this->logger->{$level}($msg);
    }
}

```

実行するとこんな感じで通知されます。
![qiita_slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157083/3e4e3f6d-3b69-b44b-bd4d-b3c40b70fdd3.png)


## 参考
https://qiita.com/ichi_system/items/ea07df5eef9763179c70
https://github.com/Seldaek/monolog
https://www.codeigniter.com/user_guide/