Skip to content

Commit f61ed28

Browse files
Added Instrumentation for ServletContainerInitializer#onStartup (#2449)
1 parent 2416ad8 commit f61ed28

File tree

4 files changed

+143
-3
lines changed

4 files changed

+143
-3
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
package co.elastic.apm.agent.servlet;
19+
package co.elastic.apm.agent.servlet.servicename;
2020

21+
import co.elastic.apm.agent.servlet.AbstractServletInstrumentation;
22+
import co.elastic.apm.agent.servlet.ServletServiceNameHelper;
2123
import co.elastic.apm.agent.servlet.adapter.JakartaServletApiAdapter;
2224
import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter;
2325
import net.bytebuddy.asm.Advice;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.servlet.servicename;
20+
21+
import co.elastic.apm.agent.servlet.AbstractServletInstrumentation;
22+
import co.elastic.apm.agent.servlet.ServletServiceNameHelper;
23+
import co.elastic.apm.agent.servlet.adapter.JakartaServletApiAdapter;
24+
import co.elastic.apm.agent.servlet.adapter.JavaxServletApiAdapter;
25+
import net.bytebuddy.asm.Advice;
26+
import net.bytebuddy.description.NamedElement;
27+
import net.bytebuddy.description.method.MethodDescription;
28+
import net.bytebuddy.description.type.TypeDescription;
29+
import net.bytebuddy.matcher.ElementMatcher;
30+
31+
import javax.annotation.Nullable;
32+
import java.util.Set;
33+
34+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
35+
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
36+
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
37+
import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;
38+
import static net.bytebuddy.matcher.ElementMatchers.named;
39+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
40+
import static net.bytebuddy.matcher.ElementMatchers.not;
41+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
42+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
43+
44+
/**
45+
* Instruments
46+
* <ul>
47+
* <li>{@link javax.servlet.ServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)} </li>
48+
* <li>{@link jakarta.servlet.ServletContainerInitializer#onStartup(java.util.Set, jakarta.servlet.ServletContext)}</li>
49+
* </ul>
50+
* <p>
51+
* Determines the service name based on the webapp's {@code META-INF/MANIFEST.MF} file early in the startup process.
52+
* As this doesn't work with runtime attachment, the service name is also determined when the first request comes in.
53+
*/
54+
public abstract class ServletContainerInitializerServiceNameInstrumentation extends AbstractServletInstrumentation {
55+
56+
@Override
57+
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
58+
return nameContains("Initializer");
59+
}
60+
61+
@Override
62+
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
63+
return not(isInterface()).and(hasSuperType(namedOneOf(
64+
"javax.servlet.ServletContainerInitializer", "jakarta.servlet.ServletContainerInitializer")));
65+
}
66+
67+
@Override
68+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
69+
return named("onStartup")
70+
.and(takesArguments(2))
71+
.and(takesArgument(0, Set.class))
72+
.and(takesArgument(1, nameEndsWith("ServletContext")));
73+
}
74+
75+
public static class JavaxInitServiceNameInstrumentation extends ServletContainerInitializerServiceNameInstrumentation {
76+
77+
private static final JavaxServletApiAdapter adapter = JavaxServletApiAdapter.get();
78+
79+
@Override
80+
public String rootClassNameThatClassloaderCanLoad() {
81+
return "javax.servlet.AsyncContext";
82+
}
83+
84+
public static class AdviceClass {
85+
86+
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
87+
public static void onEnter(@Advice.Argument(1) @Nullable Object servletContext) {
88+
if (servletContext instanceof javax.servlet.ServletContext) {
89+
ServletServiceNameHelper.determineServiceName(adapter, (javax.servlet.ServletContext) servletContext, tracer);
90+
}
91+
}
92+
}
93+
}
94+
95+
public static class JakartaInitServiceNameInstrumentation extends ServletContainerInitializerServiceNameInstrumentation {
96+
97+
private static final JakartaServletApiAdapter adapter = JakartaServletApiAdapter.get();
98+
99+
@Override
100+
public String rootClassNameThatClassloaderCanLoad() {
101+
return "jakarta.servlet.AsyncContext";
102+
}
103+
104+
public static class AdviceClass {
105+
106+
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
107+
public static void onEnter(@Advice.Argument(1) @Nullable Object servletContext) {
108+
if (servletContext instanceof jakarta.servlet.ServletContext) {
109+
ServletServiceNameHelper.determineServiceName(adapter, (jakarta.servlet.ServletContext) servletContext, tracer);
110+
}
111+
}
112+
}
113+
}
114+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ co.elastic.apm.agent.servlet.JakartaAsyncInstrumentation$JakartaStartAsyncInstru
1414
co.elastic.apm.agent.servlet.JakartaAsyncInstrumentation$JakartaAsyncContextInstrumentation
1515
co.elastic.apm.agent.servlet.JavaxRequestStreamRecordingInstrumentation
1616
co.elastic.apm.agent.servlet.JakartaRequestStreamRecordingInstrumentation
17-
co.elastic.apm.agent.servlet.InitServiceNameInstrumentation$JavaxInitServiceNameInstrumentation
18-
co.elastic.apm.agent.servlet.InitServiceNameInstrumentation$JakartaInitServiceNameInstrumentation
17+
co.elastic.apm.agent.servlet.servicename.InitServiceNameInstrumentation$JavaxInitServiceNameInstrumentation
18+
co.elastic.apm.agent.servlet.servicename.InitServiceNameInstrumentation$JakartaInitServiceNameInstrumentation
19+
co.elastic.apm.agent.servlet.servicename.ServletContainerInitializerServiceNameInstrumentation$JavaxInitServiceNameInstrumentation
20+
co.elastic.apm.agent.servlet.servicename.ServletContainerInitializerServiceNameInstrumentation$JakartaInitServiceNameInstrumentation

apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/InitServiceNameInstrumentationTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,34 @@
2929
import javax.servlet.FilterChain;
3030
import javax.servlet.FilterConfig;
3131
import javax.servlet.Servlet;
32+
import javax.servlet.ServletContainerInitializer;
33+
import javax.servlet.ServletContext;
3234
import javax.servlet.ServletContextEvent;
3335
import javax.servlet.ServletContextListener;
3436
import javax.servlet.ServletException;
3537
import javax.servlet.ServletRequest;
3638
import javax.servlet.ServletResponse;
3739
import javax.servlet.http.HttpServlet;
3840
import java.io.IOException;
41+
import java.util.Set;
3942

4043
import static org.assertj.core.api.Assertions.assertThat;
4144

4245
class InitServiceNameInstrumentationTest extends AbstractInstrumentationTest {
4346

47+
@Test
48+
void testOnStartup() {
49+
ServletContainerInitializer servletContainerInitializer = new NoopServletContainerInitializer();
50+
51+
CustomManifestLoader cl = new CustomManifestLoader(() -> getClass().getResourceAsStream("/TEST-MANIFEST.MF"));
52+
CustomManifestLoader.withThreadContextClassLoader(cl, () -> {
53+
servletContainerInitializer.onStartup(null, new MockServletContext());
54+
tracer.startRootTransaction(cl).end();
55+
});
56+
57+
assertServiceInfo();
58+
}
59+
4460
@Test
4561
void testContextInitialized() {
4662
ServletContextListener servletContextListener = new NoopServletContextListener();
@@ -87,6 +103,12 @@ private void assertServiceInfo() {
87103
assertThat(traceContext.getServiceVersion()).isEqualTo("1.42.0");
88104
}
89105

106+
private static class NoopServletContainerInitializer implements ServletContainerInitializer {
107+
@Override
108+
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) {
109+
}
110+
}
111+
90112
private static class NoopServletContextListener implements ServletContextListener {
91113
@Override
92114
public void contextInitialized(ServletContextEvent servletContextEvent) {

0 commit comments

Comments
 (0)