Laravel Backend Control Website - 10

執行後產生LOG資料 -Generate LOG Data After Execution

Hello 大家,

在執行新增、修改、刪除等動作後,必須要記錄改變後的資料以及誰進行操作,這樣出狀況才方便了解誰做出這麼偉大的事情。


首先,先建立一個 LOG 的model :

1
2
// 一次性建立
php artisan make:model Log -mcr

 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
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateLogsTable extends Migration
{
    public function up()
    {
        Schema::create('logs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('user')->comment('使用者');
            $table->string('ip')->comment('使用者IP');
            $table->string('os')->comment('作業系統');
            $table->string('browser')->comment('瀏覽器');
            $table->string('browser_detail')->comment('瀏覽器詳細資料');
            $table->string('action')->comment('動作');
            $table->string('table')->comment('資料表');
            $table->json('data')->comment('資料');
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('logs');
    }
}

Log.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Log extends Model
{
    protected $table = 'logs';

    protected $primaryKey = 'id';

    protected $fillable = [
        "user", "ip", "os", "browser", "browser_detail", "action", "table", "data",
    ];
}

1
2
3
4
Route::prefix('manage')->middleware('auth','admin')->group(function(){
    ......
    Route::resource('log', 'LogController');
});

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
views/
├── _layouts/
├── _partials/
├── auth/
└── manage/  
    └── log/
        ├── detail.blade.php
        └── index.blade.php
    └── member/  
    └── navbar/  
    └── page/

index.blade.php

 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
@extends('_layouts.manage.app')
@section('title', __('Log').__('Manage'))
@section('content')
<div class="container-fluid">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">{% raw %}{{ __('Log').__('Manage') }}{% endraw %}</div>
                <div class="card-body">
     <div class="table-responsive">
      <table id="data" class="table table-hover table-bordered text-center">
       <thead>
        <tr class="table-info active">
         <th class="text-nowrap text-center">{% raw %}{{ __('Member') }}{% endraw %}</th>
         <th class="text-nowrap text-center">{% raw %}{{ __('IP') }}{% endraw %}</th>
         <th class="text-nowrap text-center">{% raw %}{{ __('Browser') }}{% endraw %}</th>
         <th class="text-nowrap text-center">{% raw %}{{ __('Action') }}{% endraw %}</th>
         <th class="text-nowrap text-center">{% raw %}{{ __('Table') }}{% endraw %}</th>
         <th class="text-nowrap text-center">{% raw %}{{ __('Created_at') }}{% endraw %}</th>
         <th class="text-nowrap text-center">*</th>
        </tr>
       </thead>
       <tbody>
        @foreach ($logs as $log)
         <tr>
          <td>{% raw %}{{ $log->user }}{% endraw %}</td>
          <td>{% raw %}{{ $log->ip }}{% endraw %}</td>
          <td>{% raw %}{{ $log->browser }}{% endraw %}</td>
          <td>{% raw %}{{ $log->action }}{% endraw %}</td>
          <td>{% raw %}{{ $log->table }}{% endraw %}</td>
          <td>{% raw %}{{ $log->created_at }}{% endraw %}</td>
          <td>
           <form action="{% raw %}{{ route('log.show',$log->id) }}{% endraw %}" method="GET">
           @csrf
           {% raw %}{{ App\Button::detail($log->id) }}{% endraw %}
           </form>
          </td>
         </tr>
        @endforeach
       </tbody>
      </table>
     </div>
                </div>
                <div class="card-footer pagination justify-content-center table-responsive">
     {!! $logs->links("pagination::bootstrap-4") !!}
    </div>
            </div>
        </div>
    </div>
</div>
@endsection

LogController.php :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
namespace App\Http\Controllers;

use App\Log;
use Illuminate\Http\Request;

class LogController extends Controller
{
    public function index()
    {
        $logs = Log::orderby('created_at','desc')->paginate();
        return view('manage.log.index',compact('logs'));
    }
}

Log.php

 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
public static function get_agent()
{
    $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
    $browser_name = 'Unknown';
    $platform = 'Unknown';
    $version = $ub = "";

    // 取得作業系統資訊
    if (preg_match('/linux/i', $user_agent)) 
    {
        $platform = 'linux';
    }
    else if(preg_match('/macintosh|mac os x/i', $user_agent)) 
    {
        $platform = 'mac';
    }
    else if(preg_match('/windows|win32/i', $user_agent)) 
    {
        $platform = 'windows';
    }

    // 取得瀏覽器資訊
    if((preg_match('/MSIE/i',$user_agent) || preg_match('/Trident\/7.0; rv:11.0/i',$user_agent)) && !preg_match('/Opera/i',$user_agent))
    {
        $browser_name = 'Internet Explorer';
        $ub = "MSIE";
    }
    else if(preg_match('/Firefox/i',$user_agent))
    {
        $browser_name = 'Mozilla Firefox';
        $ub = "Firefox";
    }
    else if(preg_match('/Chrome/i',$user_agent))
    {
        $browser_name = 'Google Chrome';
        $ub = "Chrome";
    }
    else if(preg_match('/Safari/i',$user_agent))
    {
        $browser_name = 'Apple Safari';
        $ub = "Safari";
    }
    else if(preg_match('/Opera/i',$user_agent))
    {
        $browser_name = 'Opera';
        $ub = "Opera";
    }
    else if(preg_match('/Netscape/i',$user_agent))
    {
        $browser_name = 'Netscape';
        $ub = "Netscape";
    }

    // 瀏覽器版本
    $known = array('Version', $ub, 'other');
    $pattern = '#(?<browser>'.join('|', $known).')[/ ]+(?<version>[0-9.|a-zA-Z.]*)#';
    if (!preg_match_all($pattern, $user_agent, $matches)) 
    {
        // we have no matching number just continue
    }

    // see how many we have
    $i = count($matches['browser']);
    if ($i != 1) 
    {
        //we will have two since we are not using 'other' argument yet
        //see if version is before or after the name
        if (strripos($user_agent,"Version") < strripos($user_agent,$ub))
        {
            $version = $matches['version'][0];
        }
        else 
        {
            $version = isset($matches['version'][1]) ? $matches['version'][1] : '';
        }
    }
    else 
    {
        $version = $matches['version'][0];
    }

    // check if we have a number
    if ($version == null || $version == "")
    {
        $version = "Unknown";
    }

    return array(
        'name'  => $browser_name,
        'version' => $version,
        'platform' => $platform,
        'detail' => $user_agent
    );
}

Log.php :

 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
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Request;

class Log extends Model
{
    ......
    public static function write_log($table,$data,$action = NULL)
    {

        $agent = Log::get_agent();

        DB::table('logs')->insert([

            'user' => (Auth::user()->first())['name'],
            'ip'   => Request::ip(),
            'os'   => $agent['platform'], 
            'browser'   => $agent['name'], 
            'browser_detail'   => $agent['detail'], 
            'action'   => $action ?? Request::method(), 
            'table'   => $table, 
            'data'   => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
            'created_at'   => now()

        ]);

        return true;
    }
}
  • 第13行為進行了何種動作,新增修改或其他。
  • 第15行為新增修改刪除等寫入影響資料庫的資料,使用 json 的方式儲存。

最後在 Controller中引入 Log.php 後就可以使用 write_log() 的方式寫入LOG。

使用 MemberController.php 新增會員當範例 :

 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
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use App\User;
use App\Log;

class MemberController extends Controller
{
    ......
    public function store(Request $request)
    {
        $user = new User;

        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:1', 'confirmed'],
            'permission' => ['required', 'string', 'max:5', 'min:0'],
        ]);

        foreach ($request->except('_token','_method','password_confirmation') as $key => $value) {
            if ($request->filled($key) && $key == 'password') {
                $user->password = Hash::make($data['password']);
            }
            elseif ($request->filled($key)) {
                $user->$key = strip_tags(clean($data[$key]));
            }
        }

        if ($data) {
            // 寫入log
            Log::write_log('users',$request->all());
            $user->save();
        }

        return back()->with('success','會員新增成功 !');
    }
  ......
  • 第11行引入 Log model
  • 第38行寫入log,影響的資料表為users

