Skip to content

Developer Guide

This section covers the data model, configuration reference, log capture pipeline, and extension points available to developers integrating Activity Log into an application.

Data Model

Activity Log persists six core entities. An activity log record holds a single captured log event. An extractor defines the rules that determine which log events are saved. A remover defines the rules that determine when old records are deleted. A log viewer defines a saved view configuration used to display logs. A logger filter and a timestamp filter are reusable filter components shared by both extractors and removers.

---
config:
  layout: elk
---
erDiagram
    al_activitylog {
        integer   id                PK
        timestamp timestamp
        varchar   logger
        varchar   detail
        varchar   level
        text      stacktrace
        varchar   session_id
    }
    al_extractor {
        integer id      PK
        varchar name
        boolean active
        varchar levels
        boolean save_stacktrace
        boolean save_session_id
    }
    al_remover {
        integer id               PK
        varchar name
        varchar expiration_unit
        integer expiration_value
        boolean active
        varchar levels
    }
    al_log_viewer {
        integer id                      PK
        varchar title
        varchar levels_filter
        varchar logger_filter
        varchar detail_filter
        varchar route
        timestamp start_date_time_filter
        timestamp end_date_time_filter
        boolean show_timestamp_column
        boolean show_logger_column
        boolean show_level_column
        boolean show_detail_column
        boolean show_stacktrace_column
        boolean show_session_id_column
        boolean show_timestamp_filter
        boolean show_detail_filter
        boolean session_filter_enabled
        boolean live_enabled
    }
    al_logger_filter {
        integer id            PK
        varchar type
        varchar regex_pattern
    }
    al_timestamp_filter {
        integer id          PK
        varchar filter_type
        date    since_date
        date    until_date
        time    from_time
        time    to_time
        varchar days_of_week
    }

    al_extractor ||--o{ al_extractor_logger_filters    : "logger filters"
    al_extractor ||--o{ al_extractor_detail_filters    : "detail filters"
    al_extractor ||--o{ al_extractor_timestamp_filters : "timestamp filters"
    al_remover   ||--o{ al_remover_logger_filters      : "logger filters"
    al_remover   ||--o{ al_remover_detail_filters      : "detail filters"
    al_remover   ||--o{ al_remover_timestamp_filters   : "timestamp filters"
    al_extractor_logger_filters    }o--|| al_logger_filter    : ""
    al_extractor_detail_filters    }o--|| al_logger_filter    : ""
    al_remover_logger_filters      }o--|| al_logger_filter    : ""
    al_remover_detail_filters      }o--|| al_logger_filter    : ""
    al_extractor_timestamp_filters }o--|| al_timestamp_filter : ""
    al_remover_timestamp_filters   }o--|| al_timestamp_filter : ""
Hold "Alt" / "Option" to enable pan & zoom

Database Schema

Activity Log manages twelve tables. The schema below represents the DDL generated by Hibernate for a standard relational database.

CREATE TABLE al_activitylog (
    id          INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    timestamp   TIMESTAMP(6),
    logger      VARCHAR(255),
    detail      VARCHAR(255),
    level       VARCHAR(255),
    stacktrace  TEXT,
    session_id  VARCHAR(255),
    CONSTRAINT pk_al_activitylog PRIMARY KEY (id)
);

CREATE TABLE al_logger_filter (
    id            INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    type          VARCHAR(255),
    regex_pattern VARCHAR(255),
    CONSTRAINT pk_al_logger_filter PRIMARY KEY (id)
);

CREATE TABLE al_timestamp_filter (
    id          INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    filter_type VARCHAR(255),
    since_date  DATE,
    until_date  DATE,
    from_time   TIME,
    to_time     TIME,
    days_of_week VARCHAR(255),
    CONSTRAINT pk_al_timestamp_filter PRIMARY KEY (id)
);

CREATE TABLE al_extractor (
    id              INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    name            VARCHAR(255) NOT NULL,
    active          BOOLEAN,
    levels          VARCHAR(255),
    save_stacktrace BOOLEAN,
    save_session_id BOOLEAN,
    CONSTRAINT pk_al_extractor        PRIMARY KEY (id),
    CONSTRAINT uq_al_extractor_name   UNIQUE (name)
);

