Laravel5.4 ログ周りの設定 2/3 クエリログを残すなど

概要

ログ周りの設定レシピ、3品。今回はその2品目。
Laravel5.4 ログ周りの設定 1/3 開始・終了ログを残す
– Laravel5.4 ログ周りの設定 2/3 クエリログを残すなど(本記事)
– Laravel5.4 ログ周りの設定 3/3 ミリ秒まで表示するなど(準備中)

やりたいこと

  • 実行されたクエリをログに出力
  • クエリログは別ファイルに出力(sql.log)
  • ウェブアクセスとartisanコマンドのログを分けて出力
storagelogapp.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]"} 
storagelogsql.log
[2017-06-17 14:28:28] local.DEBUG: select * from `users`; {"time":6.88} 
storagelogcli_app.log
[2017-06-17 14:41:01] local.INFO: -- Startup {"command":" artisan log:sample"} 
[2017-06-17 14:41:01] local.INFO: -- Shutdown {"time":"444.789[ms]","memory":"6144[kb]"} 
storagelogcli_sql.log
[2017-06-17 14:41:01] local.DEBUG: select * from `users`; {"time":12.47} 

環境

Laravel 5.4.25
PHP 7.1.3

実装手順

  1. 独自のLogServiceProviderクラスを作成
  2. IlluminateFoundationApplicationクラスを継承したクラスを作成し、LogServiceProviderを登録している箇所を差し替える
  3. Applicationクラスの差し替え(bootstrapapp.php)
  4. クエリログを出力するイベントリスナークラスを作成
  5. クエリログを出力するイベントリスナークラスを登録

以下、サンプル
一応動作確認はしていますが、サンプルをそのまま使う場合は自己責任で。

LogServiceProviderクラス作成

appProvidersLogServiceProvider.php
<?php

namespace AppProviders;

use CarbonCarbon;
use IlluminateSupportServiceProvider;
use IlluminateLogWriter;
use MonologLogger 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 IlluminateLogWriter
     */
    public function createAppLogger()
    {
        $log = new Writer(
            new Monolog($this->channel()), $this->app['events']
        );
        $this->configureHandler($log, 'app');
        return $log;
    }

    /**
     * Create the sql logger.
     *
     * @return IlluminateLogWriter
     */
    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  IlluminateLogWriter  $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  IlluminateLogWriter  $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  IlluminateLogWriter  $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  IlluminateLogWriter  $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  IlluminateLogWriter  $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クラス作成

appApplication.php
<?php

namespace App;

use AppProvidersLogServiceProvider;
use IlluminateEventsEventServiceProvider;
use IlluminateRoutingRoutingServiceProvider;

/**
 * アプリケーションクラス
 * ログのサービスプロバイダを変更したいため、overwrite
 *
 * @package app.Providers
 */
class Application extends IlluminateFoundationApplication
{

    /**
     * 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クラスの差し替え

bootstrapapp.php
// Applicationクラスを差し替え
//$app = new IlluminateFoundationApplication(
$app = new AppApplication(
    realpath(__DIR__.'/../')
);

クエリログを出力するイベントリスナークラス

appListener
<?php

namespace AppListeners;

use IlluminateDatabaseEventsQueryExecuted;
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'));
    }
}

イベントリスナー登録

appProvidersEventServiceProvider
class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        // 以下を追加
        'IlluminateDatabaseEventsQueryExecuted' => [
            'AppListenersQueryLogTracker',
        ],
    ];

補足1

Laravel5.2(5.3も?)では、「appHttpKernel.php」でLogServiceProviderを登録しているのでそこを差し替えてください。ただし、サンプルのLogServiceProviderではインターフェースが合わないので上手く調整してください。

補足2

サンプルでは、Monologの設定をカスタマイズするための、configureMonologUsingメソッドの機能は削除しています。

以上です。

[紹介元] PHPタグが付けられた新着投稿 – Qiita Laravel5.4 ログ周りの設定 2/3 クエリログを残すなど

関連記事