Aspectran uses a systematic approach to build a flexible and powerful logging system. This document analyzes and explains Aspectran’s logging mechanism with examples.
1. Adoption of Standard Logging Technologies: SLF4J and Logback
Aspectran uses SLF4J (Simple Logging Facade for Java) as its logging abstraction library and adopts Logback as its default implementation. This ensures flexibility by preventing application code from directly depending on a specific logging implementation and consistently manages logs from various libraries such as java.util.logging, log4j, and commons-logging.
Dependency Configuration (aspectran-logging/pom.xml)
The aspectran-logging module includes dependencies for Logback and various SLF4J bridges, allowing logs from other logging frameworks to be controlled through SLF4J.
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>aspectran-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Bridges to forward logs from other logging frameworks to SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j-to-slf4j.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
2. Core Feature: LoggingGroupDiscriminator
The most core feature of Aspectran logging is a custom Logback Discriminator called com.aspectran.logging.logback.LoggingGroupDiscriminator. This class determines which logical “group” a log event belongs to when it occurs, thereby dynamically separating logs.
The group name is determined according to the following priority:
- SLF4J MDC: The value stored with the key
LOGGING_GROUPin the current thread’s MDC (Mapped Diagnostic Context) is used with the highest priority. MDC is a thread-local storage used to hold information that is valid only within a specific context, such as request processing. - ActivityContext Name: If the
LOGGING_GROUPkey is not present in the MDC, the name of the currently running AspectranActivityContextis used as the group name. This is useful for separating the logs of each instance when multiple Aspectran instances are running in a single JVM. - Default Value: If a value cannot be found by the two methods above, the default value specified in the Logback configuration file is used.
3. Logback Configuration and How It Works
LoggingGroupDiscriminator demonstrates its power when used with Logback’s SiftingAppender. SiftingAppender is responsible for dynamically creating child Appenders based on the value returned by the discriminator and forwarding log events to the corresponding Appender.
Here is a configuration example from logback-default.xml.
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
<!-- 1. Identify the logging group using LoggingGroupDiscriminator -->
<discriminator class="com.aspectran.logging.LoggingGroupDiscriminator">
<key>LOGGING_GROUP</key>
<defaultValue>root</defaultValue>
</discriminator>
<sift>
<!-- 2. Dynamically create an Appender based on the identified group name (${LOGGING_GROUP}) -->
<appender name="FILE-${LOGGING_GROUP}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 3. The log file path is dynamically determined by the group name and properties -->
<file>${aspectran.logsDir:-${aspectran.basePath:-app}/logs}/${LOGGING_GROUP}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${aspectran.archivedLogsDir:-${aspectran.logsDir:-${aspectran.basePath:-app}/logs}/archived}/${LOGGING_GROUP}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<charset>${aspectran.logCharset:-UTF-8}</charset>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %msg - %logger{30}.%M\(%line\)%n</pattern>
</encoder>
</appender>
</sift>
</appender>
<root level="info">
<appender-ref ref="SIFT"/>
</root>
<logger name="com.aspectran" level="debug"/>
<logger name="org.quartz" level="info"/>
<logger name="org.apache.ibatis" level="error"/>
</included>
4. Flexible Path and Charset Configuration
Aspectran provides a flexible way to configure log file paths and character sets, allowing logs to be stored outside the application’s base path if necessary. This is achieved using a nested fallback mechanism in the Logback configuration.
System Properties
aspectran.logsDir: Specifies the directory where logs will be stored. It can be an absolute path or a relative path (resolved viaApplicationAdapter.getRealPath()).aspectran.archivedLogsDir: Specifies the directory where archived/rotated logs will be stored.aspectran.logCharset: Specifies the character set to be used for log files. (e.g., UTF-8)
Nested Fallback Pattern
The configuration uses the following pattern for path resolution: ${aspectran.logsDir:-${aspectran.basePath:-app}/logs}
- If the system property
aspectran.logsDiris set, its value is used (allowing for an absolute path “escape” from the base path). - If not set, it falls back to the
/logsdirectory under${aspectran.basePath}. - If
${aspectran.basePath}is also not set, it defaults to theapp/logsdirectory.
This mechanism ensures that the logging system remains functional with sensible defaults while providing the ultimate flexibility for production environments where logs might need to be stored on dedicated storage volumes.
5. Web Application Integration Example
This logging mechanism is particularly powerful when serving multiple web applications from a single server instance. com.aspectran.undertow.server.handler.logging.PathBasedLoggingGroupHandlerWrapper is an Undertow handler that dynamically sets the logging group based on the request URI.
tow-server.xml Configuration Example
<bean id="tow.server.handler.loggingGroupHandlerWrapper"
class="com.aspectran.undertow.server.handler.logging.PathBasedLoggingGroupHandlerWrapper"
scope="prototype">
<properties>
<item name="pathPatternsByGroupName" type="map">
<entry name="jpetstore">
+: /jpetstore/**
</entry>
<entry name="petclinic">
+: /petclinic/**
</entry>
<entry name="demo">
+: /demo/**
-: /demo/examples/gs-rest-service/**
</entry>
</item>
</properties>
</bean>
Operational Flow
- An HTTP request (e.g.,
/jpetstore/shop.do) comes in. PathBasedLoggingGroupHandlerWrapperintercepts the request and compares the URI (/jpetstore/shop.do) with the configured patterns (/jpetstore/**).- It finds the matching group name
jpetstoreand stores the value in SLF4J’s MDC viaMDC.put("LOGGING_GROUP", "jpetstore"). - Subsequently, all log events that occur in the thread processing that request are identified as belonging to the
jpetstoregroup byLoggingGroupDiscriminator. SiftingAppenderdynamically creates aRollingFileAppendernamedFILE-jpetstore(if it doesn’t already exist) based on the valuejpetstoreand sends the log event to this Appender.- As a result, all logs for requests coming in to
/jpetstoreare recorded in thelogs/jpetstore.logfile.
5. Conclusion
Aspectran’s logging mechanism implements a very flexible and extensible system by combining its own unique ideas, LoggingGroupDiscriminator and PathBasedLoggingGroupHandlerWrapper, with standard technologies like SLF4J and Logback. This provides developers with a powerful feature to perfectly separate and manage logs by service or function unit, even in complex multi-tenant or microservice environments.