Skip to content

Commit 62b217e

Browse files
committed
Apply mgmt access log prefix to reactive Jetty, Tomcat, and Undertow
Fixes gh-44197
1 parent 436b51c commit 62b217e

File tree

3 files changed

+174
-2
lines changed

3 files changed

+174
-2
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfiguration.java

+135
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,33 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.web.reactive;
1818

19+
import java.io.File;
1920
import java.util.Collections;
2021
import java.util.Map;
2122

23+
import org.apache.catalina.Valve;
24+
import org.apache.catalina.valves.AccessLogValve;
25+
import org.eclipse.jetty.server.CustomRequestLog;
26+
import org.eclipse.jetty.server.RequestLog;
27+
import org.eclipse.jetty.server.RequestLogWriter;
28+
import org.eclipse.jetty.server.Server;
29+
2230
import org.springframework.beans.factory.ListableBeanFactory;
2331
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2432
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
2533
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
2634
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementWebServerFactoryCustomizer;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2736
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2837
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
38+
import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory;
39+
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
40+
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
2941
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
42+
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
3043
import org.springframework.context.ApplicationContext;
3144
import org.springframework.context.annotation.Bean;
45+
import org.springframework.core.Ordered;
3246
import org.springframework.http.server.reactive.ContextPathCompositeHandler;
3347
import org.springframework.http.server.reactive.HttpHandler;
3448
import org.springframework.util.StringUtils;
@@ -65,4 +79,125 @@ public HttpHandler httpHandler(ApplicationContext applicationContext, Management
6579
return httpHandler;
6680
}
6781

82+
@Bean
83+
@ConditionalOnClass(name = "io.undertow.Undertow")
84+
UndertowAccessLogCustomizer undertowManagementAccessLogCustomizer(ManagementServerProperties properties) {
85+
return new UndertowAccessLogCustomizer(properties);
86+
}
87+
88+
@Bean
89+
@ConditionalOnClass(name = "org.apache.catalina.valves.AccessLogValve")
90+
TomcatAccessLogCustomizer tomcatManagementAccessLogCustomizer(ManagementServerProperties properties) {
91+
return new TomcatAccessLogCustomizer(properties);
92+
}
93+
94+
@Bean
95+
@ConditionalOnClass(name = "org.eclipse.jetty.server.Server")
96+
JettyAccessLogCustomizer jettyManagementAccessLogCustomizer(ManagementServerProperties properties) {
97+
return new JettyAccessLogCustomizer(properties);
98+
}
99+
100+
abstract static class AccessLogCustomizer implements Ordered {
101+
102+
private final ManagementServerProperties properties;
103+
104+
AccessLogCustomizer(ManagementServerProperties properties) {
105+
this.properties = properties;
106+
}
107+
108+
protected String customizePrefix(String prefix) {
109+
prefix = (prefix != null) ? prefix : "";
110+
if (prefix.startsWith(this.properties.getAccesslog().getPrefix())) {
111+
return prefix;
112+
}
113+
return this.properties.getAccesslog().getPrefix() + prefix;
114+
}
115+
116+
@Override
117+
public int getOrder() {
118+
return 1;
119+
}
120+
121+
}
122+
123+
static class TomcatAccessLogCustomizer extends AccessLogCustomizer
124+
implements WebServerFactoryCustomizer<TomcatReactiveWebServerFactory> {
125+
126+
TomcatAccessLogCustomizer(ManagementServerProperties properties) {
127+
super(properties);
128+
}
129+
130+
@Override
131+
public void customize(TomcatReactiveWebServerFactory factory) {
132+
System.out.println("Looking for access log valve in " + factory);
133+
AccessLogValve accessLogValve = findAccessLogValve(factory);
134+
if (accessLogValve == null) {
135+
System.out.println("Did not find it");
136+
return;
137+
}
138+
accessLogValve.setPrefix(customizePrefix(accessLogValve.getPrefix()));
139+
System.out.println("Customized " + factory);
140+
}
141+
142+
private AccessLogValve findAccessLogValve(TomcatReactiveWebServerFactory factory) {
143+
for (Valve engineValve : factory.getEngineValves()) {
144+
if (engineValve instanceof AccessLogValve accessLogValve) {
145+
return accessLogValve;
146+
}
147+
}
148+
return null;
149+
}
150+
151+
}
152+
153+
static class UndertowAccessLogCustomizer extends AccessLogCustomizer
154+
implements WebServerFactoryCustomizer<UndertowReactiveWebServerFactory> {
155+
156+
UndertowAccessLogCustomizer(ManagementServerProperties properties) {
157+
super(properties);
158+
}
159+
160+
@Override
161+
public void customize(UndertowReactiveWebServerFactory factory) {
162+
factory.setAccessLogPrefix(customizePrefix(factory.getAccessLogPrefix()));
163+
}
164+
165+
}
166+
167+
static class JettyAccessLogCustomizer extends AccessLogCustomizer
168+
implements WebServerFactoryCustomizer<JettyReactiveWebServerFactory> {
169+
170+
JettyAccessLogCustomizer(ManagementServerProperties properties) {
171+
super(properties);
172+
}
173+
174+
@Override
175+
public void customize(JettyReactiveWebServerFactory factory) {
176+
factory.addServerCustomizers(this::customizeServer);
177+
}
178+
179+
private void customizeServer(Server server) {
180+
RequestLog requestLog = server.getRequestLog();
181+
if (requestLog instanceof CustomRequestLog customRequestLog) {
182+
customizeRequestLog(customRequestLog);
183+
}
184+
}
185+
186+
private void customizeRequestLog(CustomRequestLog requestLog) {
187+
if (requestLog.getWriter() instanceof RequestLogWriter requestLogWriter) {
188+
customizeRequestLogWriter(requestLogWriter);
189+
}
190+
}
191+
192+
private void customizeRequestLogWriter(RequestLogWriter writer) {
193+
String filename = writer.getFileName();
194+
if (StringUtils.hasLength(filename)) {
195+
File file = new File(filename);
196+
file = new File(file.getParentFile(), customizePrefix(file.getName()));
197+
writer.setFilename(file.getPath());
198+
}
199+
}
200+
201+
}
202+
68203
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/reactive/ReactiveManagementChildContextConfigurationIntegrationTests.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,13 @@
1919
import java.io.IOException;
2020
import java.nio.charset.StandardCharsets;
2121
import java.nio.file.Path;
22+
import java.util.ArrayList;
23+
import java.util.List;
2224
import java.util.function.Consumer;
2325

