Logging Changes in Laravel Eloquent

So you need to log changes to some / all database records, who made them, when and what was added / changed deleted? An easy an effective way to do this with Laravel Eloquent models is via a custom Observable trait. On any model that you wish to track changes for, you then just add the trait to the model (and an optional static function to set the message format):

use App\Traits\Observable;

Let’s start with the migration we need for a table in the database to record all these changes:

public function up() {
  Schema::create('logs', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id')->nullable();
    $table->string('model',100);
    $table->string('action',7);
    $table->text('message');
    $table->json('models');
    $table->timestamps();

    $table->foreign('user_id')->references('id')->on('users');
  });
}

The Observable trait looks like:

namespace App\Traits;

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

use App\Models\Log;

trait Observable
{
  // bootObservable() will be called on model instantiation automatically
  public static function bootObservable() {
    static::saved(function (Model $model) {
      // create or update?
      if( $model->wasRecentlyCreated ) {
        static::logChange( $model, 'CREATED' );
      } else {
        if( !$model->getChanges() ) {
          return;
        }
        static::logChange( $model, 'UPDATED' );
      }
    });
      
    static::deleted(function (Model $model) {
      static::logChange( $model, 'DELETED' );
    });
  }
  
  public static function logChange( Model $model, string $action ) {
    Log::create([
      'user_id' => Auth::check() ? Auth::user()->id : null,
      'model'   => static::class,
      'action'  => $action,
      'message' => static::logSubject($model),
      'models'  => [
        'new'     => $action !== 'DELETED' ? $model->getAttributes() : null,
        'old'     => $action !== 'CREATED' ? $model->getOriginal()   : null,
        'changed' => $action === 'UPDATED' ? $model->getChanges()    : null,
      ]
    ]);
  }

  /**
   * String to describe the model being updated / deleted / created
   * Override this in the model class
   * @return string
   */
  public static function logSubject(Model $model): string {
    return static::logImplodeAssoc($model->attributesToArray());
  }

  public static function logImplodeAssoc(array $attrs): string {
    $l = '';
    foreach( $attrs as $k => $v ) {
      $l .= "{ $k => $v } ";
    }
    return $l;
  }
}

So, again, just use this trait in any model and you have full logging of changes to the database.

You’ll find complete files for the above and an example usage with the User class on this GitHub Gist.