CodeIgniterのログを独自カテゴリーに分けて出力する

CodeIgniterにはログファイルを簡単に作成してくれる関数が用意されています。

エラー処理-log_message
[http://codeigniter.jp/user_guide/3/general/errors.html?highlight=%E3%83%AD%E3%82%B0#log_message](http://codeigniter.jp/user_guide/3/general/errors.html?highlight=%E3%83%AD%E3%82%B0#log_message)

この関数、CodeIgniterが読み込まれている範囲であればどこでも、

```php
log_message('debug', 'デバックメッセージ');
```

と書けば、```application/logs```の中に```log-2018-10-05.php```のような形でファイルが作成され、中身に

```log-2018-10-05.php
<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>

DEBUG - 2018-10-05 17:33:35 --> デバックメッセージ
```

というようなログが溜まっていきます([ただし、```config/config.php```の設定や```application/logs```へApacheの書き込み権限設定が必要](http://codeigniter.jp/user_guide/3/general/errors.html?highlight=%E3%83%AD%E3%82%B0#log_message))。

しかしこのログ、自分で仕込んだ```log_message()```以外にもCodeIgniterのコア部分で多用されていることもあり、CodeIgniterが起動する度に結構なメッセージが書き込まれていきます。

時系列に沿って書き込まれていくとはいえ、欲を言えばカテゴリー別にログが取れればもっと便利にログを使えそうです。

なので作りました。

※対象はCodeIgniterの3系です。

# Logクラスを拡張する

[コアシステムクラスの作成-システムクラス一覧](http://codeigniter.jp/user_guide/3/general/core_classes.html#id2)

だいたいのCodeIgniterのコアクラスは自作のクラスで代替可能です。

ただし公式マニュアルが口酸っぱく警告しているとおり、ほとんどの場合はクラスを継承してメソッドをオーバーライドしていくほうが簡単かつ安全でしょう。

今回の拡張も```Log.php```を継承した```MY_Log.php```を作成することにします。

以下がクラスの全貌です。

```MY_Log.php

class MY_Log extends CI_Log {

  private function _write_log($level, $msg)
  {
    $category = isset($this->_levels[$level]) ? '' : $level.'-';

    $filepath = $this->_log_path.'log-'.$category.date('Y-m-d').'.'.$this->_file_ext;
    $message = '';

    if ( ! file_exists($filepath))
    {
      $newfile = TRUE;
      // Only add protection to php files
      if ($this->_file_ext === 'php')
      {
        $message .= "<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>\n\n";
      }
    }

    if ( ! $fp = @fopen($filepath, 'ab'))
    {
      return FALSE;
    }

    flock($fp, LOCK_EX);

    // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format
    if (strpos($this->_date_fmt, 'u') !== FALSE)
    {
      $microtime_full = microtime(TRUE);
      $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000);
      $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full));
      $date = $date->format($this->_date_fmt);
    }
    else
    {
      $date = date($this->_date_fmt);
    }

    $message .= $this->_format_line($level, $date, $msg);

    for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result)
    {
      if (($result = fwrite($fp, self::substr($message, $written))) === FALSE)
      {
        break;
      }
    }

    flock($fp, LOCK_UN);
    fclose($fp);

    if (isset($newfile) && $newfile === TRUE)
    {
      chmod($filepath, $this->_file_permissions);
    }

    return is_int($result);
  }

  /**
   * Write Log File
   *
   * Generally this function will be called using the global log_message() function
   *
   * @param	string	$level 	The error level: 'error', 'debug' or 'info'
   * @param	string	$msg 	The error message
   * @return	bool
   */
  public function write_log($level, $msg)
  {
    if ($this->_enabled === FALSE)
    {
      return FALSE;
    }

    $level = strtoupper($level);

    if ( ! isset($this->_levels[$level]))
    {
      return $this->_write_log($level, $msg);
    }

    if (
      isset($this->_levels[$level])
      && (($this->_levels[$level] > $this->_threshold))
      && ! isset($this->_threshold_array[$this->_levels[$level]])
    )
    {
      return FALSE;
    }

    return $this->_write_log($level, $msg);
  }

}

```

このコードを```application/core/MY_Log.php```の位置に配置すると拡張処理が動作します。

# どんなものが出力されるのか

先述のように、

```php
log_message('debug', 'デバックメッセージ');
```

と書いても拡張前と書き出されるログは変わりません。
第一引数が「error」「debug」「info」の場合は変わらず、設定ファイルに設定できるしきい値に応じてデフォルトのログファイルに出力されたりされなかったりします。

今回の拡張が役立つのは第一引数にそれら以外の文字列を設定した場合です。

```php
log_message('custome', 'カスタムメッセージ');
```

```log-custome-2018-10-05.php
<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>

DEBUG - 2018-10-05 17:36:15 --> カスタムメッセージ
```

上述のように第一引数を```custome```と設定すると、デフォルトのログファイル名中、__log__と__日付__の間に第一引数と同じ文字列が挿入されたファイルが作成され、以後同じ第一引数が設定された```log_message()```関数はそのファイルへログを追記します。

こうすることでログの種類によってログファイルをカテゴリー分けできるのです。