From 98d81110f25693087d4d1a4ae291eb9ac1d85f27 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 22 Aug 2016 11:57:51 +0100 Subject: [PATCH] Write management and main server access logs to separate files Previously, when access logging was enabled and the management server was running on a separate port, both the main server and the management server would write their access logs to the same file. Having two separate containers writing to the same file could cause problems such as causing log rotation to break. This commit updates the actuator so that when the management server is running on a separate port (and therefore using a separate container) it prepends management_ to the access log prefix so that the main server and the management server write their access logs to separate files in the same directory. Closes gh-6618 --- spring-boot-actuator/pom.xml | 16 ++++ ...dpointWebMvcChildContextConfiguration.java | 92 +++++++++++++++++++ .../EndpointWebMvcAutoConfigurationTests.java | 74 ++++++++++++++- ...dertowEmbeddedServletContainerFactory.java | 4 + 4 files changed, 185 insertions(+), 1 deletion(-) diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 8863c7c4df..84bc130139 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -307,6 +307,22 @@ json-path test + + io.undertow + undertow-core + test + + + io.undertow + undertow-servlet + test + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.1_spec + + + org.aspectj aspectjrt diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index a61a32dceb..b4a8ef2998 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -24,6 +24,9 @@ import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.Valve; +import org.apache.catalina.valves.AccessLogValve; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.HierarchicalBeanFactory; @@ -43,6 +46,9 @@ import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -106,6 +112,17 @@ public class EndpointWebMvcChildContextConfiguration { return new ServerCustomization(); } + @Bean + public UndertowAccessLogCustomizer undertowAccessLogCustomizer() { + return new UndertowAccessLogCustomizer(); + } + + @Bean + @ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve") + public TomcatAccessLogCustomizer tomcatAccessLogCustomizer() { + return new TomcatAccessLogCustomizer(); + } + /* * The error controller is present but not mapped as an endpoint in this context * because of the DispatcherServlet having had its HandlerMapping explicitly disabled. @@ -321,4 +338,79 @@ public class EndpointWebMvcChildContextConfiguration { } + static abstract class AccessLogCustomizer + implements EmbeddedServletContainerCustomizer, Ordered { + + private final Class factoryClass; + + AccessLogCustomizer(Class factoryClass) { + this.factoryClass = factoryClass; + } + + protected String customizePrefix(String prefix) { + return "management_" + prefix; + } + + @Override + public int getOrder() { + return 1; + } + + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + if (this.factoryClass.isInstance(container)) { + customize(this.factoryClass.cast(container)); + } + } + + abstract void customize(T container); + } + + static class TomcatAccessLogCustomizer + extends AccessLogCustomizer { + + TomcatAccessLogCustomizer() { + super(TomcatEmbeddedServletContainerFactory.class); + } + + @Override + public int getOrder() { + return 1; + } + + @Override + public void customize(TomcatEmbeddedServletContainerFactory container) { + AccessLogValve accessLogValve = findAccessLogValve(container); + if (accessLogValve == null) { + return; + } + accessLogValve.setPrefix(customizePrefix(accessLogValve.getPrefix())); + } + + private AccessLogValve findAccessLogValve( + TomcatEmbeddedServletContainerFactory container) { + for (Valve engineValve : container.getEngineValves()) { + if (engineValve instanceof AccessLogValve) { + return (AccessLogValve) engineValve; + } + } + return null; + } + + } + + static class UndertowAccessLogCustomizer + extends AccessLogCustomizer { + + UndertowAccessLogCustomizer() { + super(UndertowEmbeddedServletContainerFactory.class); + } + + @Override + public void customize(UndertowEmbeddedServletContainerFactory container) { + container.setAccessLogPrefix(customizePrefix(container.getAccessLogPrefix())); + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 4ce12702fc..09a10ca285 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -29,6 +29,8 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.catalina.Valve; +import org.apache.catalina.valves.AccessLogValve; import org.apache.http.client.HttpClient; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; @@ -67,6 +69,7 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.testutil.Matched; @@ -429,7 +432,8 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.register(RootConfig.class, BaseConfiguration.class, ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); - // /health, /metrics, /env, /actuator, /heapdump (/shutdown is disabled by default) + // /health, /metrics, /env, /actuator, /heapdump (/shutdown is disabled by + // default) assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(5); } @@ -562,6 +566,54 @@ public class EndpointWebMvcAutoConfigurationTests { assertThat(managementServerProperties.getSsl().isEnabled()).isFalse(); } + @Test + public void tomcatManagementAccessLogUsesCustomPrefix() throws Exception { + this.applicationContext.register(TomcatContainerConfig.class, RootConfig.class, + EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, + EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.applicationContext, + "server.tomcat.accesslog.enabled: true"); + this.applicationContext.refresh(); + ApplicationContext managementContext = this.applicationContext + .getBean(ManagementContextResolver.class).getApplicationContext(); + EmbeddedServletContainerFactory servletContainerFactory = managementContext + .getBean(EmbeddedServletContainerFactory.class); + assertThat(servletContainerFactory) + .isInstanceOf(TomcatEmbeddedServletContainerFactory.class); + AccessLogValve accessLogValve = findAccessLogValve( + ((TomcatEmbeddedServletContainerFactory) servletContainerFactory)); + assertThat(accessLogValve).isNotNull(); + assertThat(accessLogValve.getPrefix()).isEqualTo("management_access_log"); + } + + @Test + public void undertowManagementAccessLogUsesCustomPrefix() throws Exception { + this.applicationContext.register(UndertowContainerConfig.class, RootConfig.class, + EndpointConfig.class, DifferentPortConfig.class, BaseConfiguration.class, + EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.applicationContext, + "server.undertow.accesslog.enabled: true"); + this.applicationContext.refresh(); + ApplicationContext managementContext = this.applicationContext + .getBean(ManagementContextResolver.class).getApplicationContext(); + EmbeddedServletContainerFactory servletContainerFactory = managementContext + .getBean(EmbeddedServletContainerFactory.class); + assertThat(servletContainerFactory) + .isInstanceOf(UndertowEmbeddedServletContainerFactory.class); + assertThat(((UndertowEmbeddedServletContainerFactory) servletContainerFactory) + .getAccessLogPrefix()).isEqualTo("management_access_log."); + } + + private AccessLogValve findAccessLogValve( + TomcatEmbeddedServletContainerFactory container) { + for (Valve engineValve : container.getEngineValves()) { + if (engineValve instanceof AccessLogValve) { + return (AccessLogValve) engineValve; + } + } + return null; + } + private void endpointDisabled(String name, Class type) { this.applicationContext.register(RootConfig.class, BaseConfiguration.class, ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); @@ -734,6 +786,26 @@ public class EndpointWebMvcAutoConfigurationTests { } + @Configuration + public static class TomcatContainerConfig { + + @Bean + public TomcatEmbeddedServletContainerFactory embeddedServletContainerFactory() { + return new TomcatEmbeddedServletContainerFactory(); + } + + } + + @Configuration + public static class UndertowContainerConfig { + + @Bean + public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() { + return new UndertowEmbeddedServletContainerFactory(); + } + + } + @Configuration @Import(ServerPortConfig.class) public static class DifferentPortConfig { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index e868383866..fd06f59390 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -568,6 +568,10 @@ public class UndertowEmbeddedServletContainerFactory this.accessLogPattern = accessLogPattern; } + public String getAccessLogPrefix() { + return this.accessLogPrefix; + } + public void setAccessLogPrefix(String accessLogPrefix) { this.accessLogPrefix = accessLogPrefix; }