How to Implement Audit Logging for Compliance Using the AuditAspect in Stirling-PDF
Stirling-PDF provides a declarative audit framework that uses Aspect-Oriented Programming (AOP) to intercept @Audited annotated methods and persist structured compliance records to Spring Actuator's AuditEventRepository with configurable granularity levels.
Stirling-PDF ships with a comprehensive audit framework designed for enterprise compliance requirements such as traceability and tamper-evident logging. The core mechanism relies on the AuditAspect class alongside ControllerAuditAspect, which automatically capture user actions, HTTP requests, and method arguments without requiring manual logging boilerplate. By leveraging the @Audited annotation and AuditConfigurationProperties, developers can implement granular audit trails ranging from basic authentication events to verbose payload logging.
Architecture Overview
The audit framework follows a layered AOP design that minimizes performance overhead while maximizing data capture flexibility.
Configuration via AuditConfigurationProperties
Global audit behavior is controlled by app/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java. This bean binds to the audit section in application.yml and holds two critical flags:
enabled– Master switch to turn auditing on or off system-wide.auditLevel– Global default granularity (BASIC,STANDARD, orVERBOSE).
Both AuditAspect and ControllerAuditAspect call auditConfig.isEnabled() and auditConfig.getAuditLevel() at the start of every interception to short-circuit processing when auditing is disabled, ensuring zero runtime penalty for inactive configurations.
The @Audited Annotation
The annotation defined in app/proprietary/src/main/java/stirling/software/proprietary/audit/Audited.java declares audit intent on any Spring bean method. Key attributes include:
type– The preferredAuditEventTypeenum value (defaultHTTP_REQUEST).typeString– Legacy string identifier for backward compatibility or custom events.level– Overrides the globalauditLevelfor fine-grained control.includeArgsandincludeResult– Booleans controlling whether method arguments and return values appear in the audit record (subject to level restrictions).
Core Aspects: AuditAspect vs ControllerAuditAspect
The framework provides two specialized aspects to cover different execution contexts.
AuditAspect (app/proprietary/src/main/java/stirling/software/proprietary/audit/AuditAspect.java) intercepts any method annotated with @Audited across the entire application context. It uses AuditUtils.shouldAudit() for early exit checks, then builds the audit payload via AuditUtils.createBaseAuditData(). If an HTTP request context exists, it enriches the record with web-specific details before resolving the event type and delegating to AuditService.
ControllerAuditAspect (app/proprietary/src/main/java/stirling/software/proprietary/audit/ControllerAuditAspect.java) specifically wraps Spring MVC mappings (@GetMapping, @PostMapping, etc.) and static-resource handlers. It reuses the same AuditUtils methods but adds HTTP latency and status code tracking directly, then calls AuditUtils.addTimingData() with isHttpRequest=true to avoid duplicate measurements.
Data Collection with AuditUtils
The utility class app/proprietary/src/main/java/stirling/software/proprietary/audit/AuditUtils.java centralizes all audit data construction to ensure consistency across aspects. Key methods include:
createBaseAuditData()– Generates timestamp, principal name, and optional class/method metadata.addHttpData()– Captures HTTP method, URI, client IP, session ID, request ID, and form parameters based on the current audit level.addFileData()– RecordsMultipartFiledetails such as filename, size, and content type.addMethodArguments()– Safely serializes method parameters usingsafeToString().addTimingData()– Computes latency and HTTP status codes when the level is at leastSTANDARD.resolveEventType()– Determines the finalAuditEventTypeby analyzing the annotation, controller name, and HTTP verb.safeToString()– Truncates large payloads and sanitizes binary data to prevent repository corruption.
Persistence via AuditService
The app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java wraps the Spring Actuator AuditEventRepository and performs level-aware filtering. It checks auditConfig.isEnabled() && auditConfig.getAuditLevel().includes(level) before writing. The service provides overloaded audit() methods accepting either AuditEventType enums or legacy strings, plus variants that accept a specific principal name.
Step-by-Step Implementation Guide
1. Enable Auditing in application.yml
Configure the global settings in your Spring configuration:
audit:
enabled: true
level: VERBOSE # BASIC | STANDARD | VERBOSE
The AuditConfigurationProperties bean automatically binds these values and makes them available to all aspects.
2. Annotate Target Methods
Apply @Audited to any service or controller method requiring compliance tracking:
package com.example.service;
import stirling.software.proprietary.audit.Audited;
import stirling.software.proprietary.audit.AuditEventType;
import stirling.software.proprietary.audit.AuditLevel;
import org.springframework.stereotype.Service;
@Service
public class DocumentService {
/**
* Generates a PDF report with full audit trail for compliance.
* Captures document ID, format, and result size at STANDARD level.
*/
@Audited(
type = AuditEventType.PDF_PROCESS,
level = AuditLevel.STANDARD,
includeArgs = true,
includeResult = true
)
public byte[] generateReport(String documentId, String format) {
// Business logic producing PDF bytes
byte[] pdf = createPdf(documentId, format);
return pdf;
}
}
The type attribute selects a standardized category from AuditEventType (USER_LOGIN, FILE_OPERATION, SETTINGS_CHANGED, etc.), while level forces STANDARD capture even if the global setting is BASIC. The includeArgs and includeResult flags ensure the audit record contains safe string representations of the input parameters and return value (truncated to 1,000 characters by AuditUtils.safeToString()).
3. Use Legacy String Types for Custom Events
When the standard enum does not cover a domain-specific operation, use typeString for backward compatibility:
@Audited(
typeString = "CUSTOM_BATCH_EXPORT",
level = AuditLevel.VERBOSE,
includeArgs = true
)
public void exportBatch(List<String> ids) {
// Batch processing logic
}
Both aspects detect when typeString is non-empty and route the event through auditService.audit(String type, ...) instead of the enum variant.
4. Accessing Audit Records
Audit events are stored in the Spring Actuator AuditEventRepository. Access them via the Actuator endpoint /actuator/auditevents (if exposed) or inject the repository directly:
@Autowired
private AuditEventRepository auditRepo;
public List<AuditEvent> recentSecurityEvents() {
// Query implementation depends on your repository configuration
return auditRepo.findByPrincipal("user@example.com");
}
The frontend components located in frontend/src/proprietary/components/shared/config/configSections/audit/ (including AuditEventsTable.tsx and AuditFiltersForm.tsx) provide a searchable, filterable interface for compliance officers to review logs without database access.
Complete Code Examples
Auditing a File Upload Service
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.proprietary.audit.Audited;
import stirling.software.proprietary.audit.AuditEventType;
import stirling.software.proprietary.audit.AuditLevel;
@Service
public class FileImportService {
@Audited(
type = AuditEventType.FILE_OPERATION,
level = AuditLevel.VERBOSE,
includeArgs = true,
includeResult = false
)
public String importFile(MultipartFile file) {
// Storage logic
String fileId = storageProvider.save(file.getInputStream());
return fileId;
}
}
This configuration captures the MultipartFile metadata (name, size, content type) via AuditUtils.addFileData() and logs the method arguments, but excludes the return value from the audit trail.
Auditing a Custom REST Endpoint
package com.example.web;
import org.springframework.web.bind.annotation.*;
import stirling.software.proprietary.audit.Audited;
import stirling.software.proprietary.audit.AuditLevel;
@RestController
@RequestMapping("/api/v1/jobs")
public class JobController {
@PostMapping("/execute")
@Audited(
typeString = "CUSTOM_JOB_RUN",
level = AuditLevel.STANDARD,
includeArgs = true
)
public JobStatus runJob(@RequestBody JobRequest request) {
return jobService.execute(request);
}
}
ControllerAuditAspect intercepts this mapping, adds HTTP-specific context (URI, method, latency), and records the event using the custom string type.
Manual Audit Logging
For operations outside the AOP context, inject AuditService directly:
@Service
public class ReportingService {
private final AuditService auditService;
public ReportingService(AuditService auditService) {
this.auditService = auditService;
}
public void logComplianceNote(String user, String comment) {
Map<String, Object> data = Map.of(
"timestamp", java.time.Instant.now().toString(),
"principal", user,
"comment", comment,
"source", "manual_entry"
);
auditService.audit("MANUAL_NOTE", data, AuditLevel.BASIC);
}
}
Summary
- Enable globally via
audit.enabledandaudit.levelinapplication.ymlusingAuditConfigurationProperties. - Annotate methods with
@Audited, selecting anAuditEventTypeor customtypeString, and override the level if needed. - Rely on aspects –
AuditAspecthandles general beans whileControllerAuditAspectcaptures HTTP-specific metadata for web endpoints. - Leverage utilities –
AuditUtilsstandardizes data collection including safe string serialization, file metadata, and timing calculations. - Persist correctly –
AuditServicefilters by level and writes toAuditEventRepositoryonly when appropriate. - Review in UI – Frontend components under
frontend/src/proprietary/components/shared/config/configSections/audit/render searchable compliance reports.
Frequently Asked Questions
What is the difference between AuditAspect and ControllerAuditAspect?
AuditAspect intercepts any Spring bean method annotated with @Audited and is ideal for service-layer operations, while ControllerAuditAspect specifically targets Spring MVC handler methods to capture HTTP-specific data such as request URI, client IP, and response status codes. Both delegate to AuditUtils for consistent data formatting and AuditService for persistence, but ControllerAuditAspect adds web-centric metadata that service-layer aspects cannot access.
How do I exclude sensitive arguments from audit logs?
Set includeArgs = false in the @Audited annotation, or ensure the global audit.level is set to BASIC rather than VERBOSE. At BASIC level, AuditUtils.addMethodArguments() is never invoked, and AuditUtils.safeToString() truncates or excludes large payloads automatically. For fine-grained control over specific fields, you can override the level on a per-method basis using the level annotation attribute.
Where are audit events physically stored?
Events are stored in the Spring Boot Actuator AuditEventRepository bean, which by default uses an in-memory repository but can be configured to persist to databases or external systems via Spring Boot's audit infrastructure. The AuditService class in app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java performs the final write operation after checking the configured audit level and enabled status.
Can I use custom event types not defined in AuditEventType?
Yes. Use the typeString attribute of the @Audited annotation instead of type. When typeString is provided (for example, typeString = "CUSTOM_INTEGRATION_SYNC"), the aspect routes the event to auditService.audit(String type, ...) rather than the enum-based overload, allowing arbitrary string identifiers while maintaining full compliance data structure.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →