Laravel Auditingでデータベースの変更を監視する

『誰がいつ、データをいじったか知りたい』というデータの監視が必要な時に、[Laravel Auditing](http://www.laravel-auditing.com/)が便利です。

## 概要
Laravel Auditingはデータベース上の指定した監視対象のテーブルに追加・更新・削除などの変更が行われた場合に、誰がどのような変更を行ったかを自動でログ保存するパッケージです。

![outline.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9882/7283423f-56a7-dfbb-bb80-9d1ada558a4b.jpeg)

## インストール & 設定

### インストール
composerで行います。

```
composer require owen-it/laravel-auditing
```

### 設定
インストール後、`config/app.php`にプロバイダーを追加します。

```diff:config/app.php
'providers' => [
+    OwenIt\Auditing\AuditingServiceProvider::class,
],
```

追加したら、Laravel Auditing用の設定ファイルを作成するために以下のコマンドを実行します。

```
php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="config"
```

実行すると、`config/audit.php`というファイルが作成されます。特にカスタマイズする必要がなければそのまま、データベースのマイグレーションファイルを以下のコマンドで作成します。

```
php artisan vendor:publish --provider "OwenIt\Auditing\AuditingServiceProvider" --tag="migrations"
```

実行すると、auditsテーブルを作成するための`database/migrations/yyyy_mm_dd_nnnnnn_create_audits_table.php`というファイルが作成されます。作成後にマイグレーションしてauditsテーブルを作成します。

```
php artisan migrate
```

## 監視
### 基本的な設定
設定が完了したら、監視したいデータのモデルに追記します。例えばitemsテーブルのデータを監視したい場合は、

```diff:app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
+ use OwenIt\Auditing\Contracts\Auditable;
+
+ class Item extends Model implements Auditable
- class Item extends Model
{

+    use \OwenIt\Auditing\Auditable;

```

ちょっとややこしいのですが、インターフェイスは`OwenIt\Auditing\Contracts\Auditable`、traitは`\OwenIt\Auditing\Auditable`を指定します。これらを追記するだけで、監視対象となります。

### 実際に監視
では、このテーブルに対して新規登録・変更・削除を行ってみましょう。これらを実行すると、auditsテーブルに以下のようなデータが追加されます。

| id | user\_type | user\_id | event | auditable\_type | auditable\_id | old\_values | new\_values | url | ip\_address | user\_agent | tags | created\_at | updated\_at |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | NULL | NULL | updated | App\\Item | 2 | {"name":"\\u8ffd\\u52a01"} | {"name":"\\u8ffd\\u52a02"} | http://localhost:8000/items/2 | 127.0.0.1 | Mozilla/5.0 \(Macintosh; Intel Mac OS X 10\_15\_3\) AppleWebKit/605.1.15 \(KHTML, like Gecko\) Version/13.0.5 Safari/605.1.15 | NULL | 2020-03-23 12:20:34 | 2020-03-23 12:20:34 |
| 2 | App\\User | 1 | created | App\\Item | 3 | \[\] | {"name":"\\u307b\\u3052","stock":"10","id":3} | http://localhost:8000/items | 127.0.0.1 | Mozilla/5.0 \(Macintosh; Intel Mac OS X 10\_15\_3\) AppleWebKit/605.1.15 \(KHTML, like Gecko\) Version/13.0.5 Safari/605.1.15 | NULL | 2020-03-23 12:21:39 | 2020-03-23 12:21:39 |
| 3 | App\\User | 1 | deleted | App\\Item | 3 | {"id":"3","name":"\\u307b\\u3052","stock":"10"} | \[\] | http://localhost:8000/items/3 | 127.0.0.1 | Mozilla/5.0 \(Macintosh; Intel Mac OS X 10\_15\_3\) AppleWebKit/605.1.15 \(KHTML, like Gecko\) Version/13.0.5 Safari/605.1.15 | NULL | 2020-03-23 12:51:57 | 2020-03-23 12:51:57 |

監視対象のデータは以下のような内容が蓄積されます。

| フィールド名 | 蓄積内容 |
|---|---|
| id | 監視データのID |
| user_type | ユーザモデルのクラス名 |
| user_id | ユーザID |
| event | 実行したイベント |
| auditable_type | 更新したデータモデルのクラス名 |
| auditable_id | 更新したデータのID |
| old_values | 更新前のデータ |
| new_values | 更新後のデータ |
| url | 実行されたURL |
| ip_address | IPアドレス |
| user_agent | ユーザエージェント |
| tags | タグ |
| created_at| 作成日時（= データが更新された日時） |
| updated_at | 更新日時 |


あまりないと思いますが、ログインせずにデータを更新した場合は、`user_type`, `user_id`がNULLになります。

### 監視データの取得
監視データは監視対象のモデルに関連しています。

```php
<?php

$item = Item::find(1);

// 該当データの全ての監視データ
$audits = $item->audits;

// 該当データの最初の監視データ
$first = $item->audits()->first();

// 該当データの直近の監視データ
$latest = $item->audits()->latest()->first();

// 該当データの監視データのID指定
$audit = $item->audits()->find(1);
```

監視データもEloquentモデルなので、直接取得できます。

```php
<?php

use OwenIt\Auditing\Models\Audit;

// 全件取得
$audits = Audit::all();

// ID指定
$audit = Audit::find(1);
```

## カスタマイズ
### コンソールコマンドでの実行も監視したい
デフォルトでは、コンソールコマンドやジョブの更新は監視対象外となっています。監視したい場合は、`config/audit.php`で設定を変更します。

```diff:config/audit.php
-    'console' => false,
+    'console' => true,
```

### 監視するフィールドを絞り込みたい
デフォルトでは、登録・更新日時以外を対象とします。監視するフィールドを絞り込みたい場合は監視対象モデルに設定を追記します。

```diff:app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditInclude = [
+        'name',
+    ];
```

逆に除外したい場合は、`$auditExclude`に指定することで、除外できます。

### 蓄積するデータを変えたい
蓄積するデータをカスタマイズしたい場合は、独自のResolverを作成します。Laravel Auditingでは以下のResolver用インターフェイスが用意されています。

| インターフェイス | 概要 |
|---|---|
| OwenIt\Auditing\Contracts\IpAddressResolver | IPアドレス用I/F |
| OwenIt\Auditing\Contracts\UrlResolver | URL用I/F |
| OwenIt\Auditing\Contracts\UserAgentResolver | ユーザエージェント用I/F |
| OwenIt\Auditing\Contracts\UserResolver | ユーザモデル用I/F |

これらを実装することで、蓄積するデータを変えることができます。

```php:app/resolvers/IpAddressResolver.php

<?php
namespace App\Resolvers;

use Illuminate\Support\Facades\Request;

class IpAddressResolver implements \OwenIt\Auditing\Contracts\IpAddressResolver
{
    /**
     * {@inheritdoc}
     */
    public static function resolve(): string
    {
        return Request::header('HTTP_X_FORWARDED_FOR', '0.0.0.0');
    }
}
```

独自Resolverは`config/audit.php`で設定することで反映されます。

```diff:config/audit.php
return [
    'resolver' = [
        'user'       => OwenIt\Auditing\Resolvers\UserResolver::class,
-        'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class,
+        'ip_address' => App\Resolvers\IpAddressResolver::class,
        'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class,
        'url'        => OwenIt\Auditing\Resolvers\UrlResolver::class,
    ],
];
```

### マスクしたい
監視データは変更前・後の値をそのまま保存しますが、場合によってはマスクして保存したい時もあります。その場合は、監視対象のモデルに設定することで、マスクできます。

```diff:app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;
+use OwenIt\Auditing\Redactors\LeftRedactor;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $attributeModifiers = [
+        'title' => LeftRedactor::class,
+    ];
```

`LeftRedactor`は左から90%マスクし残り10%はそのまま、`RightRedactor`は右から90%マスクします。マスク部分は`#####`で保存されます。

### 特定のイベントのみ監視したい
デフォルトは追加・更新・削除を監視しますが、設定によりイベントを絞り込むことができます。例えば削除のみ監視したい場合は以下のように設定します。

#### 全体
```diff:config/audit.php
    'events' => [
-        'created',
-        'updated',
        'deleted',
-        'restored',
    ],
```

#### 特定の監視対象のみ
```diff:app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditEvents = [
+        'deleted',
+    ];
```

### 監視ログの上限を設ける
デフォルトでは監視ログは延々と蓄積され続けます。上限を設けたい場合は以下のように設定するこで、上限を超えた古いデータが削除されていきます。

#### 全体
```diff:config/audit.php
-    'threshold' => 0, // 0だと上限なし
+    'threshold' => 10,
```

#### 特定の監視対象のみ
```diff:app/Item.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;

class Item extends Model implements Auditable
{
    use \OwenIt\Auditing\Auditable;


+    protected $auditThreshold = 10;
```