# How to Implement Audit Logging for Compliance Using the AuditAspect in Stirling-PDF

> Implement audit logging for compliance in Stirling-PDF using AuditAspect. Persist structured compliance records with configurable granularity using AOP and Spring Actuator.

- Repository: [Stirling Tools/Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF)
- Tags: how-to-guide
- Published: 2026-03-01

---

**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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/proprietary/src/main/java/stirling/software/proprietary/config/AuditConfigurationProperties.java). This bean binds to the `audit` section in [`application.yml`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/application.yml) and holds two critical flags:

- `enabled` – Master switch to turn auditing on or off system-wide.
- `auditLevel` – Global default granularity (`BASIC`, `STANDARD`, or `VERBOSE`).

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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/proprietary/src/main/java/stirling/software/proprietary/audit/Audited.java) declares audit intent on any Spring bean method. Key attributes include:

- `type` – The preferred `AuditEventType` enum value (default `HTTP_REQUEST`).
- `typeString` – Legacy string identifier for backward compatibility or custom events.
- `level` – Overrides the global `auditLevel` for fine-grained control.
- `includeArgs` and `includeResult` – 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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/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()` – Records `MultipartFile` details such as filename, size, and content type.
- `addMethodArguments()` – Safely serializes method parameters using `safeToString()`.
- `addTimingData()` – Computes latency and HTTP status codes when the level is at least `STANDARD`.
- `resolveEventType()` – Determines the final `AuditEventType` by 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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/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:

```yaml
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:

```java
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:

```java
@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:

```java
@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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/AuditEventsTable.tsx) and [`AuditFiltersForm.tsx`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/AuditFiltersForm.tsx)) provide a searchable, filterable interface for compliance officers to review logs without database access.

## Complete Code Examples

### Auditing a File Upload Service

```java
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

```java
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:

```java
@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.enabled` and `audit.level` in [`application.yml`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/application.yml) using `AuditConfigurationProperties`.
- **Annotate methods** with `@Audited`, selecting an `AuditEventType` or custom `typeString`, and override the level if needed.
- **Rely on aspects** – `AuditAspect` handles general beans while `ControllerAuditAspect` captures HTTP-specific metadata for web endpoints.
- **Leverage utilities** – `AuditUtils` standardizes data collection including safe string serialization, file metadata, and timing calculations.
- **Persist correctly** – `AuditService` filters by level and writes to `AuditEventRepository` only 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`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/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.