概要
ログ周りの設定レシピ、3品。今回はその2品目。
- Laravel5.4 ログ周りの設定 1/3 開始・終了ログを残す
- Laravel5.4 ログ周りの設定 2/3 クエリログを別ファイルで(本記事)
- Laravel5.4 ログ周りの設定 3/3 ミリ秒に出力場所を添えて
やりたいこと
- 実行されたクエリをログに出力
- クエリログは別ファイルに出力(sql.log)
- ウェブアクセスとartisanコマンドのログを分けて出力
storage\log\app.log
[2017-06-17 14:28:28] local.INFO: -- Startup {"method":"GET","uri":"/"}
[2017-06-17 14:28:28] local.INFO: -- Shutdown {"time":"186.202[ms]","memory":"4096[kb]"}
storage\log\sql.log
[2017-06-17 14:28:28] local.DEBUG: select * from `users`; {"time":6.88}
storage\log\cli_app.log
[2017-06-17 14:41:01] local.INFO: -- Startup {"command":"php artisan log:sample"}
[2017-06-17 14:41:01] local.INFO: -- Shutdown {"time":"444.789[ms]","memory":"6144[kb]"}
storage\log\cli_sql.log
[2017-06-17 14:41:01] local.DEBUG: select * from `users`; {"time":12.47}
環境
Laravel 5.4.25
PHP 7.1.3
実装手順
- 独自のLogServiceProviderクラスを作成
- \Illuminate\Foundation\Applicationクラスを継承したクラスを作成し、LogServiceProviderを登録している箇所を差し替える
- Applicationクラスの差し替え(bootstrap\app.php)
- クエリログを出力するイベントリスナークラスを作成
- クエリログを出力するイベントリスナークラスを登録
以下、サンプル
一応動作確認はしていますが、サンプルをそのまま使う場合は自己責任で。
LogServiceProviderクラス作成
app\Providers\LogServiceProvider.php
<?php
namespace App\Providers;
use Carbon\Carbon;
use Illuminate\Support\ServiceProvider;
use Illuminate\Log\Writer;
use Monolog\Logger as Monolog;
/**
* ロガー登録プロバイダ
*
* @package app.Providers
*/
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('log', function () {
return $this->createAppLogger();
});
$this->app->singleton('sql-log', function () {
return $this->createSqlLogger();
});
}
/**
* Create the app logger.
*
* @return \Illuminate\Log\Writer
*/
public function createAppLogger()
{
$log = new Writer(
new Monolog($this->channel()), $this->app['events']
);
$this->configureHandler($log, 'app');
return $log;
}
/**
* Create the sql logger.
*
* @return \Illuminate\Log\Writer
*/
public function createSqlLogger()
{
$log = new Writer(
new Monolog($this->channel()), $this->app['events']
);
$this->configureHandler($log, 'sql');
return $log;
}
/**
* Get the name of the log "channel".
*
* @return string
*/
protected function channel()
{
return $this->app->bound('env') ? $this->app->environment() : 'production';
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Log\Writer $log
* @param string $base_name
* @return void
*/
protected function configureHandler(Writer $log, $base_name)
{
$this->{'configure'.ucfirst($this->handler()).'Handler'}($log, $base_name);
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Log\Writer $log
* @param string $base_name
* @return void
*/
protected function configureSingleHandler(Writer $log, $base_name)
{
$log->useFiles(
sprintf('%s/logs/%s%s.log', $this->app->storagePath(), $this->getFilePrefix(), $base_name),
$this->logLevel()
);
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Log\Writer $log
* @param string $base_name
* @return void
*/
protected function configureDailyHandler(Writer $log, $base_name)
{
$log->useDailyFiles(
sprintf('%s/logs/%s%s.log.%s', $this->app->storagePath(), $this->getFilePrefix(), $base_name, Carbon::now()->format('Ymd')),
$this->maxFiles(),
$this->logLevel()
);
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Log\Writer $log
* @param string $base_name
* @return void
*/
protected function configureSyslogHandler(Writer $log, $base_name)
{
$log->useSyslog($base_name, $this->logLevel());
}
/**
* Configure the Monolog handlers for the application.
*
* @param \Illuminate\Log\Writer $log
* @param string $base_name
* @return void
*/
protected function configureErrorlogHandler(Writer $log, $base_name)
{
$log->useErrorLog($this->logLevel());
}
/**
* Get the default log handler.
*
* @return string
*/
protected function handler()
{
if ($this->app->bound('config')) {
return $this->app->make('config')->get('app.log', 'single');
}
return 'single';
}
/**
* Get the log level for the application.
*
* @return string
*/
protected function logLevel()
{
if ($this->app->bound('config')) {
return $this->app->make('config')->get('app.log_level', 'debug');
}
return 'debug';
}
/**
* Get the maximum number of log files for the application.
*
* @return int
*/
protected function maxFiles()
{
if ($this->app->bound('config')) {
return $this->app->make('config')->get('app.log_max_files', 5);
}
return 0;
}
/**
* ファイルプレフィックスを取得
*
* @return string
*/
private function getFilePrefix()
{
return php_sapi_name() == 'cli' ? 'cli_' : '';
}
}
Applicationクラス作成
app\Application.php
<?php
namespace App;
use App\Providers\LogServiceProvider;
use Illuminate\Events\EventServiceProvider;
use Illuminate\Routing\RoutingServiceProvider;
/**
* アプリケーションクラス
* ログのサービスプロバイダを変更したいため、overwrite
*
* @package app.Providers
*/
class Application extends \Illuminate\Foundation\Application
{
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
// ここを差し替え
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
Applicationクラスの差し替え
bootstrap\app.php
// Applicationクラスを差し替え
//$app = new Illuminate\Foundation\Application(
$app = new \App\Application(
realpath(__DIR__.'/../')
);
クエリログを出力するイベントリスナークラス
app\Listener\QueryLogTracker.php
<?php
namespace App\Listeners;
use Illuminate\Database\Events\QueryExecuted;
use DateTime;
use DB;
/**
* クエリログ出力
*
* @package app.Listeners
*/
class QueryLogTracker
{
/**
* Handle the event.
*
* @param QueryExecuted $event
* @internal param $query
* @internal param $bindings
* @internal param $time
*/
public function handle(QueryExecuted $event)
{
// クエリ情報を見やすく整形
$time = $event->time;
$bindings = $event->bindings;
foreach ($bindings as $i => $binding) {
if ($binding instanceof DateTime) {
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
} else if (is_string($binding)) {
$bindings[$i] = DB::getPdo()->quote($binding);
} else if (null === $binding) {
$bindings[$i] = 'null';
}
}
$query = str_replace(array('%', '?', "\r", "\n", "\t"), array('%%', '%s', ' ', ' ', ' '), $event->sql);
$query = preg_replace('/\s+/uD', ' ', $query);
$query = vsprintf($query, $bindings) . ';';
// ログへ出力
app('sql-log')->debug($query, compact('time'));
}
}
イベントリスナー登録
app\Providers\EventServiceProvider
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
// 以下を追加
'Illuminate\Database\Events\QueryExecuted' => [
'App\Listeners\QueryLogTracker',
],
];
補足1
Laravel5.2(5.3も?)では、「app\Http\Kernel.php」でLogServiceProviderを登録しているのでそこを差し替えてください。ただし、サンプルのLogServiceProviderではインターフェースが合わないので上手く調整してください。
補足2
サンプルでは、Monologの設定をカスタマイズするための、configureMonologUsingメソッドの機能は削除しています。
以上です。