tezu memo blog

日々行った作業をメモしていきます

Laravel Monolog Cascadeを使用してログレベル毎に出力するファイルを変更する

概要

ログレベル毎に出力するファイルを変更したい

Laravel標準ではdailyでログファイルをローテートは出来る程度

.env

APP_LOG=daily

storage/logs

laravel-2016-10-04.log
laravel-2016-10-05.log
laravel-2016-10-06.log

Monolog Cascadeを使用して、ログレベル毎に出力するファイルを変更する github.com

手順

インストール

composer.jsonに以下を追記後、composer updateを実行

"require": {
    "theorchard/monolog-cascade": "^0.4.0"
}

設定

https://github.com/theorchard/monolog-cascade Configuration structureを参考に作成

config/monolog.php

<?php

return [
    'version' => 1,

    'formatters' => [
        'dashed' => [
            'format' => "%datetime%-%channel%.%level_name% - %message% %context% %extra%\n",
            'include_stacktraces' => true
        ],
    ],
    'handlers' => [
        //-------------------------------------------------
        // Admin
        // ------------------------------------------------
        'admin_info' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'INFO',
            'filename' => env('LOG_ADMIN_INFO_PATH', storage_path('logs/admin_info.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor', 'login_user_processor'],
        ],

        'admin_error' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'ERROR',
            'filename' => env('LOG_ADMIN_ERROR_PATH', storage_path('logs/admin_error.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor', 'login_user_processor'],
        ],

        // WARNINGのみ出力
        '_admin_warning' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'WARNING',
            'filename' => env('LOG_ADMIN_WARNING_PATH', storage_path('logs/admin_warning.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor', 'login_user_processor'],
        ],
        'admin_warning' => [
            'class' => 'Monolog\Handler\FilterHandler',
            'handler' => '_admin_warning',
            'minLevelOrList' => ['WARNING'],
        ],

        //-------------------------------------------------
        // Front
        // ------------------------------------------------
        'front_info' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'INFO',
            'filename' => env('LOG_FRONT_INFO_PATH', storage_path('logs/front_info.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor'],
        ],

        'front_error' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'ERROR',
            'filename' => env('LOG_FRONT_ERROR_PATH', storage_path('logs/front_error.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor'],
        ],

        // WARNINGのみ出力
        '_front_warning' => [
            'class' => 'Monolog\Handler\RotatingFileHandler',
            'level' => 'WARNING',
            'filename' => env('LOG_FRONT_WARNING_PATH', storage_path('logs/front_warning.log')),
            'maxFiles' => 5,
            'formatter' => 'dashed',
            'processors' => ['web_processor', 'introspection_processor'],
        ],
        'front_warning' => [
            'class' => 'Monolog\Handler\FilterHandler',
            'handler' => '_front_warning',
            'minLevelOrList' => ['WARNING'],
        ],
    ],
    'processors' => [
        // IP,URL,HTTP METHOD,referrer
        'web_processor' => [
            'class' => 'Monolog\Processor\WebProcessor'
        ],
        // 出力ファイル名、出力行
        'introspection_processor' => [
            'class' => 'Monolog\Processor\IntrospectionProcessor',
            'skipClassesPartials' => [
                'Monolog\\',
                'Illuminate\\',
            ],
        ],
        // ログイン情報
        'login_user_processor' => [
            'class' => 'App\Services\Monolog\Processor\LoginUserProcessor'
        ],
    ],
    'loggers' => [
        // このキーをMonologSetting::change()で指定
        'admin' => [
            'handlers' => ['admin_info', 'admin_error', 'admin_warning']
        ],
        'front' => [
            'handlers' => ['front_info', 'front_error', 'front_warning']
        ]
    ]
];

Monolog Cascadeに差し替え

LaravelのMonolog Loggerと差し替える

app/Services/Monolog/MonologSetting.php

<?php
namespace App\Services\Monolog;

use Cascade\Cascade;
use Illuminate\Log\Writer;
use Log;

class MonologSetting
{
    /**
     * ログ設定を変更する
     * @param  string $logName 変更名 config/monolog.phpのhandlersキー
     */
    public static function change($logName)
    {
        // 登録済みインスタンスを削除
        Log::clearResolvedInstances();
        app()->instance('log', new Writer(Cascade::getLogger($logName)));
    }
}

Laravelの登録方法を参考にした

https://github.com/laravel/framework/blob/5.3/src/Illuminate/Foundation/Bootstrap/ConfigureLogging.php

