LoginSignup
18
13

More than 5 years have passed since last update.

Laravel5.4 ログ周りの設定 2/3 クエリログを別ファイルで

Last updated at Posted at 2017-06-18

概要

ログ周りの設定レシピ、3品。今回はその2品目。

やりたいこと

  • 実行されたクエリをログに出力
  • クエリログは別ファイルに出力(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

実装手順

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

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

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メソッドの機能は削除しています。

以上です。

18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13