Spring Boot jetty/tomcat embedded access log configuration

For embedded Jetty you can also write this as part of your Spring Boot configuration:

@Bean
public EmbeddedServletContainerFactory jettyConfigBean() {
    JettyEmbeddedServletContainerFactory jef = new JettyEmbeddedServletContainerFactory();
    jef.addServerCustomizers(new JettyServerCustomizer() {
        public void customize(Server server) {
            HandlerCollection handlers = new HandlerCollection();
            for (Handler handler : server.getHandlers()) {
                handlers.addHandler(handler);
            }
            RequestLogHandler reqLogs = new RequestLogHandler();
            NCSARequestLog reqLogImpl = new NCSARequestLog("./logs/access-yyyy_mm_dd.log");
            reqLogImpl.setRetainDays(30);
            reqLogImpl.setAppend(true);
            reqLogImpl.setExtended(false);
            reqLogImpl.setLogTimeZone("GMT");
            reqLogs.setRequestLog(reqLogImpl);
            handlers.addHandler(reqLogs);
            server.setHandler(handlers);

            // For Jetty 9.3+, use the following
            //RequestLogHandler reqLogs = new RequestLogHandler();
            //reqLogs.setServer(server);
            //RequestLogImpl rli = new RequestLogImpl();
            //rli.setResource("/logback-access.xml");
            //rli.setQuiet(false);
            //rli.start();
            //reqLogs.setRequestLog(rli);
            //handlers.addHandler(reqLogs);
            //server.setHandler(handlers);
        }
    });
    return jef;
}

You would have to include the relevant feature in your server container. E.g. for Tomcat add a LogbackValve in an EmbeddedServletContainerCustomizer bean. The TomcatEmbeddedServletContainerFactory has a addContextValves method for this purpose.


After many hours of trying to get a solution to work with SpringBoot 1.4 + Jetty + Logback-access I have finally found an answer to my woes.

Jetty's API interface changed in v9.3 and Logback-access no longer works.

http://shibboleth.1660669.n2.nabble.com/Jetty-9-3-access-logging-recommended-configuration-td7620755.html

There has been a pull request in the Logback project to get it to work again.

https://github.com/qos-ch/logback/pull/269

There a couple of solutions which are mentioned in the above pull request.

Option 1.

Use the org.eclipse.jetty.server.Slf4jRequestLog implementation to route the logging configuration back to classic Logback.

JettyConfiguration @Bean

RequestLogHandler requestLogsHandler = new RequestLogHandler();
requestLogsHandler.setServer(server);
Slf4jRequestLog log = new Slf4jRequestLog();
log.setLoggerName("com.example.accesslog");
requestLogsHandler.setRequestLog(log);
handlers.addHandler(requestLogsHandler);
server.setHandler(handlers);

logback.xml

<appender name="FILE-ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_PATH}/main.log</file>
    <encoder>
        <!-- You'll have to work this out -->
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>${LOG_PATH}/main.%d{yyyy-MM-dd}-%i.log
        </fileNamePattern>
        <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <maxFileSize>20MB</maxFileSize>
        </timeBasedFileNamingAndTriggeringPolicy>
        <maxHistory>14</maxHistory>
    </rollingPolicy>
</appender>

<logger name="com.example.accesslog">
    <appender-ref ref="FILE-ACCESS" />
</logger>

This works but you lose all the access log specific parameters in the custom PatternLayout available with Logback-access. You probably need to roll your own pattern classes.

Thought this might work but it didn't (or I didn't do it properly).

<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="ch.qos.logback.access.PatternLayout">
            <pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"</pattern>
        </layout>
</encoder>

Option 2

Also alluded to in the pull request above is a fix of the issue which can be used until it gets sorted.

Create a class that adds the missing interface and use that instead of RequestLogImpl.

New class

package com.example.ch.qos.logback.access.jetty;

import ch.qos.logback.access.jetty.RequestLogImpl;
import org.eclipse.jetty.util.component.LifeCycle;

public class LogbackAccessRequestLogImplFix1052 extends RequestLogImpl implements LifeCycle {

}

Jetty Configuration @Bean

RequestLogHandler requestLogs = new RequestLogHandler();
requestLogs.setServer(server);
LogbackAccessRequestLogImplFix1052 rli = new LogbackAccessRequestLogImplFix1052();
rli.setResource("/logback-access.xml");
rli.setQuiet(false);
requestLogs.setRequestLog(rli);
handlers.addHandler(requestLogs);
server.setHandler(handlers);

I tried both and ended up going with option 2 for now as I have spent tooooooooo long on this already. I would prefer option 1 though so I can keep all my logging config in the same file.

Good luck.