Spring Boot Integration¶
Hermes provides native Spring Boot integration through auto-configuration and YAML-based configuration.
Installation¶
Add the Spring Boot starter dependency:
Maven¶
<dependency>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
Gradle¶
Configuration¶
Configure Hermes in application.yml:
hermes:
# Root and package-specific log levels
level:
root: INFO
packages:
io.github.dotbrains: DEBUG
com.example: TRACE
# Log pattern for console output
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
# Async logging with LMAX Disruptor
async:
enabled: true
queue-size: 1024
block-when-full: false
Auto-Configuration¶
Hermes auto-configures when hermes-spring-boot-starter is on the classpath.
What Gets Auto-Configured¶
- Logger Provider: Registers
HermesLoggerProvidervia ServiceLoader - Log Levels: Configures root and package-specific log levels
- Appenders: Sets up console and file appenders with configured patterns
- Async Logging: Wraps appenders with AsyncAppender when enabled
- Health Indicator: Exposes logging status to Spring Boot Actuator
Configuration Properties¶
All properties are bound under the hermes prefix:
@ConfigurationProperties(prefix = "hermes")
public class HermesProperties {
private LevelConfig level = new LevelConfig();
private String pattern;
private AsyncConfig async = new AsyncConfig();
// ...
}
Usage in Spring Beans¶
With @InjectLogger¶
import io.github.dotbrains.InjectLogger;
import org.springframework.stereotype.Service;
@Service
@InjectLogger
public class UserService extends UserServiceHermesLogger {
public void createUser(String username) {
log.info("Creating user: {}", username);
}
}
Manual Logger Creation¶
import io.github.dotbrains.Logger;
import io.github.dotbrains.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void processOrder(Long orderId) {
log.info("Processing order: {}", orderId);
}
}
MDC with Web Requests¶
Configure MDC automatically for all requests:
import io.github.dotbrains.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
@Component
public class MdcFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try {
// Set MDC values for this request
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("method", request.getMethod());
MDC.put("path", request.getRequestURI());
MDC.put("clientIp", request.getRemoteAddr());
filterChain.doFilter(request, response);
} finally {
// Always clear MDC after request
MDC.clear();
}
}
}
Custom Appender Configuration¶
Configure appenders programmatically:
import io.github.dotbrains.core.appender.*;
import io.github.dotbrains.core.layout.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LoggingConfig {
@Bean
public ConsoleAppender consoleAppender(HermesProperties properties) {
ConsoleAppender appender = new ConsoleAppender();
appender.setLayout(new PatternLayout(properties.getPattern()));
appender.start();
return appender;
}
@Bean
public FileAppender fileAppender() {
FileAppender appender = new FileAppender("logs/application.log");
appender.setLayout(new PatternLayout(
"%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n"
));
appender.start();
return appender;
}
@Bean
public AsyncAppender asyncAppender(FileAppender fileAppender,
HermesProperties properties) {
AsyncAppender async = new AsyncAppender(fileAppender);
async.setQueueSize(properties.getAsync().getQueueSize());
async.setBlockWhenFull(properties.getAsync().isBlockWhenFull());
async.start();
return async;
}
}
Profiles-Based Configuration¶
Different configurations for different environments:
application.yml (default)¶
application-dev.yml¶
hermes:
level:
root: DEBUG
packages:
com.example: TRACE
pattern: "%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
async:
enabled: false # Synchronous for easier debugging
application-prod.yml¶
hermes:
level:
root: WARN
packages:
com.example: INFO
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] %logger - %msg%n"
async:
enabled: true
queue-size: 8192
block-when-full: false
Actuator Integration¶
Health Indicator¶
Hermes provides a health indicator for Spring Boot Actuator:
Access health status at: http://localhost:8080/actuator/health
Response:
{
"status": "UP",
"components": {
"hermesLogging": {
"status": "UP",
"details": {
"appenders": 2,
"asyncEnabled": true,
"queueSize": 1024
}
}
}
}
Custom Health Indicator¶
import io.github.dotbrains.core.appender.AsyncAppender;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component("hermesLogging")
public class HermesHealthIndicator implements HealthIndicator {
private final AsyncAppender asyncAppender;
public HermesHealthIndicator(AsyncAppender asyncAppender) {
this.asyncAppender = asyncAppender;
}
@Override
public Health health() {
long droppedEvents = asyncAppender.getDroppedEventCount();
if (droppedEvents > 1000) {
return Health.down()
.withDetail("droppedEvents", droppedEvents)
.withDetail("message", "Too many dropped log events")
.build();
}
return Health.up()
.withDetail("droppedEvents", droppedEvents)
.withDetail("queueSize", asyncAppender.getQueueSize())
.build();
}
}
Logstash Integration¶
Configure Logstash appender for ELK stack:
hermes:
logstash:
enabled: true
host: logstash.example.com
port: 5000
application-name: ${spring.application.name}
environment: ${spring.profiles.active}
connection-timeout: 5000
write-timeout: 10000
Bean configuration:
@Configuration
@ConditionalOnProperty("hermes.logstash.enabled")
public class LogstashConfig {
@Bean
public LogstashAppender logstashAppender(HermesProperties properties) {
LogstashProperties logstash = properties.getLogstash();
LogstashAppender appender = new LogstashAppender(
logstash.getHost(),
logstash.getPort()
);
appender.setApplicationName(logstash.getApplicationName());
appender.setEnvironment(logstash.getEnvironment());
appender.setConnectionTimeout(logstash.getConnectionTimeout());
appender.setWriteTimeout(logstash.getWriteTimeout());
AsyncAppender async = new AsyncAppender(appender);
async.setQueueSize(8192);
async.start();
return async;
}
}
Testing¶
Test Configuration¶
Reduce log verbosity in tests:
# src/test/resources/application-test.yml
hermes:
level:
root: WARN
packages:
com.example: INFO
async:
enabled: false # Synchronous for predictable test output
Test Logging¶
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("test")
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreateUser() {
// Logs will use test configuration
userService.createUser("testuser");
}
}
Best Practices¶
- Use profiles - Different log levels for dev/test/prod
- Enable async in production - Better performance
- Include request IDs - Use MDC filter for traceability
- Monitor health - Use Actuator health indicators
- Structured logging - Use JSON layout for production
- Configure retention - Use RollingFileAppender with max history
- Test configuration - Verify log output in integration tests
Complete Example¶
# application-prod.yml
spring:
application:
name: order-service
hermes:
level:
root: INFO
packages:
com.example: INFO
org.springframework: WARN
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] [%X{userId}] %logger{36} - %msg%n"
async:
enabled: true
queue-size: 8192
block-when-full: false
logstash:
enabled: true
host: logstash.internal.example.com
port: 5000
application-name: ${spring.application.name}
environment: production
connection-timeout: 5000
write-timeout: 10000
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
With this configuration:
- Logs to console with structured pattern including MDC
- Sends logs to Logstash for centralized logging
- Uses async logging for high performance
- Exposes health status via Actuator
- Ready for production use