logback: Two appenders, multiple loggers, different levels

You can also do this somewhat more simply if you are willing to inherit from the root logger, e.g. here we add an extra logger for errors, that logs to stderr. It's only enabled for particular loggers.

<configuration>
    <appender name="CONSOLE-stdout" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target> <!-- the default -->
        <encoder>
            <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="CONSOLE-stderr" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>

        <target>System.err</target>
        <encoder>
            <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="CONSOLE-stdout" />
    </root>

        <!-- We want error logging from this logger to go to an extra appender 
             It still inherits CONSOLE-stdout from the root logger -->
    <logger name="org.springframework" level="INFO">
        <appender-ref ref="CONSOLE-stderr" />
    </logger>
</configuration>

Create a ThresholdLoggerFilter class which can be put on an appender like:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>INFO</level>
    </filter>
    <filter class="com.myapp.ThresholdLoggerFilter">
        <logger>org.springframework</logger>
        <level>ERROR</level>
    </filter>
    </appender>

The following code works

package com.myapp;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class ThresholdLoggerFilter extends Filter<ILoggingEvent> {
    private Level level;
    private String logger;

    @Override
    public FilterReply decide(ILoggingEvent event) {
        if (!isStarted()) {
            return FilterReply.NEUTRAL;
        }

        if (!event.getLoggerName().startsWith(logger))
            return FilterReply.NEUTRAL;

        if (event.getLevel().isGreaterOrEqual(level)) {
            return FilterReply.NEUTRAL;
        } else {
            return FilterReply.DENY;
        }
    }

    public void setLevel(Level level) {
        this.level = level;
    }

    public void setLogger(String logger) {
        this.logger = logger;
    }

    public void start() {
        if (this.level != null && this.logger != null) {
            super.start();
        }
    }
}