Spring Boot:使用嵌入式Jetty服务器时记录所有传入的HTTP请求?

时间:2014-09-26 11:43:14

标签: spring-mvc jetty spring-boot embedded-jetty

我正在使用Spring Boot 1.1.5.RELEASE构建一个Web应用程序,并且我已经按照in the related Spring Boot's documentation所述配置了一个嵌入式Jetty。

我想记录所有传入的HTTP请求,我能想到的唯一解决方案(在Spring Boot的文档中阅读"how to configure Jetty"之后)是介绍EmbeddedServletContainerCustomizer:

package com.acme.rest;

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;


/**
 * blah blah
 * 
 * @author Dimi
 */
@Component
public class EmbededJettyConfig implements EmbeddedServletContainerCustomizer {

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer # customize
     */
    @Override
    public void customize(final ConfigurableEmbeddedServletContainer container) {
        // checks whether the container is Jetty
        if (container instanceof JettyEmbeddedServletContainerFactory) {
            ((JettyEmbeddedServletContainerFactory) container)
                    .addServerCustomizers(jettyServerCustomizer());
        }
    }

    @Bean
    public JettyServerCustomizer jettyServerCustomizer() {

        return new JettyServerCustomizer() {

            /*
             * (non-Javadoc)
             * 
             * @see org.springframework.boot.context.embedded.jetty.JettyServerCustomizer #
             * customize
             */
            @Override
            public void customize(final Server server) {

                HandlerCollection handlers = new HandlerCollection();
                RequestLogHandler requestLogHandler = new RequestLogHandler();
                handlers.setHandlers(new Handler[] {new DefaultHandler(), requestLogHandler});
                server.setHandler(handlers);

                NCSARequestLog requestLog = new NCSARequestLog("logs/requests.log");
                requestLog.setExtended(false);
                requestLogHandler.setRequestLog(requestLog);
            }
        };
    }
}

应用程序现在无法开始抛出异常:

  

java.lang.IllegalArgumentException:需要ServletContext   配置默认servlet处理

简而言之:在Spring Boot中配置嵌入式Jetty日志记录的正确方法是什么?

4 个答案:

答案 0 :(得分:4)

Jetty 9.3.8在服务器上有一个新方法setRequestLog

@Component
public class EnableRequestLog implements EmbeddedServletContainerCustomizer {

    private static final JettyServerCustomizer USE_SLF4J_REQUEST_LOG =
            server -> server.setRequestLog(new Slf4jRequestLog());

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        if (container instanceof JettyEmbeddedServletContainerFactory) {
            ((JettyEmbeddedServletContainerFactory) container)
                    .addServerCustomizers(USE_SLF4J_REQUEST_LOG);
        } else {
            throw new IllegalArgumentException(
                    "Expected a Jetty container factory but encountered " + container.getClass());
        }
    }
}

您可以实现特定的RequestLog,而不是Jetty的Slf4jRequestLog。见http://www.eclipse.org/jetty/documentation/current/configuring-jetty-request-logs.html

答案 1 :(得分:1)

巧合的是,Jetty一直在分析RequestLogHandler行为并最近使用,着眼于记录错误条件,异常和异步。

最近爆出的错误:

让我们假设我们有RequestLogHandler这样的设置......

    File logDir = new File(System.getProperty("log.dir"));

    NCSARequestLog ncsaLog = new NCSARequestLog();
    ncsaLog.setFilename(new File(logDir, "yyyy_MM_dd.request.log").getAbsolutePath());
    ncsaLog.setFilenameDateFormat("yyyy_MM_dd");
    ncsaLog.setExtended(true);
    ncsaLog.setAppend(true);
    ncsaLog.setLogTimeZone("GMT");
    ncsaLog.setRetainDays(90);

    RequestLogHandler requestLogHandler = new RequestLogHandler();
    requestLogHandler.setRequestLog(ncsaLog);

在服务器中使用处理程序设置的旧方法是以下列方式。

    HandlerCollection handlers = new HandlerCollection();
    handlers.addHandler(myHandlers);
    handlers.addHandler(new DefaultHandler());
    handlers.addHandler(requestLogHandler());

    server.setHandler(handlers);