<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Log\Writer;
use Monolog\Logger as Monolog;
use Illuminate\Contracts\Foundation\Application;

class ConfigureLogging
{
    /**
     * Register the logger instance in the container.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return \Illuminate\Log\Writer
     */
    protected function registerLogger(Application $app)
    {
        $app->instance('log', $log = new Writer(
            new Monolog($app->environment()), $app['events'])
        );

        return $log;
    }
}

Facadeを追加

<?php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class MonologSetting extends Facade {

    protected static function getFacadeAccessor() { return 'monolog_setting'; }

}

config\app.php

<?php

return [
    'aliases' => [
        'MonologSetting' => App\Facades\MonologSetting::class, // ここを追加
    ],
];

サービスプロバイダー

MonologSettingのバインドとFront/Admin/コンソールでロガーを切替

artisanコマンドで雛型作成

php artisan make:provider MonologSettingServiceProvider
Provider created successfully.
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use MonologSetting;
use Cascade\Cascade;

class MonologSettingServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Cascade::fileConfig(config_path('monolog.php'));

        $changeName = null;
        // コマンド実行時とwebアクセス時で設定を変更
        if (php_sapi_name() == 'cli') {
            $changeName = 'console';
        } elseif (str_is('/admin/*', env('REQUEST_URI'))) {
            $changeName = 'admin';
        }
        if (!is_null($changeName)) {
            MonologSetting::change($changeName);
        }
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        // キーはMonologSettingファサードのgetFacadeAccessor()の戻り値と一致
        \App::bind('monolog_setting', function()
        {
            return new \App\MonologSetting;
        });
    }
}

config\app.php

'providers' => [

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
    App\Providers\MonologSettingServiceProvider::class, // add
],

ログインユーザ名を出力

認証認可はSentinel Manual :: Cartalystを使用

<?php

namespace App\Services\Monolog\Processor;

use Sentinel;

class LoginUserProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        if (Sentinel::check()) {
            $user = Sentinel::getUser();
            $record['extra']['username'] = $user->getUserLogin();
        }

        return $record;
    }
}

結果

変更前

storage/logs/laravel-2016-10-13.log

[2016-10-13 07:02:52] local.WARNING: array (
  '_method' => 'PUT',
  '_token' => 'E5ola1t7HaX79kfkEcZW3oNxhdYau10SdxDUuBDc',
  'first_name' => '',
  'last_name' => '',
  'email' => 'admin@xxxxx.xxx',
  'password' => 'xxxxx',
  'password_confirm' => '',
)  
[2016-10-13 07:02:52] local.WARNING: array (
  'first_name' => 
  array (
    0 => 'The first name field is required.',
  ),
  'last_name' => 
  array (
    0 => 'The last name field is required.',
  ),
  'email' => 
  array (
    0 => 'The email has already been taken.',
  ),
)  
[2016-10-13 07:05:51] local.ERROR: exception 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException' in \vendor\laravel\framework\src\Illuminate\Routing\RouteCollection.php:161
Stack trace:
#0 \vendor\laravel\framework\src\Illuminate\Routing\Router.php(821): Illuminate\Routing\RouteCollection->match(Object(Illuminate\Http\Request))

変更後

errorレベル

storage/logs/front_error-2016-10-13.log

2016-10-13 07:31:04-front.ERROR - exception 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException' in vendor\laravel\framework\src\Illuminate\Routing\RouteCollection.php:161
Stack trace:
#0 vendor\laravel\framework\src\Illuminate\Routing\Router.php(821): Illuminate\Routing\RouteCollection->match(Object(Illuminate\Http\Request))

warnレベル

storage/logs/front_warning-2016-10-13.log

2016-10-13 07:31:45-front.WARNING - array (
  '_method' => 'PUT',
  '_token' => 'E5ola1t7HaX79kfkEcZW3oNxhdYau10SdxDUuBDc',
  'first_name' => '',
  'last_name' => '',
  'email' => 'admin@xxxxx.xxx',
  'password' => 'xxxxx',
  'password_confirm' => '',
)
2016-10-13 07:31:45-front.WARNING - array (
  'first_name' => 
  array (
    0 => 'The first name field is required.',
  ),
  'last_name' => 
  array (
    0 => 'The last name field is required.',
  ),
  'email' => 
  array (
    0 => 'The email has already been taken.',
  ),
)