Laravel6でArtisanコマンド作成し、そのArtisanコマンドをバッチで実行するような実装をしていました。
ログ出力をlaravel.logではなく、DBに出力する必要があったため、Laravelに標準でインストールされているMonologというパッケージを利用しました。
バージョン:Laravel 6.20.32
作業内容
- マイグレーションファイル、モデルの作成
- app/Loggers/DbMonologHandler.phpの作成
- config/logging.phpにチャネルを追加
- .envに追記
- Artisanコマンドにログを記述
マイグレーションファイル、モデルの作成
ログ出力先のテーブルを作成します。
今回はd_logs
というテーブルを作成。DLogモデルを作成し、マイグレーションも一緒に作成します。
php artisan make:model DLog --migration
モデルの記述
使用するテーブルを記載します。(省略した場合、モデル名DLogの場合、d_logs
テーブルが使用されるので、省略可能。)
すべてのカラムに値の代入可能とするので、protected $guarded = [];
とします。
<?php
namespace App\models;
use Illuminate\Database\Eloquent\Model;
class DLog extends Model
{
protected $table = 'd_logs';
protected $guarded = [];
}
マイグレーションの記述
- id
- level(debugなら100、errorなら400など)
- level_name(INFOやERRORなど)
- message(エラーメッセージなど)
他に必要なカラムがあれば適宜追加してください。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('d_logs', function(Blueprint $table)
{
$table->bigIncrements('id');
$table->text('level')->nullable();
$table->text('level_name')->nullable();
$table->text('message')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('d_logs');
}
}
app/Loggers/DbMonologHandler.phpの作成
今回出力する'level', 'level_name', 'message'を配列$dataに入れてあげます。
<?php
namespace App\Loggers;
use Illuminate\Support\Facades\DB;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use App\Models\DLog;
use Log;
class DbMonologHandler extends AbstractProcessingHandler
{
public function __construct(string $table, string $connection, $level = Logger::DEBUG, $bubble = true)
{
$this->table = $table;
$this->connection = $connection;
parent::__construct($level, $bubble);
}
protected function write(array $record): void
{
$data = [
'message' => $record['message'],
'level' => $record['level'],
'level_name' => $record['level_name'],
];
DLog::insert($data);
}
}
config/logging.phpにチャネルを追加
既存の他のチャネルと同様の記述で、チャネルを追加します。(今回は'db'という名前で追加)
::class
は、クラス名を名前空間と一緒に返しています。
'handler_with'で接続先などを指定します。env関数の第二引数には、デフォルト値を記述しているので、.envファイルに記述がない場合、この第二引数の値が使われます。
db' => [
'driver' => 'monolog',
'handler' => DbMonologHandler::class,
'handler_with' => [
'table' => env('DB_LOG_TABLE', 'd_logs'),
'connection' => env('DB_LOG_CONNECTION', env('DB_CONNECTION', 'mysql')),
],
],
チャネルの一番上、stack
に記載のチャネルにログが出力されるので、作成した'db'チャネルを追記します。
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'db'],
'ignore_exceptions' => false,
],
.envに追記
.envファイルに下記を追記。(書かない場合は、logging.phpのenv関数の第二引数の値が使われる。)
DB_LOG_TABLE=d_logs
DB_LOG_CONNECTION=mysql
Artisanコマンドにログを記述
今回は、特定のArtisanコマンドを実行した際に、ログがDBに出力されるようにしたいので、ArtisanコマンドにLogファサードで記述します。今回はバッチでデータをインポートしているので、
- インポート開始
- インポート完了
- エラーメッセージ
- ロールバック完了
などのログを出力しています。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\UpsertAllByCsvService;
use Log;
use DB;
class ImportOrderDataSetByCsv extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ImportOrderDataSetByCsv';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Upsert tables concerned about orders';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Log::info('インポート開始');
DB::beginTransaction();
try {
UpsertAllByCsvService::getOrdersDataByCsv('test');
UpsertAllByCsvService::getOrderDetailsDataByCsv('test');
UpsertAllByCsvService::getShippingsDataByCsv('test');
UpsertAllByCsvService::getShipmentItemsDataByCsv('test');
DB::commit();
Log::info('インポート完了');
} catch (\Exception $e) {
DB::rollback();
Log::error($e->getMessage());
Log::info('ロールバック完了');
}
}
}
このArtisanコマンドをTinkerで実行すると、d_logs
テーブルにレコードが追加されました!
ロールバックのタイミングに注意。ログテーブルのidが飛んでいる場合、ログは生成されているのに、そのログ出力も含めてロールバックされている可能性があります。
この記述だと、DbMonologHandlerで出力されるログは、laravel.logにも出力されます。
DBにのみ出力したい場合は、logging.phpのチャネル設定にて、stack
には'db'を記載せず、
ログファサードを使う時に、Log::channel('db')->info('インポート開始');
のように記述してください。
参考
https://qiita.com/kd9951/items/35b0a0c4e16353078b70
https://qiita.com/hirorin/items/409a66e63bb3b16245eb