Appenders¶
Appenders control where log events are written. Hermes provides several built-in appenders with support for custom implementations.
Built-in Appenders¶
ConsoleAppender¶
Writes log events to stdout/stderr:
import io.hermes.core.appender.ConsoleAppender;
import io.hermes.core.layout.PatternLayout;
ConsoleAppender appender = new ConsoleAppender();
appender.setLayout(new PatternLayout("%d{yyyy-MM-dd HH:mm:ss} %-5level %logger - %msg%n"));
appender.start();
FileAppender¶
Writes log events to a file:
import io.hermes.core.appender.FileAppender;
FileAppender appender = new FileAppender("application.log");
appender.setLayout(new PatternLayout("%d %-5level %logger - %msg%n"));
appender.setAppend(true); // Append to existing file (default: true)
appender.start();
RollingFileAppender¶
Automatically rolls log files based on size or time:
import io.hermes.core.appender.RollingFileAppender;
RollingFileAppender appender = new RollingFileAppender(
"logs/app.log",
"logs/app-%d{yyyy-MM-dd}.log.gz" // Pattern for archived files
);
// Size-based rolling
appender.setMaxFileSize("10MB");
appender.setMaxHistory(30); // Keep 30 days of logs
// Time-based rolling (daily)
appender.setRollingPolicy("daily");
appender.start();
AsyncAppender¶
High-throughput async logging using LMAX Disruptor:
import io.hermes.core.appender.AsyncAppender;
// Wrap another appender for async processing
FileAppender fileAppender = new FileAppender("app.log");
fileAppender.setLayout(new PatternLayout("%d %-5level %msg%n"));
AsyncAppender asyncAppender = new AsyncAppender(fileAppender);
asyncAppender.setQueueSize(8192); // Ring buffer size (power of 2)
asyncAppender.setBlockWhenFull(false); // Drop events when full
asyncAppender.start();
LogstashAppender¶
Sends structured logs to Logstash via TCP:
import io.hermes.core.appender.LogstashAppender;
LogstashAppender appender = new LogstashAppender("localhost", 5000);
appender.setApplicationName("my-service");
appender.setEnvironment("production");
appender.start();
Appender Lifecycle¶
All appenders implement a common lifecycle:
// Start the appender (required before use)
appender.start();
// Check if started
if (appender.isStarted()) {
// Appender is ready
}
// Stop the appender (flushes buffers, closes resources)
appender.stop();
Configuring Multiple Appenders¶
Route logs to multiple destinations:
import io.hermes.core.HermesLogger;
HermesLogger logger = (HermesLogger) LoggerFactory.getLogger(MyClass.class);
// Console appender
ConsoleAppender console = new ConsoleAppender();
console.setLayout(new PatternLayout("%d %-5level %msg%n"));
console.start();
// File appender
FileAppender file = new FileAppender("app.log");
file.setLayout(new PatternLayout("%d %-5level %logger - %msg%n"));
file.start();
// Add both appenders
logger.addAppender(console);
logger.addAppender(file);
Filtering Appenders¶
Level Filtering¶
Only log events at or above a certain level:
FileAppender errorFile = new FileAppender("errors.log");
errorFile.setMinLevel(LogLevel.ERROR); // Only ERROR and above
errorFile.start();
Marker Filtering¶
Route logs based on markers:
FileAppender securityLog = new FileAppender("security.log");
securityLog.setMarkerFilter("SECURITY"); // Only logs with SECURITY marker
securityLog.start();
Custom Appenders¶
Implement the Appender interface to create custom appenders:
import io.hermes.core.appender.Appender;
import io.hermes.core.LogEvent;
public class CustomAppender implements Appender {
private volatile boolean started = false;
private Layout layout;
@Override
public void append(LogEvent event) {
if (!started) return;
String formattedMessage = layout.format(event);
// Write to your custom destination
}
@Override
public void start() {
// Initialize resources (open connections, files, etc.)
started = true;
}
@Override
public void stop() {
// Clean up resources
started = false;
}
@Override
public boolean isStarted() {
return started;
}
@Override
public void setLayout(Layout layout) {
this.layout = layout;
}
}
Performance Considerations¶
Synchronous Appenders¶
Console, File, and RollingFile appenders are synchronous:
- Calling thread blocks until write completes
- Simple and reliable
- Lower throughput under high load
Asynchronous Appenders¶
AsyncAppender provides high throughput:
- Calling thread publishes to ring buffer (non-blocking)
- Background thread writes to wrapped appender
- Trade-off: Risk of log loss on crash
- Ideal for high-volume logging
Best Practices¶
- Always call
start()- Appenders must be started before use - Always call
stop()- Ensures buffers are flushed and resources closed - Use AsyncAppender for high throughput - Wrap slow appenders (file, network)
- Configure appropriate buffer sizes - Balance memory usage vs. throughput
- Use separate appenders for different concerns - e.g., errors.log, security.log
- Set appropriate log levels - Avoid verbose logging to slow appenders
- Monitor appender health - Check
isStarted()in health checks
Appender Configuration Examples¶
Development Setup¶
// Simple console logging for development
ConsoleAppender console = new ConsoleAppender();
console.setLayout(new PatternLayout("%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"));
console.start();
logger.addAppender(console);
Production Setup¶
// Async file logging with rolling for production
RollingFileAppender fileAppender = new RollingFileAppender(
"logs/app.log",
"logs/app-%d{yyyy-MM-dd}.log.gz"
);
fileAppender.setLayout(new PatternLayout("%d %-5level [%thread] %logger - %msg%n"));
fileAppender.setMaxFileSize("100MB");
fileAppender.setMaxHistory(30);
AsyncAppender asyncAppender = new AsyncAppender(fileAppender);
asyncAppender.setQueueSize(8192);
asyncAppender.start();
logger.addAppender(asyncAppender);