Appearance
Admin Audit Logging
Flow ID: AD-32 Module(s): audit, auth, job Complexity: Low Last Updated: 2026-04-27
Business Context
Every admin panel request and every authenticated REST write is automatically logged to the user_audit_logs table. This provides a full activity trail of which user accessed what URLs, with what HTTP method, from which IP address, and (for POST requests / JSON bodies) what parameters were submitted. The audit log is accessible to Advisable and Admin roles in the admin menu under "Administrator Activities". A nightly cleanup job purges entries older than 30 days.
Entry Points
| Type | Path / Trigger | Controller | Method |
|---|---|---|---|
| Admin | Audit log listing | ecommercen/audit/controllers/Adv_audit.php | index() |
| Admin | User detail logs (AJAX) | same | ajaxGetAuditLogs($username) |
| Auto | Every admin request | ecommercen/core/Adv_admin_controller.php | storeUserAuditLogs() |
| Auto | Every authenticated REST write (POST/PUT/DELETE/PATCH) | src/Rest/Middleware/AuditMiddleware.php | handle() |
| Job | Nightly cleanup | ecommercen/job/libraries/AdvClearUsersAudit.php | executeCommand() |
How Logging Works
Audit logging is triggered automatically from two paths: the legacy admin controller constructor and the REST middleware pipeline.
Legacy admin (every request)
Adv_admin_controller::__construct()calls$this->storeUserAuditLogs()(line 71)storeUserAuditLogs()loadsaudit/audit_modeland callsstoreUserLog()AdvAuditModel::storeUserLog()checksignoreUrl(), then inserts a row composed of:getGeneralInfo()-- timestamp (created_at)getUserData()--user_id,username, role names (comma-separated),provider(DATABASE or CONFIG),ip_addressgetPageInfo()-- HTTPmethod(GET/POST/PUT/DELETE/PATCH), fullurlwith query stringgetStates()-- POSTparamsas JSON (if any)
REST writes (authenticated POST/PUT/DELETE/PATCH only)
The REST pipeline runs AuditMiddleware last (after auth + relation filtering). It logs only authenticated writes — read operations and unauthenticated requests are skipped.
RouterDispatcher::dispatch()pipesAuditMiddlewareafterRelationFilterMiddlewareAuditMiddleware::handle()short-circuits when!$context->isAuthenticatedor the HTTP method is not in[POST, PUT, DELETE, PATCH]- User data is extracted from the JWT (
RequestContext->tokenData):- Numeric
userId→ stored inuser_idcolumn (DB-backed users) - String
userId→ stored inusernamecolumn (config_auth users) - Role IDs → resolved to comma-separated role names via
auth/roles_model provider→REST_BACKEND(admin JWT) orREST_CUSTOMER(customer JWT)
- Numeric
AdvAuditModel::storeRestLog($userData)inserts a row using the samegetPageInfo()(URL + method) plus a JSON-awaregetRestStates()that captures both$_POSTand JSON request bodies via$ci->input->inputStream()- Logging failures are swallowed and routed to
log_message('error', ...)— audit issues never break a production write
Ignored URLs
Certain high-frequency polling endpoints are excluded from logging to avoid table bloat:
| URL | Reason |
|---|---|
auth/getUserTasks | Task badge polling |
reporting/dashboardLive | Live dashboard refresh |
adminrun/pendingOrdersForAsap | ASAP order polling |
audit/index | Viewing the audit page itself |
Defined in AdvAuditModel::IGNORED_URLS.
Database Schema
Table: user_audit_logs
| Column | Type | Description |
|---|---|---|
id | INT UNSIGNED, PK, AUTO_INCREMENT | Row identifier |
user_id | INT UNSIGNED, nullable | Admin user ID (null for SSO users) |
username | VARCHAR(255) | Admin username |
roles | VARCHAR(255) | Comma-separated role names at time of request |
provider | VARCHAR(255) | Auth provider: DATABASE, CONFIG, REST_BACKEND, or REST_CUSTOMER |
method | ENUM('GET','POST','PUT','DELETE','PATCH') | HTTP method |
url | TEXT | Full URL including query string |
ip_address | VARCHAR(45) | Client IP (supports IPv6) |
params | JSON, nullable | POST body parameters as JSON |
created_at | DATETIME | Timestamp of the request |
Indexes: user_method (user_id, method), created_at, user_method_date (user_id, method, created_at).
Migration: database/migrations/20250509080101_audit_log.php
Admin UI
Route: /audit (mapped in application/config/routes.php)
Menu entry: Listed under the SETTINGS group with label "Administrator Activities", visible to Advisable and Admin roles.
List view (application/views/admin/audit/list.php):
- Paginated table showing username, roles, method, URL, IP, and date
- Filter form: text search (username or IP) and multi-select HTTP method filter (GET, POST, PUT, DELETE)
- Each username is clickable, opening a modal with the user's full activity log
User detail modal:
- Loads via AJAX:
GET /audit/ajaxGetAuditLogs/{username} - Shows method, IP, date, and a toggleable textarea for POST params
- Second row per entry shows the full URL
- CSV export button downloads
{username}_audit_logs.csvwith columns: username, user_id, method, url, ip_address, created_at
Role-Based Access
The audit controller restricts role visibility: non-Advisable users cannot see the Advisable (ID 1) and Developer (ID 2) roles in the role dropdown filter. This prevents lower-privileged admins from filtering for superuser activity patterns.
Cleanup Job
Class: AdvClearUsersAudit (implements JobCommand)
Schedule: Daily at 00:30 (30 0 * * *), configured in application/config/jobs.php
Behavior: Calls AdvAuditModel::cleanUpAudit(30) which deletes all rows where created_at is older than 30 days.
Application override: application/modules/job/libraries/ClearUsersAudit.php extends AdvClearUsersAudit (empty override, allows client customization of retention period).
Key Files
| File | Purpose |
|---|---|
ecommercen/audit/controllers/Adv_audit.php | Admin list + AJAX detail endpoint |
ecommercen/audit/models/AdvAuditModel.php | Log storage, ignored URLs, cleanup, storeRestLog() for REST writes |
ecommercen/core/Adv_admin_controller.php | Auto-logging in constructor (line 71, 346-350) |
src/Rest/Middleware/AuditMiddleware.php | REST audit middleware — logs authenticated writes |
application/controllers/RouterDispatcher.php | Wires AuditMiddleware into the REST pipeline |
ecommercen/auth/models/Adv_users_model.php | Query methods: getAdminList(), getAuditLogsByUsername() |
ecommercen/job/libraries/AdvClearUsersAudit.php | Cleanup job (30-day retention) |
application/modules/audit/controllers/Audit.php | Application-level override (empty) |
application/modules/audit/models/Audit_model.php | Application-level override (empty) |
application/modules/job/libraries/ClearUsersAudit.php | Application-level override (empty) |
application/views/admin/audit/list.php | List view with modal |
application/views/admin/footer_js.php | AJAX log loading + CSV export JS (lines 3851-3932) |
database/migrations/20250509080101_audit_log.php | Table creation migration |
Data Flow Diagram
Legacy admin
Admin Request
|
v
Adv_admin_controller::__construct()
|
+-- storeUserAuditLogs()
| |
| v
| AdvAuditModel::storeUserLog()
| |
| +-- ignoreUrl()? --> skip
| |
| +-- Collect: timestamp, user data, page info, POST params
| |
| v
| INSERT into user_audit_logs
|
v
Normal controller execution continuesREST writes
REST Request
|
v
RouterDispatcher::dispatch()
|
+-- MiddlewarePipeline
| Auth -> Authz -> ResourceContext -> RelationFilter -> AuditMiddleware
|
+-- AuditMiddleware::handle()
| |
| +-- !isAuthenticated? --> skip
| +-- method is GET/HEAD? --> skip
| |
| +-- buildUserData() from JWT
| | - user_id (numeric) or username (config_auth)
| | - roles -> CSV of names via auth/roles_model
| | - provider = REST_BACKEND | REST_CUSTOMER
| |
| v
| AdvAuditModel::storeRestLog($userData)
| |
| +-- ignoreUrl()? --> skip
| +-- INSERT row (general info + provided user data + page info + JSON-aware params)
|
v
Controller method runsBusiness Rules
- Logging is automatic and cannot be disabled per-user or per-controller
- SSO users have
user_id = nullbutusernameis populated from the session - POST parameters are captured as raw JSON with
JSON_UNESCAPED_UNICODE - REST writes capture both
$_POST(multipart uploads) and JSON request bodies (read via$ci->input->inputStream()); REST reads (GET/HEAD) and unauthenticated requests are NOT logged - The
providerfield reflects auth type at time of action:DATABASE(legacy admin DB user),CONFIG(legacy admin config_auth user),REST_BACKEND(admin via JWT), orREST_CUSTOMER(customer via JWT) - 30-day retention is hardcoded in the cleanup job; client repos can override by extending
ClearUsersAudit - The
AdvAuditModeluses theGenericWriteModeltrait for basic CRUD operations (add(),deleteWhere())
Related Flows
- AD-01 Admin Authentication & SSO -- auth providers that feed audit user data
- SY-01 Cron Framework --
ClearUsersAuditcleanup job runs on the cron schedule