Embedded Jetty Spark instance with request logger

How to create a Spark instance with an embedded Jetty server configuration with a log4j request logger

Creating an embedded Jetty server with a request logger

Spark 2.6.0 introduced the option of providing a configurable embedded Jetty server. This tutorial shows how to use this capability in order to configure such a server that supports logging of incoming requests using log4j.

Initial setup

We’ll start with a basic hello world instance of Spark


public class ApplicationMain {

    public static void main(String[] args) {
        get("/hello", (request, response) -> "world");
    }

}

Creating the logger

First up, let’s create the access logger. Given a log4j logger, we will want to log messages in a standard format. For this purpose, we can implement an instance of AbstractNCSARequestLog that takes our logger as an argument


public class RequestLogFactory {

    private Logger logger;

    public RequestLogFactory(Logger logger) {
        this.logger = logger;
    }

    AbstractNCSARequestLog create() {
        return new AbstractNCSARequestLog() {
            @Override
            protected boolean isEnabled() {
                return true;
            }

            @Override
            public void write(String s) throws IOException {
                logger.info(s);
            }
        };
    }
}

The embedded server factory

We can’t just provide Spark with a server instance. Rather, we need to provide a factory that Spark will invoke when it decides to create the server. This factory can take the request log as an argument


public class EmbeddedJettyFactoryConstructor {
    AbstractNCSARequestLog requestLog;

    public EmbeddedJettyFactoryConstructor(AbstractNCSARequestLog requestLog) {
        this.requestLog = requestLog;
    }

    EmbeddedJettyFactory create() {
        return new EmbeddedJettyFactory((maxThreads, minThreads, threadTimeoutMillis) -> {
            Server server;
            if (maxThreads > 0) {
                int max = maxThreads > 0 ? maxThreads : 200;
                int min = minThreads > 0 ? minThreads : 8;
                int idleTimeout = threadTimeoutMillis > 0 ? threadTimeoutMillis : '\uea60';
                server = new Server(new QueuedThreadPool(max, min, idleTimeout));
            } else {
                server = new Server();
            }

            server.setRequestLog(requestLog);
            return server;
        });
    }
}

The implementation here is identical to Spark’s embedded Jetty with the addition of server.setRequestLog(requestLog);

Creating a utitily function for the embedded server

Let’s tie it together in a utility function that accepts our original log4j logger and overrides Spark’s default Jetty implementation with ours


public class SparkUtils {
    public static void createServerWithRequestLog(Logger logger) {
        EmbeddedJettyFactory factory = createEmbeddedJettyFactoryWithRequestLog(logger);
        EmbeddedServers.add(EmbeddedServers.Identifiers.JETTY, factory);
    }

    private static EmbeddedJettyFactory createEmbeddedJettyFactoryWithRequestLog(org.apache.log4j.Logger logger) {
        AbstractNCSARequestLog requestLog = new RequestLogFactory(logger).create();
        return new EmbeddedJettyFactoryConstructor(requestLog).create();
    }
}

Now all that remains is to define the log4j logger and call the utility function in our main


    public static void main(String[] args) {
        Logger logger = Logger.getLogger(ApplicationMain.class);
        SparkUtils.createServerWithRequestLog(logger);

        get("/hello", (request, response) -> "world");
    }

The result

After we spin up our Spark instance and go to http://localhost:4567/hello, we will see the following output in logs:


2017-07-24 21:29:52 INFO  ApplicationMain:25 - 0:0:0:0:0:0:0:1 - - [24/Jul/2017:18:29:52 +0000] "GET /hello HTTP/1.1" 200 5