Laravel Commandでお手軽ログ出力


デフォルトではコマンドでエラーが発生しても何も出力されない。
必要に応じてtry-catchを書くのは面倒だし、細かくtry-catchする必要もない。
面倒なので継承するだけでいろいろやってくれるラッパークラスを作った。

# command実行時にいろいろするラッパークラス
コマンド開始・終了時にログ出力（処理時間、メモリ使用量）
コマンド中に発生した`Exception`を拾ってメッセージから必要な個所だけ抜き取って出力

```php:app/Libs/Command/LogCommand.php
namespace App\Libs\Command;

use Illuminate\Console\Command;
use \Symfony\Component\Console\Input\InputInterface;
use \Symfony\Component\Console\Output\OutputInterface;
use \Exception;
use \InvalidArgumentException;
use \BadMethodCallException;

class LogCommand extends Command
{
    public function __construct()
    {
        parent::__construct();
    }
    
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $return = -1;
        $memory_limit = '4096M';      // configとか別の場所で設定できるように
        ini_set('memory_limit', $memory_limit);
        $params = $input->getArguments();
        unset($params['command']);
        $time = microtime(true);
        app()->BatchLog->notice($this->getName() . ' start. ' . json_encode($params) . ' [memory_limit=' . $memory_limit . ']');
        try {
            $return = $this->laravel->call([$this, 'handle']);
        } catch (InvalidArgumentException $e) {
            self::_error_trace($e);
        } catch (BadMethodCallException $e) {
            self::_error_trace($e);
        } catch (Exception $e) {
            self::_error_trace($e);
        }
        $end_time = microtime(true);
        $diff_time = round($end_time - $time, 3);
        $max = str_replace('M', '', $memory_limit);
        $peak = round(memory_get_peak_usage(true) / 1024 / 1024, 3);
        $used = ($max != 0) ? round((int)$peak / (int)$max * 100, 3) : '--';
        app()->BatchLog->notice($this->getName() . ' end. exec_time(s)=' . $diff_time . '; ' . sprintf('Memory usage: %s %% used. (max: %sM, now: %sM)', $used, $max, $peak) . '');
        return $return;
    }

    /**
     * @param Exception|InvalidArgumentException|BadMethodCallException $e
     */
    private function _error_trace($e)
    {
        $ts = explode(PHP_EOL, $e->getTraceAsString());
        $trace = $e->getMessage() . PHP_EOL;
        foreach ($ts as $v) {
            if (strpos($v, '/app/Console/Commands/') !== false) {
                $trace .= $v . PHP_EOL;
            }
        }
        self::error($trace);
    }

    public function debug($string, $verbosity = null)
    {
        parent::line($string, null, $verbosity);
        app()->BatchLog->debug($string);
    }

    public function info($string, $verbosity = null)
    {
        parent::line($string, null, $verbosity);
        app()->BatchLog->info($string);
    }

    public function notice($string, $verbosity = null)
    {
        parent::line($string, null, $verbosity);
        app()->BatchLog->notice($string);
    }

    public function warning($string, $verbosity = null)
    {
        $this->warn($string, $verbosity);
    }

    public function warn($string, $verbosity = null)
    {
        parent::line($string, null, $verbosity);
        app()->BatchLog->warning($string);
    }

    public function error($string, $verbosity = null)
    {
        parent::line($string, null, $verbosity);
        app()->BatchLog->error($string);
    }
}
```

`php artisan make:command xxxx`で作ったクラスの継承元を`Command`から`LogCommand`に置き換えます。


## LogCommandを使うために必要なクラスなど

### バッチ処理用のログ出力クラス
`storage/logs/batch/`にログ出力する。

```php:app/Log/BatchLog.php
namespace App\Log;

use Illuminate\Log\LogServiceProvider;
use Illuminate\Log\Writer;

class BatchLog extends LogServiceProvider
{
    public function register()
    {
        $this->app->singleton('BatchLog', function () {
            return $this->createLogger();
        });
    }

    protected function channel()
    {
        return 'batchlog';
    }

    protected function configureSingleHandler(Writer $log)
    {
        $log->useFiles(
            $this->path() . $this->getFileName(),
            $this->logLevel()
        );
    }

    protected function configureDailyHandler(Writer $log)
    {
        $log->useDailyFiles(
            $this->path() . $this->getFileName(), $this->maxFiles(),
            $this->logLevel()
        );
    }

    protected function configureSyslogHandler(Writer $log)
    {
        $log->useSyslog('batchlog', $this->logLevel());
    }

    private function path(): string
    {
        return env('LOG_DIR', $this->app->storagePath() . '/logs/batch/');
    }

    private function getFileName(): string
    {
        return 'batch.log';
    }
}
```

### ログ出力クラスのサービスプロバイダ

```bash
php artisan make:provider BatchLogServiceProvider
```

```php:app/Providers/BatchLogServiceProvider.phpを編集
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class BatchLogServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function register()
    {
        app()->register(\App\Log\BatchLog::class);
    }

    public function provides()
    {
        return ['BatchLog'];
    }
}
```

このクラスを`config/app.php`に追記しておく。