FuelPHP のログを fluentd で tail する


## 前置き

FuelPHP のログを fluentd で tail したいと考えたのですが、FuelPHPはログを日毎に別ファイルに書き出すので tail 出来ない。取り敢えずググってみた結果、[こちら](http://blog.glatts.com/blog/imai/?p=1022 "FuelPHPのログをfluentdの入力に | Imai blog")が非常に参考になった。殆どの方がこちらの記事で事足りるはずである。  
自分の場合は、 FuelPHP のデフォルトのログ出力はそのまま出力されるようにしたかったため、先程の記事の

> 方法2 MonologFluentHandlerを使う

を試してみたがよく分からず諦めた…。  
そのため、 **デフォルトの出力** と **fluentd 用の出力** 二つの出力を行えるように自分なりに Log クラスを拡張した。

## FuelPHP デフォルトの設定を確認

### FuelPHP デフォルトのログ出力先

`fuel/app/logs/Y/m/d.php`

### config の確認

```fuel/app/config/config.php
…
'log_threshold'    => Fuel::L_WARNING,
'log_path'         => APPPATH.'logs/',
'log_date_format'  => 'Y-m-d H:i:s',
…
```

### core の確認

`initialize()` メソッド内を抜粋。

```fuel/app/core/classes/log.php
…
			// determine the name and location of the logfile
			$path     = \Config::get('log_path', APPPATH.'logs'.DS);
			$filename = \Config::get('log_file', null);

			if(empty($filename))
			{
				$rootpath = $path.date('Y').DS;
				$filepath = $path.date('Y/m').DS;
				$filename = $filepath.date('d').'.php';
			}
			else
			{
				$rootpath = $path;
				$filepath = $path;
				$filename = $path.$filename;
			}
…
```

`$filename = \Config::get('log_file', null);` の通り、config に `‘log_file’  => 'filename'` を追記することで、日毎のファイルではなくそのファイルに出力される。

## Log クラスの拡張

今回は、 **デフォルトの出力** と **fluentd 用の出力** 二つの出力を行い fluentd 用の出力を tail で取れるようにしたいので Log クラスを拡張する。

### `fuel/app/classes/log.php` に Log クラスを追加

拡張したコアクラスが他のコアクラスからも使用されるようにするには、 元のコアクラスと同じ名前でクラスを拡張する必要がある。"fuel/app/classes/log.php" に Log クラスを以下のように追加する。

```fuel/app/classes/log.php
class Log extends \Fuel\Core\Log {}
```

### `fuel/app/bootstrap.php` の修正

コアクラスと同じ名前を持つクラスは、デフォルトでは無視される。 オートローダに置き換えたクラスを認識させるには、アプリケーションブートストラップファイルで登録するする必要があるため、コメントで説明されている通りに Log クラスを追加する。

```fuel/app/bootstrap.php
…
Autoloader::add_classes(array(
	// Add classes you want to override here
	// Example: 'View' => APPPATH.'classes/view.php',
	'Log' => APPPATH.'classes/log.php'
));
…
```

### `initialize()` メソッドの Override

以下のように、`fuel/app/classes/log.php Log` クラス内に `initialize()` メソッドを追記する。  
コアクラスの `initialize()` メソッドを Override し、 **デフォルトの出力** と **fluentd 用の出力** 二つの出力を行うことができるようにする。

※ もっと良い Log クラスの拡張の仕方があると思いますので、参考程度にご覧になってください。

```fuel/app/classes/log.php
<?php
/**
 * Fuel is a fast, lightweight, community driven PHP5 framework.
 *
 * @package    Fuel
 * @version    1.7
 * @author     Fuel Development Team
 * @license    MIT License
 * @copyright  2010 - 2015 Fuel Development Team
 * @link       http://fuelphp.com
 */

/**
 * Log core class facade for the Monolog composer package.
 *
 * This class will provide the interface between the Fuel v1.x class API
 * and the Monolog package, in preparation for FuelPHP v2.0
 */
class Log extends \Fuel\Core\Log
{
	/**
	 * initialize the created the monolog instance
	 */
	public static function initialize()
	{
		// load the file config
		\Config::load('file', true);

		// determine the name and location of the logfile
		$path     = \Config::get('log_path', APPPATH.'logs'.DS);
		$filename = \Config::get('log_file', null);

		$paths[] =
		[
			'rootpath' => APPPATH.'logs'.DS.date('Y').DS,
			'filepath' => APPPATH.'logs'.DS.date('Y/m').DS,
			'filename' => APPPATH.'logs'.DS.date('Y/m').DS.date('d').'.php'
		];

		if( ! empty($filename))
		{
			$paths[] =
			[
				'rootpath' => $path,
				'filepath' => $path,
				'filename' => $path.$filename
			];
		}

		foreach ($paths as $path)
		{
			// make sure the log directories exist
			try
			{
				// get the required folder permissions
				$permission = \Config::get('file.chmod.folders', 0777);

				if ( ! is_dir($path['rootpath']))
				{
					mkdir($path['rootpath'], 0777, true);
					chmod($path['rootpath'], $permission);
				}
				if ( ! is_dir($path['filepath']))
				{
					mkdir($path['filepath'], 0777, true);
					chmod($path['filepath'], $permission);
				}

				$handle = fopen($path['filename'], 'a');
			}
			catch (\Exception $e)
			{
				\Config::set('log_threshold', \Fuel::L_NONE);
				throw new \FuelException('Unable to create or write to the log file. Please check the permissions on '.\Config::get('log_path').'. ('.$e->getMessage().')');
			}

			if ( ! filesize($path['filename']))
			{
				fwrite($handle, "<?php defined('COREPATH') or exit('No direct script access allowed'); ?>".PHP_EOL.PHP_EOL);
				chmod($path['filename'], \Config::get('file.chmod.files', 0666));
			}
			fclose($handle);

			// create the streamhandler, and activate the handler
			$stream = new \Monolog\Handler\StreamHandler($path['filename'], \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);
		}
	}
}
```

config に `'log_file' => 'filename'` の指定がある場合は、 **デフォルトの出力** と **fluentd 用の出力** 二つの出力を行い、指定がない場合は、今まで通り **デフォルトの出力** 一つの出力のみを行う。  

### `fuel/app/config/config.php` の修正

以下のように、config に `'log_path' => '/var/log/fuelphp/'` `'log_file' => 'error.log'` を追記する。

```diff:fuel/app/config/config.php
…
	'log_threshold'    => Fuel::L_WARNING,
-	'log_path'         => APPPATH.'logs/',
+	'log_path'         => '/var/log/fuelphp/',
+	'log_file'         => 'error.log',
	'log_date_format'  => 'Y-m-d H:i:s',
…
```

上記のような設定を行った場合は出力は以下のようになる。

| 出力       | 出力先                     |
|:-----------|:--------------------------|
| デフォルト  | fuel/app/logs/Y/m/d.php    |
| fluentd 用 | /var/log/fuelphp/error.log |

### logrotate を仕掛ける

このままだと fluentd 用 に出力したログファイルが巨大化し続けるため、 `/etc/logrotate.d/fuelphp` を作成し、logrotateを仕掛けておくことが望ましい。

```/etc/logrotate.d/fuelphp
/var/log/fuelphp/error.log {
	daily
	rotate 30
	compress
	delaycompress
	notifempty
	create 666 www-data www-data
}
```

## fluentd で tail する

```/etc/td-agent/td-agent.conf
…
<source>
  type tail
  path /var/log/fuelphp/error.log
  format /^(?<level>[^ ]*) - (?<time>[^ ]* [^ ]*) --> (?<message>.*)$/
  time_format %Y-%m-%d %H:%M:%S
  tag 任意のtag名
  pos_file /var/log/td-agent/任意のpos_file名.pos
</source>
…
```

## 参考 URL

* [FuelPHPのログをfluentdの入力に | Imai blog](http://blog.glatts.com/blog/imai/?p=1022 "FuelPHPのログをfluentdの入力に | Imai blog")
* [iact.tech - fuelPHP: ログファイルの主力先を変更](http://iact-tech.tumblr.com/post/114033084205/fuelphp-%E3%83%AD%E3%82%B0%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%B8%BB%E5%8A%9B%E5%85%88%E3%82%92%E5%A4%89%E6%9B%B4 "iact.tech - fuelPHP: ログファイルの主力先を変更")
* [Core の拡張 - 概要 - FuelPHP ドキュメント](http://fuelphp.jp/docs/1.7/general/extending_core.html "Core の拡張 - 概要 - FuelPHP ドキュメント")