CREATE TABLE al_remover (
    id               INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    name             VARCHAR(255) NOT NULL,
    expiration_unit  VARCHAR(255),
    expiration_value INTEGER,
    active           BOOLEAN,
    levels           VARCHAR(255),
    CONSTRAINT pk_al_remover       PRIMARY KEY (id),
    CONSTRAINT uq_al_remover_name  UNIQUE (name)
);

CREATE TABLE al_log_viewer (
    id                      INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    title                   VARCHAR(255),
    levels_filter           VARCHAR(255),
    logger_filter           VARCHAR(255),
    detail_filter           VARCHAR(255),
    route                   VARCHAR(255),
    start_date_time_filter  TIMESTAMP(6),
    end_date_time_filter    TIMESTAMP(6),
    show_timestamp_column   BOOLEAN,
    show_logger_column      BOOLEAN,
    show_level_column       BOOLEAN,
    show_detail_column      BOOLEAN,
    show_stacktrace_column  BOOLEAN,
    show_session_id_column  BOOLEAN,
    show_timestamp_filter   BOOLEAN,
    show_detail_filter      BOOLEAN,
    session_filter_enabled  BOOLEAN NOT NULL DEFAULT FALSE,
    live_enabled            BOOLEAN NOT NULL DEFAULT FALSE,
    CONSTRAINT pk_al_log_viewer PRIMARY KEY (id)
);

CREATE TABLE al_extractor_logger_filters (
    extractor_entity_id  INTEGER NOT NULL,
    logger_filters_id    INTEGER NOT NULL,
    CONSTRAINT fk_al_extractor_logger_filters_extractor     FOREIGN KEY (extractor_entity_id) REFERENCES al_extractor (id),
    CONSTRAINT fk_al_extractor_logger_filters_logger_filter FOREIGN KEY (logger_filters_id)   REFERENCES al_logger_filter (id)
);

CREATE TABLE al_extractor_detail_filters (
    extractor_entity_id  INTEGER NOT NULL,
    detail_filters_id    INTEGER NOT NULL,
    CONSTRAINT fk_al_extractor_detail_filters_extractor     FOREIGN KEY (extractor_entity_id) REFERENCES al_extractor (id),
    CONSTRAINT fk_al_extractor_detail_filters_logger_filter FOREIGN KEY (detail_filters_id)   REFERENCES al_logger_filter (id)
);

CREATE TABLE al_extractor_timestamp_filters (
    extractor_entity_id    INTEGER NOT NULL,
    timestamp_filters_id   INTEGER NOT NULL,
    CONSTRAINT fk_al_extractor_timestamp_filters_extractor        FOREIGN KEY (extractor_entity_id)  REFERENCES al_extractor (id),
    CONSTRAINT fk_al_extractor_timestamp_filters_timestamp_filter FOREIGN KEY (timestamp_filters_id) REFERENCES al_timestamp_filter (id)
);

CREATE TABLE al_remover_logger_filters (
    remover_entity_id   INTEGER NOT NULL,
    logger_filters_id   INTEGER NOT NULL,
    CONSTRAINT fk_al_remover_logger_filters_remover        FOREIGN KEY (remover_entity_id) REFERENCES al_remover (id),
    CONSTRAINT fk_al_remover_logger_filters_logger_filter  FOREIGN KEY (logger_filters_id) REFERENCES al_logger_filter (id)
);

CREATE TABLE al_remover_detail_filters (
    remover_entity_id   INTEGER NOT NULL,
    detail_filters_id   INTEGER NOT NULL,
    CONSTRAINT fk_al_remover_detail_filters_remover        FOREIGN KEY (remover_entity_id) REFERENCES al_remover (id),
    CONSTRAINT fk_al_remover_detail_filters_logger_filter  FOREIGN KEY (detail_filters_id) REFERENCES al_logger_filter (id)
);

CREATE TABLE al_remover_timestamp_filters (
    remover_entity_id      INTEGER NOT NULL,
    timestamp_filters_id   INTEGER NOT NULL,
    CONSTRAINT fk_al_remover_timestamp_filters_remover           FOREIGN KEY (remover_entity_id)   REFERENCES al_remover (id),
    CONSTRAINT fk_al_remover_timestamp_filters_timestamp_filter  FOREIGN KEY (timestamp_filters_id) REFERENCES al_timestamp_filter (id)
);