實際操作一次新增會員並確認是否產生LOG。

https://i.imgur.com/OuBdopp.png
log

上圖可以看到有一個 Detail 的按鈕,用來看被寫入資料庫的資料。

修改 LogController.php :

1
2
3
4
5
public function show($id)
{
    $log = Log::where('id',$id)->first();
    return view('manage.log.detail',compact('log'));
}

detail.blade.php :

 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
@extends('_layouts.manage.app')
@section('title', __('Log').__('Detail'))
@section('content')
<div class="container-fluid">
    <div class="row justify-content-center">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header">{% raw %}{{ __('Log').__('Detail') }}{% endraw %}</div>               
                <div class="card-body">
                    <ul class="list-unstyled">
                        <li>{% raw %}{{ App\Button::GoBack(route('log.index')) }}{% endraw %}</li>
                    </ul>
                    <div class="table-responsive">     
                        <table class="table table-hover table-bordered">
                            <thead>
                                <tr class="table-info active">
                                    <th class="text-nowrap text-center">{% raw %}{{ __('Item') }}{% endraw %}</th>
                                    <th class="text-nowrap text-center">{% raw %}{{ __('Data') }}{% endraw %}</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr>
                                    <td>{% raw %}{{ __('Member') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->user }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('IP') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->ip }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Operating system') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->os }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Browser') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->browser }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Browser').__('Detail') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->browser_detail }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Action') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->action }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Table') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->table }}{% endraw %}</td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Data') }}{% endraw %}</td>
                                    <td><pre>{% raw %}{{ $log->data }}{% endraw %}</pre></td>
                                </tr>
                                <tr>
                                    <td>{% raw %}{{ __('Created_at') }}{% endraw %}</td>
                                    <td>{% raw %}{{ $log->created_at }}{% endraw %}</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
                <div class="card-footer">

                </div>
            </div>
        </div>
    </div>
</div>
@endsection

現在只要點 Detail 按鈕後就可以看到以下畫面 :

https://i.imgur.com/Xqg3TGL.png
detail

其中資料欄位就是新增會員所寫入的資料。


叮叮結束🚗

這裡有一個問題,可以看到在 detail 的資料部分,"password": "Pa$$w0rd!"

表示你剛剛輸入的密碼沒有被 hash 處理到,這是因為上面控制器寫入LOG使用的是 $request->all()

$request->all() 是處理前的資料,只要改成 Log::write_log('users',$user); 問題即可解決。