This content originally appeared on DEV Community and was authored by Hoang Manh Cam
Activity logging is essential for tracking user actions in web applications. This article will guide you through implementing a comprehensive activity logging system in Laravel with multilingual support, from database setup to displaying logs.
Step 1: Create the Database Structure
First, create a migration for the activity logs table:
php artisan make:migration create_activity_logs_table
In the migration file, define the structure:
public function up(): void
{
Schema::create('activity_logs', function (Blueprint $table) {
$table->bigInteger('id', true)->unsigned()->comment('ID');
$table->string('record_table')->comment('Target Record Model');
$table->bigInteger('record_id')->unsigned()->comment('Target Record ID');
$table->string('action')->comment('Method');
$table->string('ip_address')->comment('IP Address');
$table->string('user_agent')->comment('User Agent');
$table->string('user_table')->comment('User Model');
$table->bigInteger('user_id')->unsigned()->nullable()->comment('User ID');
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
$table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP'));
$table->timestamp('deleted_at')->nullable();
// Add indexes for better performance
$table->index(['user_table', 'user_id']);
$table->index(['record_table', 'record_id']);
$table->index('action');
});
}
Run the migration:
php artisan migrate
Step 2: Create the ActivityLog Model
Create a model that handles activity logs:
php artisan make:model ActivityLog
Implement the model with dynamic description generation:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ActivityLog extends Model
{
use SoftDeletes;
protected $fillable = [
'record_table',
'record_id',
'action',
'ip_address',
'user_agent',
'user_table',
'user_id',
];
protected $appends = ['description'];
public function getDescriptionAttribute(): string
{
// The user who performed the action
$user = $this->getRecord($this->user_table, $this->user_id);
// The record on which the action was performed
$record = $this->getRecord($this->record_table, $this->record_id);
if (!$user || !$record) {
return '';
}
$action = $this->action;
$user_table = __("label.tables.$this->user_table");
$record_table = __("label.tables.$this->record_table");
// Format the description based on the action type
return match ($action) {
default => __(
"activity_log.descriptions.$action",
[
'user_table' => $user_table,
'user_name' => $user->activity_log_name,
'record_table' => $record_table,
'record_name' => $record->activity_log_name,
'record_id' => $this->record_id,
]
),
'export' => __(
"activity_log.descriptions.export",
['user_table' => $user_table, 'user_name' => $user->activity_log_name, 'record_table' => $record_table]
),
'import' => __(
"activity_log.descriptions.import",
['user_table' => $user_table, 'user_name' => $user->activity_log_name, 'record_table' => $record_table]
),
// Other custom actions
};
}
protected function getRecord($table, $id)
{
// Get model name from config
$model = config("const.models.$table");
if (class_exists($model)) {
$query = $model::query();
// Handle deleted records gracefully
return $query->find($id) ?? new class {
public string $activity_log_name;
public function __construct()
{
$this->activity_log_name = __('label.labels.deleted');
}
};
}
return null;
}
}
Step 3: Create the ActivityLogService
Create a service to handle activity log creation:
<?php
namespace App\Services;
use App\Helpers\UserHelper;
use App\Models\ActivityLog;
class ActivityLogService
{
public function save(string $recordTable, string $recordId, string $action): ActivityLog
{
$activityLog = new ActivityLog();
$activityLog->record_table = $recordTable;
$activityLog->record_id = $recordId;
$activityLog->action = $action;
$activityLog->ip_address = request()->ip() ?? 'undefined';
$activityLog->user_agent = request()->userAgent() ?? 'undefined';
$activityLog->user_table = UserHelper::getAuthTable(auth()->user());
$activityLog->user_id = auth()->id() ?? null;
$activityLog->save();
return $activityLog;
}
}
Step 4: Create a Helper for Authentication Types
Create a helper to determine the authenticated user type:
<?php
namespace App\Helpers;
class UserHelper
{
public static function getAuthTable($user = null): string
{
if (!$user) {
return '';
}
return match (get_class($user)) {
'App\Models\Admin' => 'admins',
'App\Models\User' => 'users',
'App\Models\Manager' => 'managers',
default => '',
};
}
}
Step 5: Update Your Models
Add a method to each model that should be tracked in activity logs. For example User model:
public function getActivityLogNameAttribute(): string
{
if ($this->trashed()) {
return __('deleted');
}
$name = $this->full_name;
if (Route::is('admin.*')) {
return "<a href='" . route('admin.users.edit', $this->id) . "'>" . $name . "</a>";
}
return $name;
}
Step 6: Configure Model Mappings
In config/const.php, add model and icon mappings:
'models' => [
'admins' => 'App\Models\Admin',
'users' => 'App\Models\User',
'settings' => 'App\Models\Setting',
'activity_logs' => 'App\Models\ActivityLog',
'managers' => 'App\Models\Manager',
],
'icons' => [
'admins' => 'ph-user-gear',
'users' => 'ph-users',
'settings' => 'ph-gear',
'languages' => 'ph-translate',
'activity_logs' => 'ph-scroll',
'notifications' => 'ph-bell',
'managers' => 'ph-person-simple',
],
Step 7: Create Translation Files
Create language files for activity logs:
For English (lang/en/activity_log.php
):
<?php
return [
'actions' => [
'store' => 'Create',
'update' => 'Update',
'destroy' => 'Delete',
'import' => 'Import',
'export' => 'Export',
'update_password' => 'Update Password',
// other actions
],
'descriptions' => [
'store' => ':user_table :user_name created :record_table :record_name.',
'update' => ':user_table :user_name updated :record_table :record_name.',
'destroy' => ':user_table :user_name deleted :record_table ID::record_id.',
'import' => ':user_table :user_name imported :record_table.',
'export' => ':user_table :user_name exported :record_table data.',
// other descriptions
]
];
Please update similarly for other languages.
Step 8: Create the Controller
Create a controller to display activity logs:
php artisan make:controller Admin/ActivityLogController
Implement the controller:
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Services\DataTableService;
use App\Services\ActivityLogService;
use Illuminate\Http\Request;
class ActivityLogController extends Controller
{
protected DataTableService $dataTableService;
protected ActivityLogService $activityLogService;
public function __construct(DataTableService $dataTableService, ActivityLogService $activityLogService)
{
$this->dataTableService = $dataTableService;
$this->activityLogService = $activityLogService;
}
public function index(Request $request)
{
if ($request->ajax()) {
$columns = [
'id', 'record_table', 'record_id', 'action', 'ip_address',
'user_agent', 'user_table', 'user_id', 'created_at',
];
$data = ActivityLog::select($columns);
$response = $this->dataTableService->processDataTable($data, $request, $columns);
return response()->json($response);
}
return view('admin.activity_logs.index');
}
}
Step 9: Create the View
Create a view to display activity logs using DataTables:
@extends('admin.layouts.master')
@section('title')
{{ __('label.tables.activity_logs') }}
@endsection
@section('center-scripts')
<script src="{{URL::asset('assets/admin/js/vendor/tables/datatables/datatables.min.js')}}"></script>
@endsection
@section('content')
<div class="content">
<!-- Single row selection -->
<div class="card">
<table class="table datatable-selection-single" id="activity-log-table">
<thead>
<tr>
<th width="5%">{{ __('label.columns.common.id') }}</th>
<th width="15%">{{ __('label.columns.activity_logs.record') }}</th>
<th width="15%">{{ __('label.columns.activity_logs.action') }}</th>
<th width="25%">{{ __('label.columns.activity_logs.description') }}</th>
<th width="15%">{{ __('label.columns.activity_logs.ip_address') }}</th>
<th width="25%">{{ __('label.columns.activity_logs.user_agent') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
@endsection
@section('scripts')
<script>
let dataTableLanguage = @json(trans('datatable'));
let icons = @json(config('const.icons'));
let models = @json(trans('label.tables'));
let action = @json(trans('activity_log.actions'));
$(document).ready(function() {
const config = {
tableId: 'activity-log-table',
ajaxUrl: "{{ route('admin.activity_logs.index') }}",
columns: [
{'data': 'id'},
{
'data': 'record_table',
'render': function(data, type, row) {
return icons.hasOwnProperty(data) ?
`<i class="${icons[data]}"></i> ${models[data]}` : data;
},
},
{
'data': 'action',
'render': function(data, type, row) {
return action.hasOwnProperty(data) ? action[data] : data;
},
},
{'data': 'description'},
{'data': 'ip_address'},
{'data': 'user_agent'},
],
language: dataTableLanguage,
};
initializeDataTable(config);
});
</script>
@endsection
For more details on implementing DataTables in Laravel, you can refer to our previous article: Implementing Dynamic DataTables in Laravel with Server-Side Processing.
Step 10: Register Routes
Add routes for activity logs:
Route::group(['middleware' => 'can:admin:activity_logs'], function () {
Route::get('/activity_logs', [ActivityLogController::class, 'index'])
->name('activity_logs.index');
});
Step 11: Using the Activity Log Service
Here's how to use the service in controllers like UserController:
For creating records:
public function store(Request $request)
{
// Create a new user
$user = User::create($request->validated());
// Log the activity
$this->activityLogService->save(
$user->getTable(), // Table name
(string)$user->id, // Record ID
__FUNCTION__ // Action (store)
);
return redirect()->route('admin.users.index')
->with('success', 'User created successfully.');
}
For updates:
public function update(User $user, UserRequest $request)
{
$user->fill($request->all());
$user->save();
$this->activityLogService->save(
$user->getTable(),
(string)$user->id,
__FUNCTION__
);
return redirect()->back()->with('success', 'User updated successfully.');
}
For custom actions like password updates:
public function updatePassword(User $user, UserPasswordRequest $request)
{
$user->fill($request->all());
$user->save();
$this->activityLogService->save(
$user->getTable(),
(string)$user->id,
"update_password" // Custom action name
);
return redirect()->back()->with('success', 'Password updated successfully.');
}
How It Works
Saving Activity Logs:
- Each controller action calls the ActivityLogService
- The service records details like table name, record ID, action type
- Context info (IP, user agent) is automatically captured
Dynamic Description Generation:
- The
getDescriptionAttribute()
method formats descriptions based on action type - It uses translation strings with placeholders (
:user_name, :record_name
) - Models provide their own
activity_log_name
methods for custom formatting
Multilingual Support:
- Descriptions and action names come from language files
- The system automatically uses the current locale
- Format strings differ between languages for natural phrasing
DataTable Integration:
- Server-side processing handles large log volumes efficiently
- Client-side rendering applies formatting to icons and action names
- All strings come from translation files for consistency
Benefits of This Approach
- Complete Audit Trail: Track who did what, when, and from where
- Multilingual Support: Automatically adapts to the user's language
- Contextual Formatting: Links to related records when appropriate
- Graceful Handling: Works even when related records are deleted
- Clean Architecture: Separation of concerns between models, service, and controller
This implementation provides a robust, multilingual activity logging system that can easily scale with your application's needs while maintaining excellent performance.
This content originally appeared on DEV Community and was authored by Hoang Manh Cam

Hoang Manh Cam | Sciencx (2025-08-28T01:28:40+00:00) Building a Multilingual Activity Logging System in Laravel. Retrieved from https://www.scien.cx/2025/08/28/building-a-multilingual-activity-logging-system-in-laravel/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.