al_activitylog, al_logger_filter, and al_timestamp_filter use GENERATED BY DEFAULT AS IDENTITY rather than sequence-based generation, reflecting the use of GenerationType.IDENTITY in those entities. The level column stores the string representation of the AuditLevel enum. The levels column in al_extractor and al_remover stores a comma-separated list of AuditLevel values. The expiration_unit column stores the string representation of a ChronoUnit value. The days_of_week column in al_timestamp_filter stores a comma-separated list of DayOfWeek values. The stacktrace column in al_activitylog is mapped as a large text column (unbounded length) rather than a fixed VARCHAR, so full stack traces are stored without truncation and without relying on database large-object handling.

Instant-based timestamps (al_activitylog.timestamp and the al_log_viewer date-time filter bounds) are persisted as absolute points in time (UTC) and rendered in the browser's time zone by the views. Storing instants rather than local date-time strings keeps ordering and range filtering correct regardless of the time zone of the server or of the user viewing the data.

Both al_logger_filter and al_timestamp_filter are referenced by extractor and remover join tables: each entity uses a dedicated set of join tables to avoid cross-contamination of filter associations. The al_logger_filter type covers both logger-name matching and detail-content matching, distinguished by the type column storing the LoggerFilterType enum value.

Module Overview

Activity Log is structured as six Maven modules following the AppJars layered architecture:

Module Artifact ID Description
Model appjars-activity-log-model DTOs, enums, auto-configuration
Business API appjars-activity-log-business ActivityLogService, ExtractorService, RemoverService, LogViewerService interfaces
Business Impl appjars-activity-log-business-impl Service implementations, ActivityLogAppender, ActivityLogAppendersConnector, ExpiredLogsPruner
Data API appjars-activity-log-data ActivityLogDao, ExtractorDao, RemoverDao, LogViewerDao interfaces
Data Impl appjars-activity-log-data-impl JPA entities and DAO implementations
Flow UI appjars-activity-log-flow-free-it Vaadin views and route configuration

A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow-free-it). The API modules (-business, -data) are pulled in transitively.

Spring Auto-Configuration

Activity Log registers itself through Spring Boot's auto-configuration mechanism. The entry point is ActivityLogAutoConfiguration, declared in:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

This class scans the com.appjars.activitylog package for all Spring components and JPA entities. On startup, ActivityLogAppendersConnector initialises a BlockingQueue with the configured capacity and starts a background consumer thread that drains the queue and persists log records via ActivityLogService. The Log4j2 custom appender (ActivityLogAppender) is registered through the standard Log4j2 plugin mechanism and begins forwarding events to the connector as soon as logging is active.

Log Capture Pipeline

Activity Log captures log events through a three-stage pipeline that sits outside the standard Spring request lifecycle:

flowchart TD
    A[Application Code] -->|log statement| B[Log4j2]
    B -->|plugin appender| C[ActivityLogAppender]
    C -->|offer| D[BlockingQueue\ncapacity 1000]
    D -->|consumer thread| E[ActivityLogAppendersConnector]
    E -->|extractor matching| F{Active extractor\nmatches?}
    F -->|yes| G[ActivityLogService.save]
    F -->|no| H[discard]
    G --> I[(al_activitylog)]
Hold "Alt" / "Option" to enable pan & zoom

ActivityLogAppender is a Log4j2 plugin appender declared in log4j2.xml. It receives every log event that passes through the Log4j2 pipeline and offers it to the connector's queue. Events offered to a full queue are dropped rather than blocking the calling thread.

ActivityLogAppendersConnector maintains a BlockingQueue<LogEvent> and a single background consumer thread. The consumer takes events from the queue, evaluates each against all active extractors, and calls ActivityLogService.save() for every event that matches. If no active extractor matches the event, the record is not persisted.

Extractor matching checks whether the log event's level, logger name, message content, and timestamp fall within the rules defined by each active extractor's filter sets. An event must pass all filter sets on a given extractor to be saved by that extractor.

The queue capacity defaults to 1000 events. Under sustained high-throughput logging, events are dropped when the queue is full. This design prevents logging from blocking application threads, at the cost of potential loss of individual records during traffic spikes.

Startup Buffering

Log events emitted while the application is starting — before the Spring context is ready and the connector is available — would otherwise be lost. To avoid this, ActivityLogAppender holds early events in a bounded pre-context buffer and drains them into the connector once Activity Log becomes active. The buffer is configured with two optional attributes on the <ActivityLogAppender> element in log4j2.xml:

Attribute Default Description
bufferCapacity 10000 Maximum number of startup events held before the oldest entries are evicted
bufferLevel INFO Minimum severity level buffered during startup

