Every Java project needs logging. And every Java developer has a war story about it — the XML file that grew to 200 lines, the Log4j-to-SLF4J bridge that conflicted with the SLF4J-to-Log4j bridge, the production incident where the wrong log level burned 40 GB of disk overnight.
Solon takes a different approach. It keeps the industry standard (SLF4J) but replaces the configuration chaos with a single app.yml section. The result is logging that works from day one — and scales when you need it.
Let me walk through how it works.
The Stack: SLF4J All the Way Down
Solon uses SLF4J as its native logging facade. You write against the same LoggerFactory you already know:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void createOrder(Order order) {
log.info("Creating order: {}", order.getId());
// business logic
}
}
No bridge. No adapter. No jcl-over-slf4j dependency. If you've used SLF4J before, you already know how to log in Solon.
Five Plugins, One Configuration Model
Solon offers five logging implementations. Each maps to the same unified configuration, so switching between them means changing one dependency:
| Plugin | Appender Support | Use Case |
|---|---|---|
solon-logging-simple |
console, cloud | Testing, lightweight, no file output |
solon-logging-logback |
console, file, cloud | General production (Logback 1.3.x, Java 8+) |
solon-logging-logback-jakarta |
console, file, cloud | Jakarta namespace (Logback 1.5.x, Java 11+) |
solon-logging-log4j2 |
console, file, cloud | Teams standardizing on Log4j2 |
water-solon-cloud-plugin |
console, cloud | Water service governance platform |
Add one to your pom.xml:
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-logging-logback</artifactId>
</dependency>
That is the entire setup step. No logback.xml required, no log4j2.xml — unless you want one.
Configuration: One Section in app.yml
All logging config lives in solon.logging.appender and solon.logging.logger:
solon:
app:
name: demoapp
solon.logging.appender:
console:
level: TRACE
enable: true
pattern: "%highlight{%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} #%5X{pid} [-%t][*%X{traceId}]%tags[%logger{20}]:} %n%msg%n"
file:
name: "logs/${solon.app.name}"
level: INFO
enable: true
extension: ".log"
maxFileSize: "10 MB"
maxHistory: "7"
pattern: "%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} #%5X{pid} [-%t][*%X{traceId}]%tags[%logger{20}]: %n%msg%n"
cloud:
level: INFO
enable: true
Three built-in appenders:
- console — stdout, defaults to enabled
- file — rolling file appender, defaults to enabled
- cloud — remote log service, defaults to disabled
Logger-level Configuration
Control log levels by package prefix without touching individual classes:
solon.logging.logger:
"root":
level: DEBUG
"com.zaxxer.hikari":
level: WARN
"com.demo.order":
level: INFO
The priority rule is straightforward: appender.level > logger.level > root.level. More specific wins.
Five Levels, Standard Semantics
TRACE < DEBUG < INFO < WARN < ERROR
Same levels you've been using. Nothing new to learn.
MDC: Standard SLF4J Support
Solon inherits SLF4J's MDC (Mapped Diagnostic Context) without any custom API. The default log pattern includes %X{pid} (process ID) and %X{traceId} (distributed trace ID), which are populated automatically by the framework:
solon.logging.appender:
console:
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %X{traceId} %-5level %logger{20} - %msg%n"
If you need custom context, use the standard SLF4J approach:
import org.slf4j.MDC;
MDC.put("orderId", order.getId());
log.info("Processing payment");
MDC.remove("orderId");
No Solon-specific MDC wrapper. Just SLF4J, as-is.
Custom Appenders in Three Lines
When the built-in appenders aren't enough, extend AppenderBase:
package demo.log;
import org.noear.solon.logging.event.AppenderBase;
import org.noear.solon.logging.event.LogEvent;
public class JsonAppender extends AppenderBase {
@Override
public void append(LogEvent logEvent) {
// Convert to JSON and send to your system
String json = toJson(logEvent);
send(json);
}
}
Register it in app.yml:
solon.logging.appender:
json:
level: INFO
class: demo.log.JsonAppender
Async Batch Logging with PersistentAppenderBase
For high-throughput scenarios, Solon provides PersistentAppenderBase — a base class that buffers log events and flushes them in batches:
package demo.log;
import org.noear.solon.logging.persistent.PersistentAppenderBase;
import org.noear.solon.logging.event.LogEvent;
import org.noear.solon.Solon;
public class DatabaseAppender extends PersistentAppenderBase {
LogService logService;
public DatabaseAppender() {
Solon.context().getBeanAsync(LogService.class, bean -> {
logService = bean;
});
}
@Override
public void onEvents(List<LogEvent> list) {
if (logService != null) {
logService.insertList(list);
}
}
}
This is ideal for writing logs to a database, Elasticsearch, or any remote store without blocking the request thread. The batching reduces I/O overhead significantly under load.
Advanced: XML Customization When You Need It
If YAML doesn't give you enough control, drop a logback-solon.xml or log4j2-solon.xml in your classpath root. The framework picks it up automatically, environment-aware variants supported (logback-solon-dev.xml, logback-solon-prod.xml).
You can also use standard logback.xml or log4j2.xml — but then you lose the environment-switching feature.
Cloud-Native: One Plugin Away
For distributed environments, plug in water-solon-cloud-plugin and enable the cloud appender:
solon.logging.appender:
cloud:
level: INFO
enable: true
This ships logs to the Water service governance platform — centralized log aggregation without a separate agent or sidecar. The cloud appender is available in all logging plugins, so you can pair it with any backend.
The Honest Picture
Solon's logging won't replace a dedicated observability pipeline (Logstash + Elasticsearch, Loki + Grafana). But for 90% of projects — monoliths, microservices, cloud-native apps — it covers everything you need:
- SLF4J-native, no bridge hell
- YAML-driven: one section, no XML boilerplate
- Three built-in appenders (console, file, cloud)
- Custom appender support
- Async batch persistence
- MDC out of the box
- Per-package level control
- Environment-aware config switching
- Logback/Log4j2 XML escape hatch
The philosophy is the same as the rest of Solon: solve the common case with minimal config, and get out of the way when you need something custom.
What's your current logging setup? If you've been wrestling with XML configs or bridge conflicts, Solon's approach might save you a few headaches. The official docs at solon.noear.org have the full plugin list and config reference.
Top comments (0)