DEV Community

Solon Framework
Solon Framework

Posted on

Solon Logging: SLF4J-Native, YAML-Driven, No XML Required

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The priority rule is straightforward: appender.level > logger.level > root.level. More specific wins.

Five Levels, Standard Semantics

TRACE < DEBUG < INFO < WARN < ERROR
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Register it in app.yml:

solon.logging.appender:
  json:
    level: INFO
    class: demo.log.JsonAppender
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)