When the buffer fills, the oldest entries are dropped. If any events were evicted this way, a single summary record is persisted as an ERROR entry on activation, so the loss is visible in the log itself.

If the ActivityLogAppender is not declared in the logging configuration, the pre-context buffer is never created and startup events are not captured. Capture still works from activation on: Activity Log verifies the logging setup when the Spring context refreshes and repairs the configuration if needed — registering a fallback appender on the root logger, attaching a declared-but-unreferenced appender, or replacing an appender copy loaded by a different classloader. A setup no repair can save (wrong LogManager provider or wrong SLF4J binding) fails the startup instead. See Logging Requirements for the verification and repair rules and the properties that control them.

Logging Requirements

This section describes what the application's classpath must provide for capture to work, how those requirements are verified at startup, and how to diagnose a setup where logs are not being captured.

Supported logging backends

As of 2.0.0, Log4j2 is the only supported logging backend, and the requirements below are enforced at startup. Support for capturing through other backends (such as Logback, or directly at the SLF4J level) is under consideration for a future release.

The Two Requirements

For Activity Log to capture anything, two conditions must both hold at runtime:

  1. log4j-core must be the active LogManager provider. The appender is a Log4j2 plugin: if another provider wins (typically log4j-to-slf4j, which redirects the Log4j2 API to SLF4J), the Log4j2 configuration file is never read and the appender never receives a single event.

  2. log4j-slf4j2-impl must own the SLF4J binding. Most application logging — Spring, Hibernate, Vaadin, and most libraries — is emitted through the SLF4J API, not the Log4j2 API. If another binding owns SLF4J (typically logback-classic), those events go to Logback and never reach Log4j2, even when requirement 1 holds. The appender then looks perfectly healthy while capturing almost nothing.

