diff --git a/logback-access/src/main/java/ch/qos/logback/access/jetty/JettyModernServerAdapter.java b/logback-access/src/main/java/ch/qos/logback/access/jetty/JettyModernServerAdapter.java new file mode 100644 index 0000000000..299ba1c757 --- /dev/null +++ b/logback-access/src/main/java/ch/qos/logback/access/jetty/JettyModernServerAdapter.java @@ -0,0 +1,66 @@ +/** + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2015, QOS.ch. All rights reserved. + * + * This program and the accompanying materials are dual-licensed under + * either the terms of the Eclipse Public License v1.0 as published by + * the Eclipse Foundation + * + * or (per the licensee's choosing) + * + * under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation. + */ +package ch.qos.logback.access.jetty; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import ch.qos.logback.access.spi.ServerAdapter; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +/** + * A Jetty 9.4.x and 10.0.x specific implementation of the {@link ServerAdapter} interface. + * + * @author Sébastien Pennec + * @author Ceki Gulcu + * @author Joakim Erdfelt + */ +public class JettyModernServerAdapter extends JettyServerAdapter +{ + public JettyModernServerAdapter(Request jettyRequest, Response jettyResponse) { + super(jettyRequest, jettyResponse); + } + + @Override + public long getContentLength() { + return response.getHttpChannel().getBytesWritten(); + } + + @Override + public int getStatusCode() { + return response.getCommittedMetaData().getStatus(); + } + + @Override + public long getRequestTimestamp() { + return request.getTimeStamp(); + } + + @Override + public Map buildResponseHeaderMap() { + Map responseHeaderMap = new HashMap(); + Iterator httpFieldIter = response.getHttpFields().iterator(); + while (httpFieldIter.hasNext()) { + HttpField httpField = httpFieldIter.next(); + String key = httpField.getName(); + String value = httpField.getValue(); + responseHeaderMap.put(key, value); + } + return responseHeaderMap; + } + +} diff --git a/logback-access/src/main/java/ch/qos/logback/access/jetty/RequestLogImpl.java b/logback-access/src/main/java/ch/qos/logback/access/jetty/RequestLogImpl.java index bb11766d90..0574eae0d3 100644 --- a/logback-access/src/main/java/ch/qos/logback/access/jetty/RequestLogImpl.java +++ b/logback-access/src/main/java/ch/qos/logback/access/jetty/RequestLogImpl.java @@ -19,14 +19,6 @@ import java.util.Iterator; import java.util.List; -import ch.qos.logback.core.status.InfoStatus; -import ch.qos.logback.core.util.FileUtil; -import ch.qos.logback.core.util.StatusPrinter; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.RequestLog; -import org.eclipse.jetty.server.Response; - import ch.qos.logback.access.joran.JoranConfigurator; import ch.qos.logback.access.spi.AccessEvent; import ch.qos.logback.access.spi.IAccessEvent; @@ -42,22 +34,114 @@ import ch.qos.logback.core.spi.FilterAttachableImpl; import ch.qos.logback.core.spi.FilterReply; import ch.qos.logback.core.status.ErrorStatus; +import ch.qos.logback.core.status.InfoStatus; +import ch.qos.logback.core.util.FileUtil; import ch.qos.logback.core.util.OptionHelper; +import ch.qos.logback.core.util.StatusPrinter; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.component.LifeCycle; /** - * This class is logback's implementation of jetty's RequestLog interface.

+ * This class is logback's implementation of jetty's RequestLog interface. + *

* It can be seen as logback classic's LoggerContext. Appenders can be attached * directly to RequestLogImpl and RequestLogImpl uses the same StatusManager as - * LoggerContext does. It also provides containers for properties.

To - * configure jetty in order to use RequestLogImpl, the following lines must be - * added to the jetty configuration file, namely etc/jetty.xml: + * LoggerContext does. It also provides containers for properties. + *

+ *

Supported Jetty Versions

+ *

+ * This {@code RequestLogImpl} only supports Jetty 7.0.0 through Jetty 10. + * If you are using Jetty 11 with the new Jakarta Servlets (namespace {@code jakarta.servlet}) + * then you will need a more modern version of {@code logback-access}. + *

+ *

Configuring for Jetty 9.4.x through to Jetty 10.0.x

+ *

+ * Jetty 9.4.x and Jetty 10.x use a modern @{code server.setRequestLog(RequestLog)} interface that + * is based on a Server level RequestLog behavior. This means all requests are logged, + * even bad requests, and context-less requests. The internals of the Jetty Request and + * Response objects track the state of the object at the time they are committed (the + * actual state during the application when an action on the network commits the + * request/response exchange). This prevents behaviors from 3rd party libraries + * that change the state of the request / response before the RequestLog gets a chance + * to log the details. This differs from Jetty 9.3.x and + * older in that those versions used a (now deprecated) {@code RequestLogHandler} and + * would never see bad requests, or context-less requests, + * and if a 3rd party library modifies the the response (for example by setting + * {@code response.setStatus(200)} after the response has been initiated on the network) + * this change in status would be logged, instead of the actual status that was sent. + *

+ *

+ * First, you must be using the proper {@code ${jetty.home}} and {@code ${jetty.base}} + * directory split. Configure your {@code ${jetty.base}} with at least the `resources` module + * enabled (so that your configuration can be found). + *

+ *

+ * Next, create a {@code ${jetty.base}/etc/logback-access-requestlog.xml} file with the following + * content. + *

+ *
+ *   <?xml version="1.0"?>
+ *   <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
+ *
+ *   <Configure id="Server" class="org.eclipse.jetty.server.Server">
+ *     <Set name="requestLog">
+ *       <New id="LogbackAccess" class="ch.qos.logback.access.jetty.RequestLogImpl">
+ *         <Set name="resource">logback-access.xml</Set>
+ *       </New>
+ *     </Set>
+ *   </Configure>
+ * 
*

+ *

+ * Now you'll need a {@code ${jetty.base}/resources/logback-access.xml} configuration file. + *

+ * By default, {@code RequestLogImpl} looks for a logback configuration file called + * {@code etc/logback-access.xml}, in the {@code ${jetty.base}} directory, then + * the older {@code ${jetty.home}} directory. + * The {@code logback-access.xml} file is slightly + * different than the usual logback classic configuration file. Most of it is + * the same: Appenders and Layouts are declared the exact same way. However, + * loggers elements are not allowed.

It is possible to put the logback + * configuration file anywhere, as long as it's path is specified. Here is + * another example, with a path to the logback-access.xml file. + *

+ *

+ *   <?xml version="1.0"?>
+ *   <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
+ *
+ *   <Configure id="Server" class="org.eclipse.jetty.server.Server">
+ *     <Set name="requestLog">
+ *       <New id="LogbackAccess" class="ch.qos.logback.access.jetty.RequestLogImpl">
+ *         <Set name="fileName">/path/to/logback-access.xml</Set>
+ *       </New>
+ *     </Set>
+ *   </Configure>
+ * 
+ *

Configuring for Jetty 7.x thru to Jetty 9.3.x

+ *

+ * To configure these older Jetty instances to use {@code RequestLogImpl}, + * the use of the {@code RequestLogHandler} is the technique available to you. + * Modify your {@code etc/jetty-requestlog.xml} + *

*
- *    <Ref id="requestLog">
- *      <Set name="requestLog">
- *        <New id="requestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"></New>
- *      </Set>
- *    </Ref>
+ *   <?xml version="1.0"?>
+ *   <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+ *
+ *   <Configure id="Server" class="org.eclipse.jetty.server.Server">
+ *     <Ref id="Handlers">
+ *       <Call name="addHandler">
+ *         <Arg>
+ *           <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+ *             <Set name="requestLog">
+ *               <New id="RequestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"/>
+ *             </Set>
+ *           </New>
+ *         </Arg>
+ *       </Call>
+ *     </Ref>
+ *   </Configure>
  * 
*

* By default, RequestLogImpl looks for a logback configuration file called @@ -70,12 +154,24 @@ * another example, with a path to the logback-access.xml file. *

*

- *    <Ref id="requestLog">
- *      <Set name="requestLog">
- *        <New id="requestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"></New>
- *          <Set name="fileName">path/to/logback.xml</Set>
- *      </Set>
- *    </Ref>
+ *   <?xml version="1.0"?>
+ *   <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+ *
+ *   <Configure id="Server" class="org.eclipse.jetty.server.Server">
+ *     <Ref id="Handlers">
+ *       <Call name="addHandler">
+ *         <Arg>
+ *           <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+ *             <Set name="requestLog">
+ *               <New id="RequestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl">
+ *                 <Set name="fileName">path/to/logback-access.xml</Set>
+ *               </New>
+ *             </Set>
+ *           </New>
+ *         </Arg>
+ *       </Call>
+ *     </Ref>
+ *   </Configure>
  * 
*

*

Here is a sample logback-access.xml file that can be used right away: @@ -112,24 +208,49 @@ * @author Ceki Gülcü * @author Sébastien Pennec */ -public class RequestLogImpl extends ContextBase implements RequestLog, AppenderAttachable, FilterAttachable { - +public class RequestLogImpl extends ContextBase implements RequestLog, LifeCycle, AppenderAttachable, FilterAttachable { public final static String DEFAULT_CONFIG_FILE = "etc" + File.separatorChar + "logback-access.xml"; + enum State { + FAILED, STOPPED, STARTING, STARTED, STOPPING + } + State state = State.STOPPED; + AppenderAttachableImpl aai = new AppenderAttachableImpl(); FilterAttachableImpl fai = new FilterAttachableImpl(); String fileName; String resource; - boolean started = false; + + // Jetty 9.4.x and newer is considered modern. + boolean modernJettyRequestLog; boolean quiet = false; public RequestLogImpl() { putObject(CoreConstants.EVALUATOR_MAP, new HashMap>()); + + // plumb the depths of Jetty and the environment ... + if (classIsPresent("jakarta.servlet.http.HttpServlet")) { + throw new RuntimeException("The new jakarta.servlet classes are not supported by this " + + "version of logback-access (check for a newer version of logback-access that " + + "does support it)"); + } + + // look for modern approach to RequestLog + modernJettyRequestLog = classIsPresent("org.eclipse.jetty.server.CustomRequestLog"); + } + + private boolean classIsPresent(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } } @Override public void log(Request jettyRequest, Response jettyResponse) { - JettyServerAdapter adapter = new JettyServerAdapter(jettyRequest, jettyResponse); + JettyServerAdapter adapter = newJettyServerAdapter(jettyRequest, jettyResponse); IAccessEvent accessEvent = new AccessEvent(jettyRequest, jettyResponse, adapter); if (getFilterChainDecision(accessEvent) == FilterReply.DENY) { return; @@ -137,7 +258,15 @@ public void log(Request jettyRequest, Response jettyResponse) { aai.appendLoopOnAppenders(accessEvent); } - private void addInfo(String msg) { + private JettyServerAdapter newJettyServerAdapter(Request jettyRequest, Response jettyResponse) { + if (modernJettyRequestLog) { + return new JettyModernServerAdapter(jettyRequest, jettyResponse); + } else { + return new JettyServerAdapter(jettyRequest, jettyResponse); + } + } + + protected void addInfo(String msg) { getStatusManager().add(new InfoStatus(msg, this)); } @@ -147,11 +276,17 @@ private void addError(String msg) { @Override public void start() { - configure(); - if (!isQuiet()) { - StatusPrinter.print(getStatusManager()); + state = State.STARTING; + try { + configure(); + if (!isQuiet()) { + StatusPrinter.print(getStatusManager()); + } + state = State.STARTED; + } catch(Throwable t) { + t.printStackTrace(); + state = State.FAILED; } - started = true; } protected void configure() { @@ -176,14 +311,25 @@ protected URL getConfigurationFileURL() { return this.getClass().getResource(resource); } - String jettyHomeProperty = OptionHelper.getSystemProperty("jetty.home"); String defaultConfigFile = DEFAULT_CONFIG_FILE; - if (!OptionHelper.isEmpty(jettyHomeProperty)) { - defaultConfigFile = jettyHomeProperty + File.separatorChar + DEFAULT_CONFIG_FILE; - } else { - addInfo("[jetty.home] system property not set."); + // Always attempt ${jetty.base} first + String jettyBaseProperty = OptionHelper.getSystemProperty("jetty.base"); + if (!OptionHelper.isEmpty(jettyBaseProperty)) { + defaultConfigFile = jettyBaseProperty + File.separatorChar + DEFAULT_CONFIG_FILE; } + File file = new File(defaultConfigFile); + if(!file.exists()) { + // Then use ${jetty.home} (not supported in Jetty 10+) + String jettyHomeProperty = OptionHelper.getSystemProperty("jetty.home"); + if (!OptionHelper.isEmpty(jettyHomeProperty)) { + defaultConfigFile = jettyHomeProperty + File.separatorChar + DEFAULT_CONFIG_FILE; + } else { + addInfo("Neither [jetty.base] nor [jetty.home] system properties are set."); + } + } + + file = new File(defaultConfigFile); addInfo("Assuming default configuration file [" + defaultConfigFile + "]"); if (!file.exists()) return null; @@ -205,13 +351,14 @@ private void runJoranOnFile(URL configURL) { @Override public void stop() { + state = State.STOPPING; aai.detachAndStopAllAppenders(); - started = false; + state = State.STOPPED; } @Override public boolean isRunning() { - return started; + return state == State.STARTED; } public void setFileName(String fileName) { @@ -224,27 +371,26 @@ public void setResource(String resource) { @Override public boolean isStarted() { - return started; + return state == State.STARTED; } @Override public boolean isStarting() { - return false; + return state == State.STARTING; } @Override public boolean isStopping() { - return false; + return state == State.STOPPING; } - @Override public boolean isStopped() { - return !started; + return state == State.STOPPED; } @Override public boolean isFailed() { - return false; + return state == State.FAILED; } public boolean isQuiet() { @@ -311,13 +457,12 @@ public FilterReply getFilterChainDecision(IAccessEvent event) { } @Override - public void addLifeCycleListener(Listener listener) { + public void addLifeCycleListener(LifeCycle.Listener listener) { // we'll implement this when asked } @Override - public void removeLifeCycleListener(Listener listener) { + public void removeLifeCycleListener(LifeCycle.Listener listener) { // we'll implement this when asked } - } diff --git a/logback-access/src/main/java/ch/qos/logback/access/spi/AccessEvent.java b/logback-access/src/main/java/ch/qos/logback/access/spi/AccessEvent.java index ffe73383ae..f40cb5ce5f 100755 --- a/logback-access/src/main/java/ch/qos/logback/access/spi/AccessEvent.java +++ b/logback-access/src/main/java/ch/qos/logback/access/spi/AccessEvent.java @@ -335,13 +335,22 @@ public void buildRequestHeaderMap() { public void buildRequestParameterMap() { requestParameterMap = new HashMap(); - Enumeration e = httpRequest.getParameterNames(); - if (e == null) { - return; - } - while (e.hasMoreElements()) { - String key = e.nextElement(); - requestParameterMap.put(key, httpRequest.getParameterValues(key)); + try { + Enumeration e = httpRequest.getParameterNames(); + if (e == null) { + return; + } + while (e.hasMoreElements()) { + String key = e.nextElement(); + requestParameterMap.put(key, httpRequest.getParameterValues(key)); + } + } catch(Throwable t) { + // The use of HttpServletRequest.getParameterNames() can cause + // a READ of the Request body content. This can fail with various + // Throwable failures depending on the state of the Request + // at the time this method is called. + // We don't want to fail the logging due to these types of requests + t.printStackTrace(); } } @@ -479,12 +488,12 @@ public String getRequestContent() { if (Util.isFormUrlEncoded(httpRequest)) { StringBuilder buf = new StringBuilder(); - Enumeration pramEnumeration = httpRequest.getParameterNames(); - - // example: id=1234&user=cgu - // number=1233&x=1 - int count = 0; try { + Enumeration pramEnumeration = httpRequest.getParameterNames(); + + // example: id=1234&user=cgu + // number=1233&x=1 + int count = 0; while (pramEnumeration.hasMoreElements()) { String key = pramEnumeration.nextElement(); @@ -500,9 +509,14 @@ public String getRequestContent() { buf.append(""); } } - } catch (Exception e) { - // FIXME Why is try/catch required? - e.printStackTrace(); + } catch (Throwable t) { + // The use of HttpServletRequest.getParameterNames() and + // HttpServletRequest.getParameter(String) can cause + // a READ of the Request body content. This can fail with various + // Throwable failures depending on the state of the Request + // at the time this method is called. + // We don't want to fail the logging due to these types of requests + t.printStackTrace(); } requestContent = buf.toString(); } else { diff --git a/logback-access/src/test/java/ch/qos/logback/access/jetty/JettyFixtureBase.java b/logback-access/src/test/java/ch/qos/logback/access/jetty/JettyFixtureBase.java index e461c5842e..eaef6b546c 100644 --- a/logback-access/src/test/java/ch/qos/logback/access/jetty/JettyFixtureBase.java +++ b/logback-access/src/test/java/ch/qos/logback/access/jetty/JettyFixtureBase.java @@ -13,21 +13,16 @@ */ package ch.qos.logback.access.jetty; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.server.handler.RequestLogHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.util.ByteArrayISO8859Writer; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.OutputStream; +import java.io.Writer; public class JettyFixtureBase { final protected RequestLogImpl requestLogImpl; @@ -51,20 +46,12 @@ public String getUrl() { } public void start() throws Exception { - server = new Server(); - Connector connector = new SelectChannelConnector(); - connector.setPort(port); - server.setConnectors(new Connector[] { connector }); + server = new Server(port); - RequestLogHandler requestLogHandler = new RequestLogHandler(); + server.setRequestLog(requestLogImpl); configureRequestLogImpl(); - requestLogHandler.setRequestLog(requestLogImpl); - HandlerList handlers = new HandlerList(); - handlers.addHandler(requestLogHandler); - handlers.addHandler(getRequestHandler()); - - server.setHandler(handlers); + server.setHandler(getRequestHandler()); server.start(); } @@ -83,16 +70,12 @@ protected Handler getRequestHandler() { class BasicHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - OutputStream out = response.getOutputStream(); - ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(); + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/plain"); + Writer writer = response.getWriter(); writer.write("hello world"); writer.flush(); - response.setContentLength(writer.size()); - writer.writeTo(out); - out.flush(); - baseRequest.setHandled(true); - } } } diff --git a/pom.xml b/pom.xml index a72cd73aab..590bb61921 100755 --- a/pom.xml +++ b/pom.xml @@ -62,9 +62,7 @@ 1.1.0 8.5.9 - - 8.2.0.v20160908 - + 9.4.44.v20210927 1.9 3.8.1 2.19.1