From f5db75792eb5ff4792b3909d4734e7695103b663 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 27 Apr 2017 10:41:39 +0200 Subject: [PATCH] Add support for configuring Jetty's request log via the environment Closes gh-8819 --- .../autoconfigure/web/ServerProperties.java | 137 ++++++++++-------- ...aultServletWebServerFactoryCustomizer.java | 32 ++-- .../web/ServerPropertiesTests.java | 10 +- ...ervletWebServerFactoryCustomizerTests.java | 70 +++++++++ .../appendix-application-properties.adoc | 26 ++-- spring-boot-docs/src/main/asciidoc/howto.adoc | 12 +- 6 files changed, 186 insertions(+), 101 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index da5b7dc977..8db78f9923 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -23,18 +23,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; - -import org.eclipse.jetty.server.AbstractConnector; -import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.NCSARequestLog; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.HandlerWrapper; +import java.util.TimeZone; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; @@ -58,6 +50,7 @@ import org.springframework.util.StringUtils; * @author Venil Noronha * @author Aurélien Leboulanger * @author Brian Clozel + * @author Olivier Lamy */ @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties { @@ -895,6 +888,11 @@ public class ServerProperties { */ public static class Jetty { + /** + * Access log configuration. + */ + private final Accesslog accesslog = new Accesslog(); + /** * Maximum size in bytes of the HTTP post or put content. */ @@ -910,7 +908,9 @@ public class ServerProperties { */ private Integer selectors; - private final Accesslog accesslog = new Accesslog(); + public Accesslog getAccesslog() { + return this.accesslog; + } public int getMaxHttpPostSize() { return this.maxHttpPostSize; @@ -936,67 +936,70 @@ public class ServerProperties { this.selectors = selectors; } - public Accesslog getAccesslog() { - return this.accesslog; - } - + /** + * Jetty access log properties. + */ public static class Accesslog { /** * Enable access log. */ - private boolean enabled; + private boolean enabled = false; /** - * accesslog filename. If no filename, logs will be redirected to System.err + * Log filename. If not specified, logs will be redirected to "System.err". */ private String filename; /** - * number of days before rotated log files are deleted. Default 31 + * Date format to place in log file name. */ - private int retainDays = 31; + private String fileDateFormat; /** - * append to log. + * Number of days before rotated log files are deleted. + */ + private int retentionPeriod = 31; // no days + + /** + * Append to log. */ private boolean append; /** - * the log file name date format. + * Enable extended NCSA format. */ - private String filenameDateFormat; + private boolean extendedFormat; /** - * extended NCSA format. + * Timestamp format of the request log. */ - private boolean extended; + private String dateFormat = "dd/MMM/yyyy:HH:mm:ss Z"; /** - * Set the timezone of the request log. + * Locale of the request log. */ - private String logTimeZone; + private Locale locale; /** - * Controls logging of the request cookies. + * Timezone of the request log. */ - private boolean logCookies; + private TimeZone timeZone = TimeZone.getTimeZone("GMT"); /** - * Controls logging of the request hostname. + * Enable logging of the request cookies. */ - private boolean logServer; + private boolean logCookies; /** - * Controls logging of request processing time. + * Enable logging of the request hostname. */ - private boolean logLatency; + private boolean logServer; /** - * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request - * timestamp is used. + * Enable logging of request processing time. */ - private String logDateFormat; + private boolean logLatency; public boolean isEnabled() { return this.enabled; @@ -1014,12 +1017,20 @@ public class ServerProperties { this.filename = filename; } - public int getRetainDays() { - return this.retainDays; + public String getFileDateFormat() { + return this.fileDateFormat; + } + + public void setFileDateFormat(String fileDateFormat) { + this.fileDateFormat = fileDateFormat; } - public void setRetainDays(int retainDays) { - this.retainDays = retainDays; + public int getRetentionPeriod() { + return this.retentionPeriod; + } + + public void setRetentionPeriod(int retentionPeriod) { + this.retentionPeriod = retentionPeriod; } public boolean isAppend() { @@ -1030,28 +1041,36 @@ public class ServerProperties { this.append = append; } - public String getFilenameDateFormat() { - return this.filenameDateFormat; + public boolean isExtendedFormat() { + return this.extendedFormat; + } + + public void setExtendedFormat(boolean extendedFormat) { + this.extendedFormat = extendedFormat; + } + + public String getDateFormat() { + return this.dateFormat; } - public void setFilenameDateFormat(String filenameDateFormat) { - this.filenameDateFormat = filenameDateFormat; + public void setDateFormat(String dateFormat) { + this.dateFormat = dateFormat; } - public boolean isExtended() { - return this.extended; + public Locale getLocale() { + return this.locale; } - public void setExtended(boolean extended) { - this.extended = extended; + public void setLocale(Locale locale) { + this.locale = locale; } - public String getLogTimeZone() { - return this.logTimeZone; + public TimeZone getTimeZone() { + return this.timeZone; } - public void setLogTimeZone(String logTimeZone) { - this.logTimeZone = logTimeZone; + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; } public boolean isLogCookies() { @@ -1063,28 +1082,20 @@ public class ServerProperties { } public boolean isLogServer() { - return logServer; + return this.logServer; } - public void setLogServer( boolean logServer ) { + public void setLogServer(boolean logServer) { this.logServer = logServer; } public boolean isLogLatency() { - return logLatency; + return this.logLatency; } - public void setLogLatency( boolean logLatency ) { + public void setLogLatency(boolean logLatency) { this.logLatency = logLatency; } - - public String getLogDateFormat() { - return logDateFormat; - } - - public void setLogDateFormat( String logDateFormat ) { - this.logDateFormat = logDateFormat; - } } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java index bdfe285768..12c660b3a8 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizer.java @@ -67,6 +67,7 @@ import org.springframework.util.StringUtils; * * @author Brian Clozel * @author Stephane Nicoll + * @author Olivier Lamy * @since 2.0.0 */ public class DefaultServletWebServerFactoryCustomizer @@ -643,27 +644,30 @@ public class DefaultServletWebServerFactoryCustomizer } private static void customizeAccesslog(JettyServletWebServerFactory factory, - final ServerProperties.Jetty.Accesslog accesslog) { - factory.addServerCustomizers( server -> { + final ServerProperties.Jetty.Accesslog accesslog) { + factory.addServerCustomizers(server -> { NCSARequestLog requestLog = new NCSARequestLog(); if (accesslog.getFilename() != null) { requestLog.setFilename(accesslog.getFilename()); } - if (accesslog.getFilenameDateFormat() != null) { - requestLog.setFilenameDateFormat(accesslog.getFilenameDateFormat()); + if (accesslog.getFileDateFormat() != null) { + requestLog.setFilenameDateFormat(accesslog.getFileDateFormat()); } - if (accesslog.getRetainDays() > 0) { - requestLog.setRetainDays(accesslog.getRetainDays()); - } - requestLog.setLogTimeZone(accesslog.getLogTimeZone()); - requestLog.setExtended(accesslog.isExtended()); + requestLog.setRetainDays(accesslog.getRetentionPeriod()); requestLog.setAppend(accesslog.isAppend()); - requestLog.setLogCookies(accesslog.isLogCookies()); - requestLog.setLogServer( accesslog.isLogServer() ); - requestLog.setLogLatency( accesslog.isLogLatency() ); - if (!StringUtils.isEmpty( accesslog.getLogDateFormat() )) { - requestLog.setLogDateFormat( accesslog.getLogDateFormat() ); + requestLog.setExtended(accesslog.isExtendedFormat()); + if (accesslog.getDateFormat() != null) { + requestLog.setLogDateFormat(accesslog.getDateFormat()); + } + if (accesslog.getLocale() != null) { + requestLog.setLogLocale(accesslog.getLocale()); } + if (accesslog.getTimeZone() != null) { + requestLog.setLogTimeZone(accesslog.getTimeZone().getID()); + } + requestLog.setLogCookies(accesslog.isLogCookies()); + requestLog.setLogServer(accesslog.isLogServer()); + requestLog.setLogLatency(accesslog.isLogLatency()); server.setRequestLog(requestLog); }); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java index a9f81860ab..849d91127c 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java @@ -185,20 +185,20 @@ public class ServerPropertiesTests { } @Test - public void testJettyBinding() throws Exception { + public void testCustomizeJettyAccessLog() throws Exception { Map map = new HashMap(); map.put("server.jetty.accesslog.enabled", "true"); map.put("server.jetty.accesslog.filename", "foo.txt"); - map.put("server.jetty.accesslog.retainDays", "4"); + map.put("server.jetty.accesslog.file-date-format", "yyyymmdd"); + map.put("server.jetty.accesslog.retention-period", "4"); map.put("server.jetty.accesslog.append", "true"); - map.put("server.jetty.accesslog.filenameDateFormat", "yyyymmdd"); bindProperties(map); ServerProperties.Jetty jetty = this.properties.getJetty(); assertThat(jetty.getAccesslog().isEnabled()).isEqualTo(true); assertThat(jetty.getAccesslog().getFilename()).isEqualTo("foo.txt"); - assertThat(jetty.getAccesslog().getRetainDays()).isEqualTo(4); + assertThat(jetty.getAccesslog().getFileDateFormat()).isEqualTo("yyyymmdd"); + assertThat(jetty.getAccesslog().getRetentionPeriod()).isEqualTo(4); assertThat(jetty.getAccesslog().isAppend()).isEqualTo(true); - assertThat(jetty.getAccesslog().getFilenameDateFormat()).isEqualTo("yyyymmdd"); } private void bindProperties(Map map) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java index a8e119f043..c9c4eb9456 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/DefaultServletWebServerFactoryCustomizerTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.net.URL; import java.util.EnumSet; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import javax.servlet.ServletContext; @@ -33,6 +34,8 @@ import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; import org.apache.coyote.AbstractProtocol; +import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.RequestLog; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -45,6 +48,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; +import org.springframework.boot.web.embedded.jetty.JettyWebServer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; @@ -527,6 +531,72 @@ public class DefaultServletWebServerFactoryCustomizerTests { verify(factory).setSessionStoreDir(new File("myfolder")); } + @Test + public void jettyAccessLogCanBeEnabled() { + JettyServletWebServerFactory factory = new JettyServletWebServerFactory(0); + Map map = new HashMap<>(); + map.put("server.jetty.accesslog.enabled", "true"); + bindProperties(map); + this.customizer.customize(factory); + JettyWebServer webServer = (JettyWebServer) factory.getWebServer(); + try { + NCSARequestLog requestLog = getNCSARequestLog(webServer); + assertThat(requestLog.getFilename()).isNull(); + assertThat(requestLog.isAppend()).isFalse(); + assertThat(requestLog.isExtended()).isFalse(); + assertThat(requestLog.getLogCookies()).isFalse(); + assertThat(requestLog.getLogServer()).isFalse(); + assertThat(requestLog.getLogLatency()).isFalse(); + } + finally { + webServer.stop(); + } + } + + @Test + public void jettyAccessLogCanBeCustomized() { + JettyServletWebServerFactory factory = new JettyServletWebServerFactory(0); + Map map = new HashMap<>(); + map.put("server.jetty.accesslog.enabled", "true"); + map.put("server.jetty.accesslog.filename", "foo"); + map.put("server.jetty.accesslog.file-date-format", "yyyy-MM-dd"); + map.put("server.jetty.accesslog.retention-period", "42"); + map.put("server.jetty.accesslog.append", "true"); + map.put("server.jetty.accesslog.extended-format", "true"); + map.put("server.jetty.accesslog.date-format", "HH:mm:ss"); + map.put("server.jetty.accesslog.locale", "en_BE"); + map.put("server.jetty.accesslog.time-zone", "UTC"); + map.put("server.jetty.accesslog.log-cookies", "true"); + map.put("server.jetty.accesslog.log-server", "true"); + map.put("server.jetty.accesslog.log-latency", "true"); + bindProperties(map); + this.customizer.customize(factory); + JettyWebServer webServer = (JettyWebServer) factory.getWebServer(); + NCSARequestLog requestLog = getNCSARequestLog(webServer); + try { + assertThat(requestLog.getFilename()).isEqualTo("foo"); + assertThat(requestLog.getFilenameDateFormat()).isEqualTo("yyyy-MM-dd"); + assertThat(requestLog.getRetainDays()).isEqualTo(42); + assertThat(requestLog.isAppend()).isTrue(); + assertThat(requestLog.isExtended()).isTrue(); + assertThat(requestLog.getLogDateFormat()).isEqualTo("HH:mm:ss"); + assertThat(requestLog.getLogLocale()).isEqualTo(new Locale("en", "BE")); + assertThat(requestLog.getLogTimeZone()).isEqualTo("UTC"); + assertThat(requestLog.getLogCookies()).isTrue(); + assertThat(requestLog.getLogServer()).isTrue(); + assertThat(requestLog.getLogLatency()).isTrue(); + } + finally { + webServer.stop(); + } + } + + private NCSARequestLog getNCSARequestLog(JettyWebServer webServer) { + RequestLog requestLog = webServer.getServer().getRequestLog(); + assertThat(requestLog).isInstanceOf(NCSARequestLog.class); + return (NCSARequestLog) requestLog; + } + @Test public void skipNullElementsForUndertow() throws Exception { UndertowServletWebServerFactory factory = mock( diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 441bdc5e56..fb79a5db1f 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -157,22 +157,20 @@ content into your application; rather pick only the properties that you need. server.error.path=/error # Path of the error controller. server.error.whitelabel.enabled=true # Enable the default error page displayed in browsers in case of a server error. server.jetty.acceptors= # Number of acceptor threads to use. + server.jetty.accesslog.append=false # Append to log. + server.jetty.accesslog.date-format=dd/MMM/yyyy:HH:mm:ss Z # Timestamp format of the request log. + server.jetty.accesslog.enabled=false # Enable access log. + server.jetty.accesslog.extended-format=false # Enable extended NCSA format. + server.jetty.accesslog.file-date-format= # Date format to place in log file name. + server.jetty.accesslog.filename= # Log filename. If not specified, logs will be redirected to "System.err". + server.jetty.accesslog.locale= # Locale of the request log. + server.jetty.accesslog.log-cookies=false # Enable logging of the request cookies. + server.jetty.accesslog.log-latency=false # Enable logging of request processing time. + server.jetty.accesslog.log-server=false # Enable logging of the request hostname. + server.jetty.accesslog.retention-period=31 # Number of days before rotated log files are deleted. + server.jetty.accesslog.time-zone=GMT # Timezone of the request log. server.jetty.max-http-post-size=0 # Maximum size in bytes of the HTTP post or put content. server.jetty.selectors= # Number of selector threads to use. - server.jetty.accesslog.enabled=false # Enable access log. - server.jetty.accesslog.filename= # Filename of Jetty access logs. - server.jetty.accesslog.retainDays=31 # number of days before rotated log files are deleted. - server.jetty.accesslog.append=true # append to log flag. - server.jetty.accesslog.filenameDateFormat=yyyy_MM_dd # log file name date format. - server.jetty.accesslog.extended= true # extended request log format flag. - server.jetty.accesslog.logTimeZone= GMT # the timezone of the request log - server.jetty.accesslog.logCookies=false # Controls logging of the request cookies. - server.jetty.accesslog.logServer=false # Controls logging of the request hostname. - server.jetty.accesslog.logLatency=false # Controls logging of request processing time. - server.jetty.accesslog.logDateFormat= # Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request timestamp is used. - server.jsp-servlet.class-name=org.apache.jasper.servlet.JspServlet # The class name of the JSP servlet. - server.jsp-servlet.init-parameters.*= # Init parameters used to configure the JSP servlet - server.jsp-servlet.registered=true # Whether or not the JSP servlet is registered server.port=8080 # Server HTTP port. server.server-header= # Value to use for the Server response header (no header is sent if empty) server.use-forward-headers= # If X-Forwarded-* headers should be applied to the HttpRequest. diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 8abf63694a..7de90bc27d 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -636,7 +636,8 @@ sample project for an example. [[howto-configure-accesslogs]] === Configure Access Logging -Access logs can be configured for Tomcat, Undertow and Jetty via their respective namespaces. +Access logs can be configured for Tomcat, Undertow and Jetty via their respective +namespaces. For instance, the following logs access on Tomcat with a https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Access_Logging[custom pattern]. @@ -664,16 +665,17 @@ Access logging for undertow can be configured in a similar fashion Logs are stored in a `logs` directory relative to the working directory of the application. This can be customized via `server.undertow.accesslog.directory`. -Access logging for jetty can be configured in a similar fashion +Finally, access logging for jetty can also be configured that way: [source,properties,indent=0,subs="verbatim,quotes,attributes"] ---- server.jetty.accesslog.enabled=true - server.jetty.accesslog.filename= + server.jetty.accesslog.filename=access-log ---- -If no filename logs will go to System.err. -For more details please refer to https://www.eclipse.org/jetty/documentation/9.3.x/configuring-jetty-request-logs.html. +By default, logs will be redirected to `System.err`. For more details, please refer to +https://www.eclipse.org/jetty/documentation/9.3.x/configuring-jetty-request-logs.html[the documentation]. + [[howto-use-behind-a-proxy-server]]