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
This commit is contained in:
Andy Wilkinson 2016-08-22 11:57:51 +01:00
parent 81275887e4
commit 98d81110f2
4 changed files with 185 additions and 1 deletions

View File

@ -307,6 +307,22 @@
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>

View File

@ -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<T extends EmbeddedServletContainerFactory>
implements EmbeddedServletContainerCustomizer, Ordered {
private final Class<T> factoryClass;
AccessLogCustomizer(Class<T> 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<TomcatEmbeddedServletContainerFactory> {
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<UndertowEmbeddedServletContainerFactory> {
UndertowAccessLogCustomizer() {
super(UndertowEmbeddedServletContainerFactory.class);
}
@Override
public void customize(UndertowEmbeddedServletContainerFactory container) {
container.setAccessLogPrefix(customizePrefix(container.getAccessLogPrefix()));
}
}
}

View File

@ -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<? extends MvcEndpoint> 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 {

View File

@ -568,6 +568,10 @@ public class UndertowEmbeddedServletContainerFactory
this.accessLogPattern = accessLogPattern;
}
public String getAccessLogPrefix() {
return this.accessLogPrefix;
}
public void setAccessLogPrefix(String accessLogPrefix) {
this.accessLogPrefix = accessLogPrefix;
}