--> -->
Laravelのログは5.6以降で拡張方法が若干変わりました。
これは古い記事の更新版です。
修正、追加が必要なファイルは以下の通りです。
次の項で1つずつ見ていきましょう。
この記事ではログの記録と閲覧を行いますが、まずはログの記録編です。
DB_LOG_TABLE=logs DB_LOG_CONNECTION=mysql_logまた、後ほど新たに定義するログチャンネルを指定します。
LOG_CHANNEL=logtable
'default' => env('DB_CONNECTION', 'mysql'),
'log' => env('DB_LOG_CONNECTION', 'mysql_log'),
'logtable' => env('DB_LOG_TABLE', 'logs'),
:
'connections' => [
:
'mysql_log' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
'fetch' => PDO::FETCH_ASSOC,
],
'default' => env('LOG_CHANNEL', 'stack'), : 'logtable' => [ 'driver' => 'monolog', 'handler' => \App\Log\MySqlHandler::class, 'formatter' => 'default', 'with' => [ 'level' => Logger::DEBUG, 'maxdays' => 7, ], ],記録するログレベルやログの残存期間も指定できます。
<?php
namespace App\Log;
use DB;
use Monolog\Handler\AbstractHandler;
use Monolog\Logger;
class MySqlHandler extends AbstractHandler
{
protected $table;
protected $connection;
protected $maxdays;
protected $isTesting;
/**
* @param int $level The minimum logging level at which this handler will be triggered
* @param int $maxdays The days of keep the logs records.
* @param bool $isTesting Whether the environment is testing or not.
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $maxdays = 7, $isTesting = false, $bubble = true)
{
$this->table = config()->get('database.logtable');
$this->connection = config()->get('database.log');
$this->maxdays = $maxdays;
$this->isTesting = $isTesting;
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
// write log to mysql table
$data = [
'message' => $record['message'],
'channel' => $record['channel'], // local/provider...
'level' => $record['level'],
'level_name' => $record['level_name'],
'context' => json_encode($record['context']),
'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null,
'created_at' => $record['datetime']->format("Y-m-d H:i:s.u"),
];
if( null!=$this->connection and null!=$this->table ) {
DB::connection($this->connection)->table($this->table)->insert($data);
}
return true;
}
/**
* delete old logs
*/
public function close() {
parent::close();
// DB has a no function in case of testing.
if( !$this->isTesting and null!=$this->connection and null!=$this->table ) {
DB::connection($this->connection)
->table($this->table)
->where('created_at','<',DB::Raw('DATE_ADD(NOW(), INTERVAL -'.$this->maxdays.' DAY)'))
->delete();
}
}
}
-- DROP TABLE logs; CREATE TABLE logs ( id BIGINT NOT NULL AUTO_INCREMENT, channel VARCHAR(10), level INT, level_name VARCHAR(10), message LONGTEXT, context TEXT, remote_addr VARCHAR(40), user_agent TEXT, created_at TIMESTAMP(6) default CURRENT_TIMESTAMP(6), CONSTRAINT PRIMARY KEY( id ) ); CREATE INDEX logs_idx1 on logs ( remote_addr ); CREATE INDEX logs_idx2 on logs ( level );
次にビューワの方を作成していきます。
Laravelではページネーション*2というページ管理機能を持っています。この機能が生成するHTMLは Bootstrap CSSフレームワーク に対応したものです。また、参考にした従来のビューア(rap2hpoutre/laravel-log-viewer)もBootstrapを使用していたことから、今回も全体的にBootstrapを使用しました。
// IPアドレス制限をかける
Route::group(array('middleware' => 'auth.ipaddress'), function () {
Route::get('oldlogs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index');
Route::any('logs', 'LogViewController@anyView');
});
<?php
namespace App\Http\Controllers;
use DB;
use Illuminate\Support\Facades\Input;
use Monolog\Logger;
use View;
class LogViewController extends Controller {
// 各行のクラス名
private $levels_classes = [
'DEBUG' => '',
'INFO' => 'info',
'NOTICE' => 'info',
'WARNING' => 'warning',
'ERROR' => 'danger',
'CRITICAL' => 'danger',
'ALERT' => 'danger',
'EMERGENCY' => 'danger',
];
// 行頭のアイコン
private $levels_imgs = [
'DEBUG' => 'debug', // 出ないけどその方が分かりやすい
'INFO' => 'info',
'NOTICE' => 'info',
'WARNING' => 'warning',
'ERROR' => 'warning',
'CRITICAL' => 'warning',
'ALERT' => 'warning',
'EMERGENCY' => 'warning',
];
// levelでのDB検索用
private $levels_value = [
'debug' => Logger::DEBUG,
'info' => Logger::INFO,
'notice' => Logger::NOTICE,
'warning' => Logger::WARNING,
'error' => Logger::ERROR,
'critical' => Logger::CRITICAL,
'alert' => Logger::ALERT,
'emergency' => Logger::EMERGENCY,
];
// level用チェック初期値
private $level_chk = [
'debug'=>1,
'info'=>1,
'notice'=>1,
'warning'=>1,
'error'=>1,
'critical'=>1,
'alert'=>1,
'emergency'=>1,
];
// special words(検索文字列において特別な意味を持つワード)
private $search_sw = [
'ip:'=>'remote_addr', // 'ip:'に続く文字列はremote_addrから検索する
'ua:'=>'user_agent',
''=>'message',
];
// 1ページの表示件数
private $rpp_list = [
25=>'25',
50=>'50',
100=>'100'
];
/**
* ログViewer: MySQLに格納されたログを表示する
* @return mixed
*/
public function anyView() {
$search = Input::get('search');
$rpp = intval(Input::get('rpp')); // rows per page
$chk_get = Input::get('level_chk');
$delete = Input::get('delete');
$level_chk = [];
if( $delete == 'all' ){
DB::connection(config()->get('database.log'))->table('logs')->delete();
return redirect(route('logs',['rpp'=>$rpp])); // URLから'delete=all'を消すためredirectする
}
// checkの初期値をセット
if( null == $chk_get ){
$level_chk = $this->level_chk;
} else {
foreach( $this->level_chk as $name => $check ) {
$level_chk[$name] = isset($chk_get[$name]) ? $chk_get[$name] : 0;
}
}
// 検索(ログに記録されないようログ用のコネクションでアクセス)
$tmpSql = DB::connection(config()->get('database.log'))
->table('logs')
->select('*',DB::raw('concat(created_at) as created_at_ms')) // _ms=with microsec.
->orderBy('created_at','desc');
if( !empty($search) ){
// キーワード検索
foreach( explode(" ", $search) as $keyword ){
foreach($this->search_sw as $sw => $field){
if( empty($sw) or substr($keyword,0,strlen($sw))==$sw ){
$tmpSql = $tmpSql->where($field,'like','%'.substr($keyword,strlen($sw)).'%');
break;
}
}
}
}
// レベルで絞る
$aryKeys = array_keys($level_chk,true);
if( count($aryKeys)>0 and count($aryKeys)<count($level_chk) ){
// 全チェックかノーチェック以外は条件を付ける
$arySearch = [];
foreach( $aryKeys as $value ){
$arySearch[] = $this->levels_value[$value];
}
$tmpSql = $tmpSql->whereIn('level',$arySearch);
}
// 検索GO!
$logs = $tmpSql->paginate($rpp==0 ? current($this->rpp_list) : $rpp );
// 表示用の値をセット
foreach($logs as $key => $value){
$logs[$key]->levels_classes = $this->levels_classes[$value->level_name];
$logs[$key]->levels_imgs = $this->levels_imgs[$value->level_name];
$uaa = explode(' ',$value->user_agent);
$logs[$key]->short_user_agent = end($uaa); // end()の中は必ず変数の必要あり
}
// View表示
return View::make('admin.log_viewer',['logs'=>$logs, 'search'=>$search, 'rpp'=>$rpp, 'rpp_list'=>$this->rpp_list, 'level_chk'=>$level_chk]);
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
|
以上で完成です!
うまく動かない場合にデバッグが難しい(ログが出ないから)のですが、「var_dump() & exit;」を駆使するか、xdebugでステップ実行するなどして頑張ってみてください。
私はPhpStormで無駄な警告が出ないよう、Laravel IDE Helperを入れています。そのため、 use DB; なんて書き方をしています。IDE Helperを導入していない方は自分の環境に合わせて書き換えてくださいm(_ _)m。
SQL文をログ出力している場合、「SQL実行→LOG出力→LOGをDBに出力→SQL実行→・・・」の無限ループになります。
このため、ログ出力の為のSQL文をログ出力しないようif文を入れる必要があります。
私は app/Providers/AppServiceProvider.php でログ出力していたので、以下のように処理を追加しました。
public function boot()
{
// DBのSQLをログ出力する
DB::listen(function ($query) {
// LogのDBへの書き出しはログ出力しない(無限loopになる)
if( $query->connectionName != config()->get('database.log') ) {
Log::info("Query Time:[$query->time] $query->sql, data:[[" . implode(', ', $query->bindings). "]]");
}
});
古い記事でも触れたように、テスト環境.env.testingでは、
LOG_CHANNEL=stack
とするのが良いと思います。
Laravel6では(もっと前から?)ログのスタックトレース部などはmessageではなくcontextに入るようになったようです。これに対応するには、log_viewer.blade.phpの98行目付近を以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
LaravelのログをMysqlで管理する。
https://laravel.cg0.xyz/laravel-mysql-email-log/
Laravel 5.4 データベース:ペジネーション
https://readouble.com/laravel/5.4/ja/pagination.html