XHealtXperience maintains a per-tenant audit log — referred to internally as the bitácora — that records every significant action performed within a clinic. Each tenant database hosts its own dedicatedDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Arthurr23/XHealtXperience/llms.txt
Use this file to discover all available pages before exploring further.
audit_logs table, so one clinic’s activity is completely isolated from another’s. The log is append-only (no update or delete operations) and captures the actor, the target, the action type, a human-readable description, supporting metadata, and the originating IP address and user agent.
The audit log serves two audiences: Administrador Clinica staff who need operational visibility, and Auditor role users who provide independent oversight of security-relevant events.
Database Schema
Theaudit_logs table is created by a tenant migration (2025_07_01_000001_create_audit_logs_table.php) that runs once per new clinic:
| Column | Type | Description |
|---|---|---|
id | bigint | Auto-incrementing primary key |
user_id | bigint|null | ID of the actor (best-effort; actor may be from the central DB) |
user_name | string|null | Name snapshot of the actor at the time of the event |
user_role | string|null | Role snapshot of the actor at the time of the event |
target_user_id | bigint|null | ID of the user the action was performed on, if applicable |
target_user_name | string|null | Name snapshot of the target user |
action | string(100) | Machine-readable action slug, indexed. E.g. auth.login_failed |
description | string | Human-readable description of what happened |
metadata | json|null | Optional structured data providing extra context |
ip_address | string(45)|null | IPv4 or IPv6 address of the request |
user_agent | string|null | Browser/client user agent string (truncated to 255 chars) |
created_at | timestamp | When the event was recorded (auto-set to current time) |
user_id is stored as a plain unsignedBigInteger without a foreign key constraint. This is intentional: the actor may be the Super Admin, whose user record lives in the central database and does not exist in the tenant’s users table. The text snapshot fields user_name and user_role are the authoritative source of truth for display purposes.The AuditLogService
All audit entries are created through a single central method:AuditLogService::log(). Controllers and other services should always use this method rather than creating AuditLog model instances directly.
Method Signature
Parameters
A dot-namespaced slug identifying the type of event. Use a consistent naming convention such as
{resource}.{verb}. Examples from the codebase: user.created, auth.account_unlocked, two_factor.enabled, two_factor.disabled.A human-readable sentence describing exactly what happened. Should be understandable without additional context. Example:
"María López desbloqueó la cuenta de Juan Pérez".Optional associative array of structured data providing additional context. Cast to JSON in the database. Use for machine-readable details that complement the description, such as changed field values or IDs.
The user the action was performed on, if applicable. Must have
id and name properties. Stored as target_user_id and target_user_name.The user who performed the action. Defaults to
auth()->user() when omitted. Override this parameter when the acting user is not the currently authenticated user (e.g., system-initiated events).The role name of the actor, if already resolved. Providing this avoids an extra
getRoleNames() query. Useful when the actor comes from the central database (e.g., during a Super Admin action that somehow reaches a tenant context).Usage Examples
Logging a User Creation
Logging an Account Unlock
Logging a 2FA State Change
Logging with Metadata
Super Admin Actions Are Not Logged
AuditLogService::log() begins with a tenancy guard:
When the Super Admin performs actions from the central panel (
/panel-global/...), tenancy is not initialized — there is no tenant context and no audit_logs table to write to. These calls return silently without throwing an exception. This is by design: the per-clinic audit log is a tenant-level concept. If your application later requires central-level auditing of Super Admin actions, a separate central audit_logs table and logging path would need to be introduced.Viewing the Audit Log
Administrador Clinica and Auditor — Full Log
The full paginated audit log is accessible at:Administrador Clinica or Auditor role via the middleware:
| Parameter | Type | Description |
|---|---|---|
accion | string | Filter by exact action slug (e.g. two_factor.disabled) |
usuario | string | Case-insensitive partial match on user_name |
desde | date | Show only entries on or after this date (Y-m-d) |
hasta | date | Show only entries on or before this date (Y-m-d) |
Auditor — Security Dashboard
Users with theAuditor role also have access to a dedicated security dashboard that aggregates metrics and highlights suspicious activity:
Auditor role:
Eventos hoy
Total number of audit events recorded since midnight today.
Logins fallidos (7 días)
Count of
auth.login_failed events in the past 7 days — a leading indicator of brute-force attempts.Cuentas bloqueadas
Current number of user accounts with
is_locked = true.2FA desactivados (30 días)
Count of
two_factor.disabled events in the past 30 days — useful for detecting social-engineering patterns.Common Action Slugs
The following action slugs are used across the application. Use these exact values when filtering or building tooling against the audit log:| Slug | Triggered by |
|---|---|
user.created | UserController::store() — new user account created |
auth.account_unlocked | UserController::desbloquear() — admin unlocked a locked account |
two_factor.enabled | TwoFactorController::confirm() — user activated 2FA |
two_factor.disabled | TwoFactorController::disable() — user deactivated 2FA |
auth.login_failed | Failed login attempt (tracked for lockout and auditor dashboard) |