Статьи

SiftingAppender: регистрация различных потоков в разных файлах журнала

Одна из новых функций Logback — SiftingAppender ( JavaDoc ). Короче говоря, это прокси-приложение, которое создает одного дочернего приложения для каждого уникального значения данного свойства среды выполнения. Обычно это свойство взято из MDC . Вот пример, основанный на официальной документации, указанной выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  
    <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
        <discriminator>
            <key>userid</key>
            <defaultValue>unknown</defaultValue>
        </discriminator>
        <sift>
            <appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender">
                <file>user-${userid}.log</file>
                <layout class="ch.qos.logback.classic.PatternLayout">
                    <pattern>%d{HH:mm:ss:SSS} | %-5level | %thread | %logger{20} | %msg%n%rEx</pattern>
                </layout>
            </appender>
        </sift>
    </appender>
  
    <root level="ALL">
        <appender-ref ref="SIFT" />
    </root>
</configuration>

Обратите внимание, что свойство <file> параметризовано с помощью свойства ${userid} . Откуда эта собственность? Это должно быть помещено в MDC. Например, в веб-приложении, использующем Spring Security, я склонен использовать фильтр сервлетов с помощью SecurityContextHolder :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import javax.servlet._
import org.slf4j.MDC
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
  
class UserIdFilter extends Filter
{
    def init(filterConfig: FilterConfig) {}
  
    def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
        val userid = Option(
            SecurityContextHolder.getContext.getAuthentication
        ).collect{case u: UserDetails => u.getUsername}
  
        MDC.put("userid", userid.orNull)
        try {
            chain.doFilter(request, response)
        } finally {
            MDC.remove("userid")
        }
  
    }
  
    def destroy() {}
}

Просто убедитесь, что этот фильтр применяется после фильтра Spring Security. Но дело не в этом. Наличие заполнителя ${userid} в имени файла приводит к тому, что просеивающий аппендер создает одного дочернего аппендера для каждого отдельного значения этого свойства (то есть для разных имен пользователей). Запуск вашего веб-приложения с этой конфигурацией быстро создаст несколько файлов журнала, таких как user-alice.log , user-bob.log и user-unknown.log user-alice.log , если свойство MDC не установлено. Другой вариант использования — это использование имени потока, а не свойства MDC. К сожалению, это не встроено, но может быть легко подключено с помощью пользовательского Discriminator а не MDCBasedDiscriminator по умолчанию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ThreadNameBasedDiscriminator implements Discriminator<ILoggingEvent> {
  
    private static final String KEY = "threadName";
  
    private boolean started;
  
    @Override
    public String getDiscriminatingValue(ILoggingEvent iLoggingEvent) {
        return Thread.currentThread().getName();
    }
  
    @Override
    public String getKey() {
        return KEY;
    }
  
    public void start() {
        started = true;
    }
  
    public void stop() {
        started = false;
    }
  
    public boolean isStarted() {
        return started;
    }
}

Теперь мы должны logback.xml использовать наш собственный дискриминатор:

01
02
03
04
05
06
07
08
09
10
11
<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator class="com.blogspot.nurkiewicz.ThreadNameBasedDiscriminator"/>
    <sift>
        <appender class="ch.qos.logback.core.FileAppender">
            <file>app-${threadName}.log</file>
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{HH:mm:ss:SSS} | %-5level | %logger{20} | %msg%n%rEx</pattern>
            </layout>
        </appender>
    </sift>
</appender>

Обратите внимание, что мы больше не помещаем %thread в PatternLayout — это не нужно, поскольку имя потока является частью имени файла журнала:

  • app-main.log
  • app-http-nio-8080-exec-1.log
  • app-taskScheduler-1
  • app-ForkJoinPool-1-worker-1.log
  • …и так далее

Вероятно, это не самая удобная настройка для серверного приложения, но на настольном компьютере, где у вас ограниченное количество целевых потоков, таких как EDT , поток ввода-вывода и т. Д., Это может быть жизненно важной альтернативой.