Apply mgmt access log prefix to reactive Jetty, Tomcat, and Undertow

Fixes gh-44197
This commit is contained in:
Andy Wilkinson
2025-02-10 14:53:35 +00:00
parent 436b51c065
commit 62b217e0da
3 changed files with 174 additions and 2 deletions

View File

@@ -16,19 +16,33 @@
package org.springframework.boot.actuate.autoconfigure.web.reactive;
import java.io.File;
import java.util.Collections;
import java.util.Map;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.AccessLogValve;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.RequestLogWriter;
import org.eclipse.jetty.server.Server;
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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ContextPathCompositeHandler;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.StringUtils;
@@ -65,4 +79,125 @@ public class ReactiveManagementChildContextConfiguration {
return httpHandler;
}
@Bean
@ConditionalOnClass(name = "io.undertow.Undertow")
UndertowAccessLogCustomizer undertowManagementAccessLogCustomizer(ManagementServerProperties properties) {
return new UndertowAccessLogCustomizer(properties);
}
@Bean
@ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve")
TomcatAccessLogCustomizer tomcatManagementAccessLogCustomizer(ManagementServerProperties properties) {
return new TomcatAccessLogCustomizer(properties);
}
@Bean
@ConditionalOnClass(name = "org.eclipse.jetty.server.Server")
JettyAccessLogCustomizer jettyManagementAccessLogCustomizer(ManagementServerProperties properties) {
return new JettyAccessLogCustomizer(properties);
}
abstract static class AccessLogCustomizer implements Ordered {
private final ManagementServerProperties properties;
AccessLogCustomizer(ManagementServerProperties properties) {
this.properties = properties;
}
protected String customizePrefix(String prefix) {
prefix = (prefix != null) ? prefix : "";
if (prefix.startsWith(this.properties.getAccesslog().getPrefix())) {
return prefix;
}
return this.properties.getAccesslog().getPrefix() + prefix;
}
@Override
public int getOrder() {
return 1;
}
}
static class TomcatAccessLogCustomizer extends AccessLogCustomizer
implements WebServerFactoryCustomizer<TomcatReactiveWebServerFactory> {
TomcatAccessLogCustomizer(ManagementServerProperties properties) {
super(properties);
}
@Override
public void customize(TomcatReactiveWebServerFactory factory) {
System.out.println("Looking for access log valve in " + factory);
AccessLogValve accessLogValve = findAccessLogValve(factory);
if (accessLogValve == null) {
System.out.println("Did not find it");
return;
}
accessLogValve.setPrefix(customizePrefix(accessLogValve.getPrefix()));
System.out.println("Customized " + factory);
}
private AccessLogValve findAccessLogValve(TomcatReactiveWebServerFactory factory) {
for (Valve engineValve : factory.getEngineValves()) {
if (engineValve instanceof AccessLogValve accessLogValve) {
return accessLogValve;
}
}
return null;
}
}
static class UndertowAccessLogCustomizer extends AccessLogCustomizer
implements WebServerFactoryCustomizer<UndertowReactiveWebServerFactory> {
UndertowAccessLogCustomizer(ManagementServerProperties properties) {
super(properties);
}
@Override
public void customize(UndertowReactiveWebServerFactory factory) {
factory.setAccessLogPrefix(customizePrefix(factory.getAccessLogPrefix()));
}
}
static class JettyAccessLogCustomizer extends AccessLogCustomizer
implements WebServerFactoryCustomizer<JettyReactiveWebServerFactory> {
JettyAccessLogCustomizer(ManagementServerProperties properties) {
super(properties);
}
@Override
public void customize(JettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(this::customizeServer);
}
private void customizeServer(Server server) {
RequestLog requestLog = server.getRequestLog();
if (requestLog instanceof CustomRequestLog customRequestLog) {
customizeRequestLog(customRequestLog);
}
}
private void customizeRequestLog(CustomRequestLog requestLog) {
if (requestLog.getWriter() instanceof RequestLogWriter requestLogWriter) {
customizeRequestLogWriter(requestLogWriter);
}
}
private void customizeRequestLogWriter(RequestLogWriter writer) {
String filename = writer.getFileName();
if (StringUtils.hasLength(filename)) {
File file = new File(filename);
file = new File(file.getParentFile(), customizePrefix(file.getName()));
writer.setFilename(file.getPath());
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,13 @@ package org.springframework.boot.actuate.autoconfigure.web.reactive;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.apache.catalina.Valve;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.AccessLogValve;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -39,7 +44,11 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplic
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.http.MediaType;
@@ -55,6 +64,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class ReactiveManagementChildContextConfigurationIntegrationTests {
private final List<WebServer> webServers = new ArrayList<>();
private final ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(
AnnotationConfigReactiveWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class,
@@ -63,6 +74,8 @@ class ReactiveManagementChildContextConfigurationIntegrationTests {
WebFluxAutoConfiguration.class))
.withUserConfiguration(SucceedingEndpoint.class)
.withInitializer(new ServerPortInfoApplicationContextInitializer())
.withInitializer((context) -> context.addApplicationListener(
(ApplicationListener<WebServerInitializedEvent>) (event) -> this.webServers.add(event.getWebServer())))
.withPropertyValues("server.port=0", "management.server.port=0", "management.endpoints.web.exposure.include=*");
@TempDir
@@ -99,6 +112,26 @@ class ReactiveManagementChildContextConfigurationIntegrationTests {
.run((context) -> assertThat(context).hasNotFailed());
}
@Test
void accessLogHasManagementServerSpecificPrefix() {
this.runner.withPropertyValues("server.tomcat.accesslog.enabled=true").run((context) -> {
AccessLogValve accessLogValve = findAccessLogValve();
assertThat(accessLogValve).isNotNull();
assertThat(accessLogValve.getPrefix()).isEqualTo("management_access_log");
});
}
private AccessLogValve findAccessLogValve() {
assertThat(this.webServers).hasSize(2);
Tomcat tomcat = ((TomcatWebServer) this.webServers.get(1)).getTomcat();
for (Valve valve : tomcat.getEngine().getPipeline().getValves()) {
if (valve instanceof AccessLogValve accessLogValve) {
return accessLogValve;
}
}
return null;
}
private void addConfigTreePropertySource(ConfigurableApplicationContext applicationContext) {
try {
applicationContext.getEnvironment()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -112,6 +112,10 @@ public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerF
this.delegate.setAccessLogPattern(accessLogPattern);
}
public String getAccessLogPrefix() {
return this.delegate.getAccessLogPrefix();
}
@Override
public void setAccessLogPrefix(String accessLogPrefix) {
this.delegate.setAccessLogPrefix(accessLogPrefix);