Both problems have the same root cause: spring-boot-starter-logging (Spring Boot's default) brings log4j-to-slf4j and logback-classic to the classpath.

Setting Up the Classpath

Add the Log4j2 starter and exclude the default logging starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Every starter needs its own exclusion

spring-boot-starter-logging is a transitive dependency of every Spring Boot starter. The exclusion must be repeated on each starter the application declares — spring-boot-starter-web, spring-boot-starter-actuator, and so on. A single missed starter silently reintroduces the conflicting jars.

Do not place the exclusion on the spring-boot-dependencies BOM in <dependencyManagement>: Maven ignores <exclusions> on a dependency imported with <scope>import</scope>, so that exclusion has no effect at all while appearing to provide blanket coverage.

To verify the result, resolve the dependency tree and confirm that neither log4j-to-slf4j nor logback-classic appears:

mvn dependency:tree -Dincludes="*:log4j-to-slf4j,*:logback-classic"

To keep the classpath from regressing when new starters are added later, the ban can be enforced at build time:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.6.2</version>
    <executions>
        <execution>
            <id>ban-spring-boot-starter-logging</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <bannedDependencies>
                        <excludes>
                            <exclude>org.springframework.boot:spring-boot-starter-logging</exclude>
                        </excludes>
                    </bannedDependencies>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

Startup Verification (Fail Fast)

Since 2.0.0, both requirements are verified when Activity Log activates (on the Spring context refresh). If either one fails, the application fails to start with a message naming the problem and the exact exclusion to add. A silently empty activity log is treated as worse than a loud boot error.

If you need the application to start anyway — accepting that activity logging will capture nothing or almost nothing — the failure can be downgraded to a warning:

com.appjars.activitylog.appender.strict=false

Why Activity Log does not force Log4j2 itself

A library could try to win the provider conflict programmatically (for example by shipping log4j2.component.properties or setting log4j.provider). This was evaluated and rejected: it restores logging emitted through the Log4j2 API, but the SLF4J binding — which carries nearly all real application logging — would still belong to Logback, so the logs would keep disappearing while the warning went silent. Fixing the classpath is the only correct solution, so that is what the startup error asks for.

Appender Registration and Self-Repair

The recommended setup declares the appender in log4j2.xml, as shown in Getting Started. Declaring it enables startup buffering: events emitted before the Spring context is ready are held in a bounded buffer and persisted once Activity Log activates.

On activation, Activity Log inspects the Log4j2 configuration and repairs common problems:

  • Appender not present (not declared, or the plugin was not discovered — see IDE builds): a fallback appender is registered on the root logger, with a warning. Capture works from that point on, but startup events were not buffered.
  • Appender declared but not referenced by any <AppenderRef>: the declared appender is attached to the root logger, with a warning suggesting the missing reference.
  • Appender loaded by a different classloader (IDE hot-restart, classloader isolation): the unreachable copy is replaced by a reachable instance, preserving its name, filter, and logger attachments — including each <AppenderRef>'s level and filter.

The first two repairs change what the configuration captures (they attach an unfiltered appender to the root logger). An application that prefers to keep its logging configuration untouched can decline them:

com.appjars.activitylog.appender.autoRegister=false

With auto-registration declined and no appender configured, activity logging stays inert and only the warning is emitted.

IDE Builds and Plugin Discovery

Log4j2 discovers plugins through a Log4j2Plugins.dat descriptor generated by an annotation processor during compilation. Maven builds run it automatically, but Eclipse JDT-based compilers (including the Eclipse IDE and VS Code Java tooling) have annotation processing disabled by default — which historically produced the confusing symptom of an application that captures logs when launched with mvn spring-boot:run but not from the IDE.

Two safeguards cover this case:

  • The packages="com.appjars.activitylog.business.service" attribute on <Configuration> (part of the recommended log4j2.xml) lets Log4j2 find the appender without the descriptor.
  • Even if discovery fails entirely, the fallback registration described above kicks in on activation.

Troubleshooting

Startup message Cause Fix
Log4j2 core is not the active logging implementation (LogManager resolved to ...) log4j-to-slf4j is on the classpath and outranks log4j-core as provider Exclude spring-boot-starter-logging from the starter that introduced it
The SLF4J binding is ..., so logs emitted through the SLF4J API ... cannot be captured logback-classic (or another binding) owns the SLF4J binding instead of log4j-slf4j2-impl Exclude spring-boot-starter-logging from the starter that introduced it
ActivityLogAppender was not found in the Log4j2 configuration The appender is not declared in log4j2.xml, or the plugin was not discovered (IDE build without annotation processing) Declare the appender and keep the packages attribute on <Configuration>; capture still works through the fallback, but without startup buffering
ActivityLogAppender is declared in the Log4j2 configuration but no logger references it <ActivityLogAppender> exists under <Appenders> but no <AppenderRef> points to it Add <AppenderRef ref="ActivityLogAppender"/> to the intended <Root>/<Logger>
The configured ActivityLogAppender was loaded by a different classloader IDE hot-restart or classloader isolation created an unreachable appender copy Automatic — the copy is replaced; note the replaced copy's startup buffer is not recovered
N activity log(s) were discarded during startup (persisted as an ERROR record) The startup buffer filled before the Spring context was ready Increase bufferCapacity or raise bufferLevel on <ActivityLogAppender>

Expired Log Pruning

ExpiredLogsPruner is a Spring-managed @Component that runs on a configurable schedule and deletes log records that have exceeded the expiration window defined by active removers. It processes up to a configurable maximum number of records per round to bound the size of each delete operation.

The pruner is enabled by default and can be disabled by setting com.appjars.activitylog.logspruner.removalEnabled to false. When disabled, old records accumulate indefinitely unless deleted manually or through ActivityLogService.deleteExpiredLogs().

Configuration Properties

Log Capture

Property Default Description
com.appjars.activitylog.appendersconnector.queueCapacity 1000 Maximum number of log events held in the capture queue before events are dropped
com.appjars.activitylog.appender.strict true Fail the application startup when the logging setup cannot be captured — see Logging Requirements
com.appjars.activitylog.appender.autoRegister true Allow Activity Log to register or re-attach its appender on activation — see Logging Requirements

Log Pruning

Property Default Description
com.appjars.activitylog.logspruner.removalEnabled true Whether the expired logs pruner runs automatically
com.appjars.activitylog.logspruner.prunningFrecuency 5000 Interval in milliseconds between pruning rounds
com.appjars.activitylog.logspruner.maxLogsPerRound 500 Maximum number of records deleted in a single pruning round

Display Formatting

Property Default Description
com.appjars.activitylog.dateformat dd-MM-yy Date format pattern used in log viewer columns
com.appjars.activitylog.timeformat HH:mm Time format pattern used in log viewer columns
com.appjars.activitylog.datetimeformat dd-MM-yyyy HH:mm:ss Combined date-time format pattern used in log viewer columns

View URLs

Property Default Description
com.appjars.activitylog.url.activitylogview al/activitylog URL path of the activity log view
com.appjars.activitylog.url.extractorsview al/extractors URL path of the extractor list view
com.appjars.activitylog.url.extractorsview-create al/extractors/create URL path of the extractor creation view
com.appjars.activitylog.url.extractorsview-edit al/extractors/edit URL path of the extractor edit view
com.appjars.activitylog.url.removersview al/removers URL path of the remover list view
com.appjars.activitylog.url.removersview-create al/removers/create URL path of the remover creation view
com.appjars.activitylog.url.removersview-edit al/removers/edit URL path of the remover edit view
com.appjars.activitylog.url.logviewer al/logviewer URL path of the log viewer list view
com.appjars.activitylog.url.logviewer-create al/logviewer/create URL path of the log viewer creation view
com.appjars.activitylog.url.logviewer-edit al/logviewer/edit URL path of the log viewer edit view

Service API

ActivityLogService

ActivityLogService is the primary interface for accessing and managing log records. It extends CrudService and exposes the following methods specific to the module:

// Persist a single log record
void log(ActivityLogDto activityLog);

// Retrieve a paginated, filtered, and sorted stream of log records
Stream<ActivityLogDto> getLogs(int offset, int limit, ActivityLogFilter filter, List<ActivityLogSort> sortOrder);

// Count log records matching a filter
Integer countLogs(int offset, int limit, ActivityLogFilter filter);

// Delete all records that have exceeded any active remover's expiration window
void deleteExpiredLogs();

// Count records created today
Long countTodayLogs();

deleteExpiredLogs() is the method called by ExpiredLogsPruner on each scheduled round. It can also be called directly from application code when a manual pruning pass is required.

ExtractorService

ExtractorService manages extractor definitions. Key methods beyond standard CRUD:

// Toggle the active state of an extractor
void switchActiveExtractor(ExtractorDto extractor);

// Find an extractor by its unique name
Optional<ExtractorDto> findByName(String name);

Only active extractors participate in event matching. An application with no active extractor saves no log records regardless of how many events flow through the Log4j2 pipeline.

RemoverService

RemoverService manages remover definitions. Key methods beyond standard CRUD:

// Toggle the active state of a remover
void switchActiveRemover(RemoverDto remover);

// Find a remover by its unique name
Optional<RemoverDto> findByName(String name);

Only active removers are evaluated by ExpiredLogsPruner. A remover's expiration window is defined by combining expiration_value (a number) with expiration_unit (a ChronoUnit such as DAYS or HOURS).

LogViewerService

LogViewerService manages saved log viewer configurations. Key methods beyond standard CRUD:

// Find a log viewer by its route path
Optional<LogViewerDto> findByRoute(String route);

// Find a log viewer by its display title
Optional<LogViewerDto> findByTitle(String title);

Each log viewer record stores both the filter state and the column visibility configuration for a specific view route, allowing multiple independently configured log displays to coexist in the same application.

Customisation

Assigning a Router Layout

By default, Activity Log registers its views without a parent layout. To wrap them in the application's main layout, inject the RouteConfigurer bean using its qualifier and call setViewsRouterLayout in a @PostConstruct method:

@Autowired
@Qualifier("ActivityLogRouteConfigurer")
private RouteConfigurer routeConfigurer;

@PostConstruct
public void configure() {
    routeConfigurer.setViewsRouterLayout(MainLayout.class);
}

The @Qualifier is required because multiple AppJars may contribute a bean named RouteConfigurer.

Customising View URLs

The default URL paths can be overridden in application.properties:

com.appjars.activitylog.url.activitylogview=myapp/activity
com.appjars.activitylog.url.extractorsview=myapp/activity/extractors
com.appjars.activitylog.url.removersview=myapp/activity/removers
com.appjars.activitylog.url.logviewer=myapp/activity/viewers

The full list of configurable properties and their defaults is in Configuring View Route Paths.

Tuning the Capture Queue

For applications that generate very high log volumes, the queue capacity and pruning frequency can be adjusted to balance memory use against record loss:

com.appjars.activitylog.appendersconnector.queueCapacity=5000
com.appjars.activitylog.logspruner.prunningFrecuency=60000
com.appjars.activitylog.logspruner.maxLogsPerRound=2000

Increasing the queue capacity reduces the probability of event loss under burst load. Increasing the pruning frequency (in milliseconds) reduces the rate at which the background pruner runs, which lowers database write pressure during periods of heavy activity.