Audit Customization Tutorial
This tutorial shows how to customize audit logging in SecureHealth, including custom events, data filtering, and compliance reporting.
Prerequisitesβ
- SecureHealth installation
- Admin access to the system
- Understanding of audit logging requirements
Step 1: Define Custom Audit Eventsβ
Create custom audit event types:
<?php
namespace App\Security;
class AuditEventTypes
{
const CUSTOM_EVENTS = [
'PATIENT_TRANSFER' => 'Patient transferred between departments',
'MEDICATION_CHANGE' => 'Medication dosage or type changed',
'EMERGENCY_ACCESS' => 'Emergency access to patient data',
'DATA_EXPORT' => 'Patient data exported',
'CONSENT_UPDATE' => 'Patient consent updated'
];
public static function getAllEvents(): array
{
return array_merge(self::STANDARD_EVENTS, self::CUSTOM_EVENTS);
}
}
Step 2: Create Custom Audit Loggerβ
Extend the base audit logger:
<?php
namespace App\Service;
use App\Entity\AuditLog;
use App\Entity\User;
use App\Security\AuditEventTypes;
class CustomAuditLogger extends AuditLogger
{
public function logPatientTransfer(User $user, Patient $fromPatient, Patient $toPatient, string $reason): void
{
$details = [
'fromPatientId' => $fromPatient->getPatientId(),
'toPatientId' => $toPatient->getPatientId(),
'reason' => $reason,
'transferType' => 'DEPARTMENT_TRANSFER'
];
$this->logCustomEvent($user, 'PATIENT_TRANSFER', $fromPatient, $details);
}
public function logMedicationChange(User $user, Patient $patient, array $oldMedication, array $newMedication): void
{
$details = [
'oldMedication' => $oldMedication,
'newMedication' => $newMedication,
'changeType' => 'MEDICATION_UPDATE',
'prescribedBy' => $user->getEmail()
];
$this->logCustomEvent($user, 'MEDICATION_CHANGE', $patient, $details);
}
public function logEmergencyAccess(User $user, Patient $patient, string $emergencyReason): void
{
$details = [
'emergencyReason' => $emergencyReason,
'accessType' => 'EMERGENCY_OVERRIDE',
'justification' => 'Emergency medical situation'
];
$this->logCustomEvent($user, 'EMERGENCY_ACCESS', $patient, $details);
}
private function logCustomEvent(User $user, string $eventType, $resource, array $details): void
{
$auditLog = new AuditLog();
$auditLog->setUser($user);
$auditLog->setAction($eventType);
$auditLog->setTimestamp(new DateTime());
$auditLog->setResource($resource);
$auditLog->setDetails($details);
$auditLog->setHipaaCompliant(true);
$auditLog->setAuditRequired(true);
$auditLog->setRetentionPeriod('7_years');
$this->auditRepository->save($auditLog);
}
}
Step 3: Create Audit Event Listenersβ
Add custom event listeners:
<?php
namespace App\EventListener;
use App\Service\CustomAuditLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomAuditEventListener implements EventSubscriberInterface
{
private CustomAuditLogger $auditLogger;
public function __construct(CustomAuditLogger $auditLogger)
{
$this->auditLogger = $auditLogger;
}
public static function getSubscribedEvents(): array
{
return [
'patient.transfer' => 'onPatientTransfer',
'medication.change' => 'onMedicationChange',
'emergency.access' => 'onEmergencyAccess',
'data.export' => 'onDataExport'
];
}
public function onPatientTransfer(PatientTransferEvent $event): void
{
$this->auditLogger->logPatientTransfer(
$event->getUser(),
$event->getFromPatient(),
$event->getToPatient(),
$event->getReason()
);
}
public function onMedicationChange(MedicationChangeEvent $event): void
{
$this->auditLogger->logMedicationChange(
$event->getUser(),
$event->getPatient(),
$event->getOldMedication(),
$event->getNewMedication()
);
}
public function onEmergencyAccess(EmergencyAccessEvent $event): void
{
$this->auditLogger->logEmergencyAccess(
$event->getUser(),
$event->getPatient(),
$event->getReason()
);
}
public function onDataExport(DataExportEvent $event): void
{
$this->auditLogger->logDataExport(
$event->getUser(),
$event->getPatient(),
$event->getExportType(),
$event->getDataFields()
);
}
}
Step 4: Create Custom Eventsβ
Define custom event classes:
<?php
namespace App\Event;
use App\Entity\Patient;
use App\Entity\User;
use Symfony\Contracts\EventDispatcher\Event;
class PatientTransferEvent extends Event
{
private User $user;
private Patient $fromPatient;
private Patient $toPatient;
private string $reason;
public function __construct(User $user, Patient $fromPatient, Patient $toPatient, string $reason)
{
$this->user = $user;
$this->fromPatient = $fromPatient;
$this->toPatient = $toPatient;
$this->reason = $reason;
}
public function getUser(): User
{
return $this->user;
}
public function getFromPatient(): Patient
{
return $this->fromPatient;
}
public function getToPatient(): Patient
{
return $this->toPatient;
}
public function getReason(): string
{
return $this->reason;
}
}
Step 5: Create Custom Reportsβ
Add custom compliance reports:
<?php
namespace App\Service;
class CustomComplianceReporter
{
public function generatePatientTransferReport(DateTime $startDate, DateTime $endDate): array
{
$auditLogs = $this->auditRepository->findByActionAndDateRange('PATIENT_TRANSFER', $startDate, $endDate);
$report = [
'period' => [
'start' => $startDate,
'end' => $endDate
],
'summary' => [
'totalTransfers' => count($auditLogs),
'uniquePatients' => $this->countUniquePatients($auditLogs),
'transferReasons' => $this->categorizeTransferReasons($auditLogs)
],
'transfers' => $auditLogs
];
return $report;
}
public function generateMedicationChangeReport(DateTime $startDate, DateTime $endDate): array
{
$auditLogs = $this->auditRepository->findByActionAndDateRange('MEDICATION_CHANGE', $startDate, $endDate);
$report = [
'period' => [
'start' => $startDate,
'end' => $endDate
],
'summary' => [
'totalChanges' => count($auditLogs),
'uniquePatients' => $this->countUniquePatients($auditLogs),
'changeTypes' => $this->categorizeChangeTypes($auditLogs)
],
'changes' => $auditLogs
];
return $report;
}
private function countUniquePatients(array $auditLogs): int
{
$patientIds = array_unique(array_map(function($log) {
return $log->getResourceId();
}, $auditLogs));
return count($patientIds);
}
private function categorizeTransferReasons(array $auditLogs): array
{
$reasons = [];
foreach ($auditLogs as $log) {
$reason = $log->getDetails()['reason'] ?? 'Unknown';
$reasons[$reason] = ($reasons[$reason] ?? 0) + 1;
}
return $reasons;
}
}
Step 6: Create Audit Dashboardβ
Add custom audit dashboard:
<?php
namespace App\Controller;
class CustomAuditController extends AbstractController
{
#[Route('/admin/audit/custom-events', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function customEvents(Request $request): JsonResponse
{
$startDate = new DateTime($request->query->get('startDate', '-30 days'));
$endDate = new DateTime($request->query->get('endDate', 'now'));
$eventType = $request->query->get('eventType');
$auditLogs = $this->auditRepository->findCustomEvents($startDate, $endDate, $eventType);
return $this->json([
'events' => $auditLogs,
'summary' => $this->generateEventSummary($auditLogs)
]);
}
#[Route('/admin/audit/patient-transfers', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function patientTransfers(Request $request): JsonResponse
{
$startDate = new DateTime($request->query->get('startDate', '-30 days'));
$endDate = new DateTime($request->query->get('endDate', 'now'));
$report = $this->customComplianceReporter->generatePatientTransferReport($startDate, $endDate);
return $this->json($report);
}
#[Route('/admin/audit/medication-changes', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function medicationChanges(Request $request): JsonResponse
{
$startDate = new DateTime($request->query->get('startDate', '-30 days'));
$endDate = new DateTime($request->query->get('endDate', 'now'));
$report = $this->customComplianceReporter->generateMedicationChangeReport($startDate, $endDate);
return $this->json($report);
}
}
Step 7: Test Custom Audit Loggingβ
Create test cases:
<?php
namespace App\Tests\Service;
class CustomAuditLoggerTest extends TestCase
{
public function testPatientTransferLogging(): void
{
$user = new User();
$user->setEmail('doctor@securehealth.dev');
$fromPatient = new Patient();
$fromPatient->setPatientId('PAT-12345');
$toPatient = new Patient();
$toPatient->setPatientId('PAT-12346');
$this->auditLogger->logPatientTransfer($user, $fromPatient, $toPatient, 'Department transfer');
$auditLogs = $this->auditRepository->findByAction('PATIENT_TRANSFER');
$this->assertCount(1, $auditLogs);
$this->assertEquals('PATIENT_TRANSFER', $auditLogs[0]->getAction());
$this->assertEquals('Department transfer', $auditLogs[0]->getDetails()['reason']);
}
public function testMedicationChangeLogging(): void
{
$user = new User();
$user->setEmail('doctor@securehealth.dev');
$patient = new Patient();
$patient->setPatientId('PAT-12345');
$oldMedication = ['medication' => 'Metformin', 'dosage' => '500mg'];
$newMedication = ['medication' => 'Metformin', 'dosage' => '1000mg'];
$this->auditLogger->logMedicationChange($user, $patient, $oldMedication, $newMedication);
$auditLogs = $this->auditRepository->findByAction('MEDICATION_CHANGE');
$this->assertCount(1, $auditLogs);
$this->assertEquals('MEDICATION_CHANGE', $auditLogs[0]->getAction());
$this->assertEquals($oldMedication, $auditLogs[0]->getDetails()['oldMedication']);
$this->assertEquals($newMedication, $auditLogs[0]->getDetails()['newMedication']);
}
}
Step 8: Update Frontendβ
Add custom audit dashboard to the UI:
class CustomAuditDashboard {
renderCustomEvents(events) {
return `
<div class="custom-audit-dashboard">
<h2>Custom Audit Events</h2>
<div class="event-filters">
<select id="eventType">
<option value="">All Events</option>
<option value="PATIENT_TRANSFER">Patient Transfers</option>
<option value="MEDICATION_CHANGE">Medication Changes</option>
<option value="EMERGENCY_ACCESS">Emergency Access</option>
</select>
<input type="date" id="startDate">
<input type="date" id="endDate">
<button onclick="loadCustomEvents()">Filter</button>
</div>
<div class="events-list">
${events.map(event => this.renderEvent(event)).join('')}
</div>
</div>
`;
}
renderEvent(event) {
return `
<div class="audit-event">
<div class="event-header">
<span class="event-type">${event.action}</span>
<span class="event-time">${event.timestamp}</span>
</div>
<div class="event-details">
<p><strong>User:</strong> ${event.user.email}</p>
<p><strong>Resource:</strong> ${event.resource.patientId}</p>
<p><strong>Details:</strong> ${JSON.stringify(event.details)}</p>
</div>
</div>
`;
}
}
Troubleshootingβ
Common Issuesβ
Event Not Logged
- Check event listener registration
- Verify event dispatch
- Check audit logger configuration
Report Generation Issues
- Verify date range parameters
- Check database queries
- Test report generation
Performance Issues
- Optimize audit queries
- Use appropriate indexes
- Implement pagination
Next Stepsβ
- Integration Examples Tutorial - Real-world integrations
- Reference - Common issues and solutions
- Community - Getting help and support