26+
import org.apache.catalina.Valve;
27+
import org.apache.catalina.startup.Tomcat;
28+
import org.apache.catalina.valves.AccessLogValve;
2429
import org.junit.jupiter.api.Test;
2530
import org.junit.jupiter.api.io.TempDir;
2631

@@ -39,7 +44,11 @@
3944
import org.springframework.boot.test.context.runner.ContextConsumer;
4045
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
4146
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
47+
import org.springframework.boot.web.context.WebServerInitializedEvent;
48+
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
4249
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
50+
import org.springframework.boot.web.server.WebServer;
51+
import org.springframework.context.ApplicationListener;
4352
import org.springframework.context.ConfigurableApplicationContext;
4453
import org.springframework.core.convert.support.ConfigurableConversionService;
4554
import org.springframework.http.MediaType;
@@ -55,6 +64,8 @@
5564
*/
5665
class ReactiveManagementChildContextConfigurationIntegrationTests {
5766

67+
private final List<WebServer> webServers = new ArrayList<>();
68+
5869
private final ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(
5970
AnnotationConfigReactiveWebServerApplicationContext::new)
6071
.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class,
@@ -63,6 +74,8 @@ class ReactiveManagementChildContextConfigurationIntegrationTests {
6374
WebFluxAutoConfiguration.class))
6475
.withUserConfiguration(SucceedingEndpoint.class)
6576
.withInitializer(new ServerPortInfoApplicationContextInitializer())
77+
.withInitializer((context) -> context.addApplicationListener(
78+
(ApplicationListener<WebServerInitializedEvent>) (event) -> this.webServers.add(event.getWebServer())))
6679
.withPropertyValues("server.port=0", "management.server.port=0", "management.endpoints.web.exposure.include=*");
6780

6881
@TempDir
@@ -99,6 +112,26 @@ void whenManagementServerPortLoadedFromConfigTree() {
99112
.run((context) -> assertThat(context).hasNotFailed());
100113
}
101114

115+
@Test
116+
void accessLogHasManagementServerSpecificPrefix() {
117+
this.runner.withPropertyValues("server.tomcat.accesslog.enabled=true").run((context) -> {
118+
AccessLogValve accessLogValve = findAccessLogValve();
119+
assertThat(accessLogValve).isNotNull();
120+
assertThat(accessLogValve.getPrefix()).isEqualTo("management_access_log");
121+
});
122+
}
123+
124+
private AccessLogValve findAccessLogValve() {
125+
assertThat(this.webServers).hasSize(2);
126+
Tomcat tomcat = ((TomcatWebServer) this.webServers.get(1)).getTomcat();
127+
for (Valve valve : tomcat.getEngine().getPipeline().getValves()) {
128+
if (valve instanceof AccessLogValve accessLogValve) {
129+
return accessLogValve;
130+
}
131+
}
132+
return null;
133+
}
134+
102135
private void addConfigTreePropertySource(ConfigurableApplicationContext applicationContext) {
103136
try {
104137
applicationContext.getEnvironment()

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowReactiveWebServerFactory.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -112,6 +112,10 @@ public void setAccessLogPattern(String accessLogPattern) {
112112
this.delegate.setAccessLogPattern(accessLogPattern);
113113
}
114114

115+
public String getAccessLogPrefix() {
116+
return this.delegate.getAccessLogPrefix();
117+
}
118+
115119
@Override
116120
public void setAccessLogPrefix(String accessLogPrefix) {
117121
this.delegate.setAccessLogPrefix(accessLogPrefix);

0 commit comments

Comments
 (0)