处理程序链看起来像这样:

  • 服务器
    • HandlerCollection
      • (您的处理程序)
      • 的DefaultHandler
      • RequestLogHandler

事实证明,将RequestLogHandler连接到您的处理程序链是一种糟糕的方法,因为捕获并记录的错误条件很少。

建议将RequestLogHandler包裹在应记录的处理程序链中。

    HandlerCollection handlers = new HandlerCollection();
    handlers.addHandler(myHandlers);
    handlers.addHandler(new DefaultHandler());

    requestLogHandler.setHandler(handlers);

    server.setHandler(requestLogHandler);

处理程序链看起来像这样:

  • 服务器
    • RequestLogHandler
      • HandlerCollection
        • (您的处理程序)
        • 的DefaultHandler

这是连接RequestLogHandler的正确方法。

这甚至可以帮助过滤人员不希望记录的处理程序(例如重写/重定向操作)的日志记录事件。

只需将您不想登录的处理程序放在RequestLogHandler

之外

这样的结构:

  • 服务器
    • HandlerCollection
      • RewriteHandler
        • RuleA
        • RuleB
        • RuleC
      • RequestLogHandler
        • HandlerCollection
          • (您的处理程序)
          • 的DefaultHandler

根据您的RewriteHandler规则(如果您是否重定向,以及是否终止),将不会发生日志记录事件。

答案 2 :(得分:1)

稍微迟了一点,但我遇到了同样的问题,解决方案是将已经由boot创建的ServletHandler包装在RequestLogHandler中,如下所示:

@Component
public class EmbededJettyConfig implements EmbeddedServletContainerCustomizer {

/*
 * (non-Javadoc)
 * 
 * @see org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer # customize
 */
@Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
    // checks whether the container is Jetty
    if (container instanceof JettyEmbeddedServletContainerFactory) {
        ((JettyEmbeddedServletContainerFactory) container)
                .addServerCustomizers(jettyServerCustomizer());
    }
}

@Bean
public JettyServerCustomizer jettyServerCustomizer() {

    return new JettyServerCustomizer() {

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.boot.context.embedded.jetty.JettyServerCustomizer #
         * customize
         */
        @Override
        public void customize(final Server server) {
            NCSARequestLog requestLog = new NCSARequestLog("logs/requests.log");
            requestLog.setExtended(false);

            RequestLogHandler requestLogHandler = new RequestLogHandler();
            requestLogHandler.setRequestLog(requestLog);
            requestLogHandler.setHandler(server.getHandler());
            server.setHandler(requestLogHandler);
        }
    };
}

答案 3 :(得分:-1)

这不适用于Spring Boot 1.1.9版,因为JettyEmbeddedServletContainer只处理JettyEmbeddedWebAppContext(处理程序)或HandlerWrapper。我认为没有办法在Spring Boot中为嵌入式Jetty设置多个处理程序。

来自org.springframework.boot.context.embedded.jettyJettyEmbeddedServletContainer的代码片段。

@Override
public void start() throws EmbeddedServletContainerException {
    this.server.setConnectors(this.connectors);

    if (!this.autoStart) {
        return;
    }
    try {
        this.server.start();
        for (Handler handler : this.server.getHandlers()) {
            handleDeferredInitialize(handler);
        }
        Connector[] connectors = this.server.getConnectors();
        for (Connector connector : connectors) {
            connector.start();
            this.logger.info("Jetty started on port: " + getLocalPort(connector));
        }
    }
    catch (Exception ex) {
        throw new EmbeddedServletContainerException(
                "Unable to start embedded Jetty servlet container", ex);
    }
}

private void handleDeferredInitialize(Handler handler) throws Exception {
    if (handler instanceof JettyEmbeddedWebAppContext) {
        ((JettyEmbeddedWebAppContext) handler).deferredInitialize();
    }
    else if (handler instanceof HandlerWrapper) {
        handleDeferredInitialize(((HandlerWrapper) handler).getHandler());
    }
}