spring-boot
spring-boot copied to clipboard
Allow specifying a different management access log prefix
Currently access logs created by the management server (when the management server is run on a different port) automatically get the prefix "management_" prepended (implemented in org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementChildContextConfiguration
).
For configurations of access logs which write to stdout (see https://github.com/ImmobilienScout24/tomcat-stdout-accesslog/issues/2 for an example on how to configure that in tomcat) it would be useful to be able to drop this prefix.
There might also be people interested in configuring this static prefix.
Please expose the prefix as a property and thus make it configurable by end users.
Note: as a workaround I tried deactivating the customizer by overriding the Bean like this:
@ManagementContextConfiguration(ManagementContextType.CHILD)
class ServletManagementChildContextConfiguration {
@Bean
@Primary
@ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve")
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatAccessLogCustomizer() {
return server -> {
};
}
}
This however turned out to not work, any hints on a possible workaround? I then tried to run the same functionality as implemented in ServletManagementChildContextConfiguration but that ended up changing the valve in my main context. Any hints for me to understand this behaviour?
For a workaround you could your write your own WebServerFactoryCustomizer
ordered above ours that removes the default AccessLogValve
from getEngineValves
and replaces it with subclass that overrides setPrefix
but doesn't call super
.
workaround code that works for us
/**
* Add this class to /META-INF/spring.factories under the {@code org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration}
*/
@ManagementContextConfiguration(value = ManagementContextType.CHILD)
public class MyAppManagementConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> managementServerustomizer() {
return factory -> {
AccessLogValve accessLogValve = findAccessLogValve(factory);
if ((accessLogValve != null) && "management_stdout".equals(accessLogValve.getPrefix())) {
accessLogValve.setPrefix("stdout");
}
};
}
private static AccessLogValve findAccessLogValve(TomcatServletWebServerFactory factory) {
return (AccessLogValve) factory.getEngineValves().stream()
.filter(AccessLogValve.class::isInstance)
.findFirst().orElse(null);
}
}
Hi! Was there any progress with this feature?
Note for the workaround:
Since spring-boot 2.7, the ManagementConfig class needs to be specified in this file: /META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports
, according to the documentation and release notes.
Also confirming I have this issue while running in kubernetes to set up actuator health and liveness probes on a non-standard port. (Web servers can use 8080 by default and we dont want to expose metrics through the webserver)
I also did some research on this behalf, but nothing really worked. The solution https://github.com/spring-projects/spring-boot/issues/14948#issuecomment-1120117801 did only work for the normal web server but not for management server (debugged it).
So I came along to patch the class org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementChildContextConfiguration
to statically set the access log prefix to stdout
:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.actuate.autoconfigure.web.servlet;
import jakarta.servlet.Filter;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.AccessLogValve;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.JettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.embedded.UndertowWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.TomcatServletWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.UndertowServletWebServerFactoryCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.util.StringUtils;
import java.util.Iterator;
/**
* Patched by Adi Wehrli, 03-MAY-2023: change access log prefix to "" because of errors during startup due to the fact that the
* management server port is not the same as the web server port.
*/
@ManagementContextConfiguration(
value = ManagementContextType.CHILD,
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
class ServletManagementChildContextConfiguration {
ServletManagementChildContextConfiguration() {
}
@Bean
ServletManagementWebServerFactoryCustomizer servletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) {
return new ServletManagementWebServerFactoryCustomizer(beanFactory);
}
@Bean
@ConditionalOnClass(
name = {"org.apache.catalina.valves.AccessLogValve"}
)
TomcatAccessLogCustomizer tomcatManagementAccessLogCustomizer() {
return new TomcatAccessLogCustomizer();
}
static class ServletManagementWebServerFactoryCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
ServletManagementWebServerFactoryCustomizer(ListableBeanFactory beanFactory) {
super(beanFactory, new Class[]{ServletWebServerFactoryCustomizer.class, TomcatServletWebServerFactoryCustomizer.class, TomcatWebServerFactoryCustomizer.class, JettyWebServerFactoryCustomizer.class, UndertowServletWebServerFactoryCustomizer.class, UndertowWebServerFactoryCustomizer.class});
}
protected void customize(ConfigurableServletWebServerFactory webServerFactory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(webServerFactory, managementServerProperties, serverProperties);
webServerFactory.setContextPath(this.getContextPath(managementServerProperties));
}
private String getContextPath(ManagementServerProperties managementServerProperties) {
String basePath = managementServerProperties.getBasePath();
return StringUtils.hasText(basePath) ? basePath : "";
}
}
static class TomcatAccessLogCustomizer extends AccessLogCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
TomcatAccessLogCustomizer() {
}
public void customize(TomcatServletWebServerFactory factory) {
AccessLogValve accessLogValve = this.findAccessLogValve(factory);
if (accessLogValve != null) {
accessLogValve.setPrefix(this.customizePrefix(accessLogValve.getPrefix()));
}
}
private AccessLogValve findAccessLogValve(TomcatServletWebServerFactory factory) {
Iterator engineValves = factory.getEngineValves().iterator();
Valve engineValve;
do {
if (!engineValves.hasNext()) {
return null;
}
engineValve = (Valve)engineValves.next();
} while(!(engineValve instanceof AccessLogValve));
AccessLogValve accessLogValve = (AccessLogValve)engineValve;
return accessLogValve;
}
}
abstract static class AccessLogCustomizer implements Ordered {
AccessLogCustomizer() {
}
protected String customizePrefix(String prefix) {
return "stdout";
}
public int getOrder() {
return 1;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({EnableWebSecurity.class, Filter.class})
@ConditionalOnBean(
name = {"springSecurityFilterChain"},
search = SearchStrategy.ANCESTORS
)
static class ServletManagementContextSecurityConfiguration {
ServletManagementContextSecurityConfiguration() {
}
@Bean
Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
BeanFactory parent = beanFactory.getParentBeanFactory();
return (Filter)parent.getBean("springSecurityFilterChain", Filter.class);
}
@Bean
@ConditionalOnBean(
name = {"securityFilterChainRegistration"},
search = SearchStrategy.ANCESTORS
)
DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(HierarchicalBeanFactory beanFactory) {
return (DelegatingFilterProxyRegistrationBean)beanFactory.getParentBeanFactory().getBean("securityFilterChainRegistration", DelegatingFilterProxyRegistrationBean.class);
}
}
}
I also removed the Undertow and Jetty parts as I do not need them.
Hello, Has someone fix this ?
As https://github.com/spring-projects/spring-boot/issues/14948#issuecomment-1532826032 mention it, https://github.com/spring-projects/spring-boot/issues/14948#issuecomment-1120117801 do not work with management server on another port.
But I don't want to trick springboot engine...