Skip to content

Commit a882c45

Browse files
authored
Simple bypass for servlet spec < 3.x (#1077)
* Simple bypass for servlet spec < 3.x
1 parent a9005e1 commit a882c45

File tree

11 files changed

+235
-33
lines changed

11 files changed

+235
-33
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ endif::[]
2929
[float]
3030
===== Features
3131
* Gracefully abort agent init when running on a known Java 8 buggy JVM {pull}1075[#1075].
32-
3332
* Add support for <<supported-databases, Redis Redisson client>>
3433
* Makes <<config-instrument>>, <<config-trace-methods>>, and <<config-disable-instrumentations>> dynamic.
3534
Note that changing these values at runtime can slow down the application temporarily.
35+
* Do not instrument Servlet API before 3.0 {pull}1077[#1077]
3636
3737
[float]
3838
===== Bug fixes

apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/CustomElementMatchers.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* the Apache License, Version 2.0 (the "License"); you may
1212
* not use this file except in compliance with the License.
1313
* You may obtain a copy of the License at
14-
*
14+
*
1515
* http://www.apache.org/licenses/LICENSE-2.0
16-
*
16+
*
1717
* Unless required by applicable law or agreed to in writing,
1818
* software distributed under the License is distributed on an
1919
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.servlet;
26+
27+
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
28+
import co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers;
29+
import net.bytebuddy.matcher.ElementMatcher;
30+
31+
import java.util.Collection;
32+
import java.util.Collections;
33+
34+
import static co.elastic.apm.agent.servlet.ServletInstrumentation.SERVLET_API;
35+
36+
public abstract class AbstractServletInstrumentation extends ElasticApmInstrumentation {
37+
38+
@Override
39+
public Collection<String> getInstrumentationGroupNames() {
40+
return Collections.singleton(SERVLET_API);
41+
}
42+
43+
@Override
44+
public final ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
45+
// this class has been introduced in servlet spec 3.0
46+
// choice of class name to use for this test does not work as expected across all application servers
47+
// for example, 'javax.servlet.annotation.WebServlet' annotation is not working as expected on Payara
48+
return CustomElementMatchers.classLoaderCanLoadClass("javax.servlet.AsyncContext");
49+
}
50+
}

apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package co.elastic.apm.agent.servlet;
2626

27-
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
2827
import co.elastic.apm.agent.bci.HelperClassManager;
2928
import co.elastic.apm.agent.bci.VisibleForAdvice;
3029
import co.elastic.apm.agent.impl.ElasticApmTracer;
@@ -62,7 +61,7 @@
6261
* (see {@link StartAsyncInstrumentation.StartAsyncAdvice#onExitStartAsync(AsyncContext)}
6362
* and {@link AsyncContextInstrumentation.AsyncContextStartAdvice#onEnterAsyncContextStart(Runnable)}).
6463
*/
65-
public abstract class AsyncInstrumentation extends ElasticApmInstrumentation {
64+
public abstract class AsyncInstrumentation extends AbstractServletInstrumentation {
6665

6766
private static final String SERVLET_API_ASYNC_GROUP_NAME = "servlet-api-async";
6867
@Nullable

apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/FilterChainInstrumentation.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* the Apache License, Version 2.0 (the "License"); you may
1212
* not use this file except in compliance with the License.
1313
* You may obtain a copy of the License at
14-
*
14+
*
1515
* http://www.apache.org/licenses/LICENSE-2.0
16-
*
16+
*
1717
* Unless required by applicable law or agreed to in writing,
1818
* software distributed under the License is distributed on an
1919
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -24,17 +24,12 @@
2424
*/
2525
package co.elastic.apm.agent.servlet;
2626

27-
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
2827
import co.elastic.apm.agent.impl.ElasticApmTracer;
2928
import net.bytebuddy.description.NamedElement;
3029
import net.bytebuddy.description.method.MethodDescription;
3130
import net.bytebuddy.description.type.TypeDescription;
3231
import net.bytebuddy.matcher.ElementMatcher;
3332

34-
import java.util.Collection;
35-
import java.util.Collections;
36-
37-
import static co.elastic.apm.agent.servlet.ServletInstrumentation.SERVLET_API;
3833
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
3934
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
4035
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
@@ -45,7 +40,7 @@
4540
/**
4641
* Instruments {@link javax.servlet.FilterChain}s to create transactions.
4742
*/
48-
public class FilterChainInstrumentation extends ElasticApmInstrumentation {
43+
public class FilterChainInstrumentation extends AbstractServletInstrumentation {
4944

5045
public FilterChainInstrumentation(ElasticApmTracer tracer) {
5146
ServletApiAdvice.init(tracer);
@@ -74,9 +69,4 @@ public Class<?> getAdviceClass() {
7469
return ServletApiAdvice.class;
7570
}
7671

77-
@Override
78-
public Collection<String> getInstrumentationGroupNames() {
79-
return Collections.singleton(SERVLET_API);
80-
}
81-
8272
}

apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package co.elastic.apm.agent.servlet;
2626

27-
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
2827
import co.elastic.apm.agent.bci.HelperClassManager;
2928
import co.elastic.apm.agent.bci.VisibleForAdvice;
3029
import co.elastic.apm.agent.impl.ElasticApmTracer;
@@ -49,14 +48,15 @@
4948
import static net.bytebuddy.matcher.ElementMatchers.not;
5049
import static net.bytebuddy.matcher.ElementMatchers.returns;
5150

52-
public class RequestStreamRecordingInstrumentation extends ElasticApmInstrumentation {
51+
public class RequestStreamRecordingInstrumentation extends AbstractServletInstrumentation {
5352

5453
@Nullable
5554
@VisibleForAdvice
5655
// referring to InputStreamWrapperFactory is legal because of type erasure
5756
public static HelperClassManager<InputStreamWrapperFactory> wrapperHelperClassManager;
5857

5958
public RequestStreamRecordingInstrumentation(ElasticApmTracer tracer) {
59+
ServletApiAdvice.init(tracer);
6060
synchronized (RequestStreamRecordingInstrumentation.class) {
6161
if (wrapperHelperClassManager == null) {
6262
wrapperHelperClassManager = HelperClassManager.ForSingleClassLoader.of(tracer,

apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package co.elastic.apm.agent.servlet;
2626

27-
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
2827
import co.elastic.apm.agent.bci.HelperClassManager;
2928
import co.elastic.apm.agent.bci.VisibleForAdvice;
3029
import co.elastic.apm.agent.impl.ElasticApmTracer;
@@ -36,8 +35,6 @@
3635

3736
import javax.annotation.Nullable;
3837
import javax.servlet.http.HttpServletRequest;
39-
import java.util.Collection;
40-
import java.util.Collections;
4138

4239
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
4340
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
@@ -56,7 +53,7 @@
5653
* this makes sure to record a transaction in that case.
5754
* </p>
5855
*/
59-
public class ServletInstrumentation extends ElasticApmInstrumentation {
56+
public class ServletInstrumentation extends AbstractServletInstrumentation {
6057

6158
static final String SERVLET_API = "servlet-api";
6259

@@ -101,11 +98,6 @@ public Class<?> getAdviceClass() {
10198
return ServletApiAdvice.class;
10299
}
103100

104-
@Override
105-
public Collection<String> getInstrumentationGroupNames() {
106-
return Collections.singleton(SERVLET_API);
107-
}
108-
109101
@VisibleForAdvice
110102
public interface ServletTransactionCreationHelper<R> {
111103
@Nullable
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.servlet;
26+
27+
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
28+
import co.elastic.apm.agent.bci.VisibleForAdvice;
29+
import net.bytebuddy.asm.Advice;
30+
import net.bytebuddy.description.NamedElement;
31+
import net.bytebuddy.description.method.MethodDescription;
32+
import net.bytebuddy.description.type.TypeDescription;
33+
import net.bytebuddy.matcher.ElementMatcher;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
import javax.annotation.Nullable;
38+
import javax.servlet.Servlet;
39+
import javax.servlet.ServletConfig;
40+
import javax.servlet.ServletContext;
41+
import javax.servlet.ServletRequest;
42+
import javax.servlet.ServletResponse;
43+
import java.util.Collection;
44+
import java.util.Collections;
45+
46+
import static co.elastic.apm.agent.servlet.ServletInstrumentation.SERVLET_API;
47+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
48+
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
49+
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
50+
import static net.bytebuddy.matcher.ElementMatchers.nameContainsIgnoreCase;
51+
import static net.bytebuddy.matcher.ElementMatchers.named;
52+
import static net.bytebuddy.matcher.ElementMatchers.not;
53+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
54+
55+
/**
56+
* Instruments {@link javax.servlet.Servlet} to log Servlet container details and warns about unsupported version.
57+
* <p>
58+
* Does not inherit from {@link AbstractServletInstrumentation} in order to still instrument when servlet version is not
59+
* supported.
60+
*/
61+
public abstract class ServletVersionInstrumentation extends ElasticApmInstrumentation {
62+
63+
@VisibleForAdvice
64+
public static final Logger logger = LoggerFactory.getLogger(ServletVersionInstrumentation.class);
65+
66+
@VisibleForAdvice
67+
public static volatile boolean alreadyLogged = false;
68+
69+
@Override
70+
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
71+
return nameContains("Servlet").or(nameContainsIgnoreCase("jsp"));
72+
}
73+
74+
@Override
75+
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
76+
return not(isInterface())
77+
.and(hasSuperType(named("javax.servlet.Servlet")));
78+
}
79+
80+
@Override
81+
public Collection<String> getInstrumentationGroupNames() {
82+
return Collections.singleton(SERVLET_API);
83+
}
84+
85+
/**
86+
* Instruments {@link javax.servlet.Servlet#init(ServletConfig)}
87+
*/
88+
public static class Init extends ServletVersionInstrumentation {
89+
90+
@Override
91+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
92+
return named("init")
93+
.and(takesArgument(0, named("javax.servlet.ServletConfig")));
94+
}
95+
96+
@Advice.OnMethodEnter(suppress = Throwable.class)
97+
@SuppressWarnings("Duplicates") // duplication is fine here as it allows to inline code
98+
private static void onEnter(@Advice.Argument(0) @Nullable ServletConfig servletConfig) {
99+
if (alreadyLogged) {
100+
return;
101+
}
102+
alreadyLogged = true;
103+
104+
int majorVersion = -1;
105+
int minorVersion = -1;
106+
String serverInfo = null;
107+
if (servletConfig != null) {
108+
ServletContext servletContext = servletConfig.getServletContext();
109+
if (null != servletContext) {
110+
majorVersion = servletContext.getMajorVersion();
111+
minorVersion = servletContext.getMinorVersion();
112+
serverInfo = servletContext.getServerInfo();
113+
}
114+
}
115+
116+
logger.info("Servlet container info = {}", serverInfo);
117+
if (majorVersion < 3) {
118+
logger.warn("Unsupported servlet version detected: {}.{}, no Servlet transaction will be created", majorVersion, minorVersion);
119+
}
120+
}
121+
}
122+
123+
/**
124+
* Instruments {@link javax.servlet.Servlet#service(ServletRequest, ServletResponse)}
125+
*/
126+
public static class Service extends ServletVersionInstrumentation {
127+
128+
@Override
129+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
130+
return named("service")
131+
.and(takesArgument(0, named("javax.servlet.ServletRequest")))
132+
.and(takesArgument(1, named("javax.servlet.ServletResponse")));
133+
}
134+
135+
@Advice.OnMethodEnter(suppress = Throwable.class)
136+
@SuppressWarnings("Duplicates") // duplication is fine here as it allows to inline code
137+
private static void onEnter(@Advice.This Servlet servlet) {
138+
if (alreadyLogged) {
139+
return;
140+
}
141+
alreadyLogged = true;
142+
143+
ServletConfig servletConfig = servlet.getServletConfig();
144+
145+
int majorVersion = -1;
146+
int minorVersion = -1;
147+
String serverInfo = null;
148+
if (servletConfig != null) {
149+
ServletContext servletContext = servletConfig.getServletContext();
150+
if (null != servletContext) {
151+
majorVersion = servletContext.getMajorVersion();
152+
minorVersion = servletContext.getMinorVersion();
153+
serverInfo = servletContext.getServerInfo();
154+
}
155+
}
156+
157+
logger.info("Servlet container info = {}", serverInfo);
158+
if (majorVersion < 3) {
159+
logger.warn("Unsupported servlet version detected: {}.{}, no Servlet transaction will be created", majorVersion, minorVersion);
160+
}
161+
}
162+
}
163+
164+
165+
}

apm-agent-plugins/apm-servlet-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
co.elastic.apm.agent.servlet.ServletInstrumentation
2+
co.elastic.apm.agent.servlet.ServletVersionInstrumentation$Init
3+
co.elastic.apm.agent.servlet.ServletVersionInstrumentation$Service
24
co.elastic.apm.agent.servlet.FilterChainInstrumentation
35
co.elastic.apm.agent.servlet.AsyncInstrumentation$StartAsyncInstrumentation
46
co.elastic.apm.agent.servlet.AsyncInstrumentation$AsyncContextInstrumentation

integration-tests/application-server-integration-tests/src/test/java/co/elastic/apm/servlet/AbstractServletContainerIntegrationTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ public String executeAndValidateRequest(String pathToTest, String expectedConten
281281
Map<String, String> headersMap) throws IOException, InterruptedException {
282282
Response response = executeRequest(pathToTest, headersMap);
283283
if (expectedResponseCode != null) {
284-
assertThat(response.code()).withFailMessage(response.toString() + getServerLogs()).isEqualTo(expectedResponseCode);
284+
assertThat(response.code())
285+
.withFailMessage(response.toString() + getServerLogs())
286+
.isEqualTo(expectedResponseCode);
285287
}
286288
final ResponseBody responseBody = response.body();
287289
assertThat(responseBody).isNotNull();
@@ -295,7 +297,9 @@ public String executeAndValidateRequest(String pathToTest, String expectedConten
295297
public String executeAndValidatePostRequest(String pathToTest, RequestBody postBody, String expectedContent, Integer expectedResponseCode) throws IOException, InterruptedException {
296298
Response response = executePostRequest(pathToTest, postBody);
297299
if (expectedResponseCode != null) {
298-
assertThat(response.code()).withFailMessage(response.toString() + getServerLogs()).isEqualTo(expectedResponseCode);
300+
assertThat(response.code())
301+
.withFailMessage(response.toString() + getServerLogs())
302+
.isEqualTo(expectedResponseCode);
299303
}
300304
final ResponseBody responseBody = response.body();
301305
assertThat(responseBody).isNotNull();

0 commit comments

Comments
 (0)