Skip to content

Commit 246b7d3

Browse files
videnkzfelixbarny
authored andcommitted
Instrument View#render instead of DispatcherServlet#render (#829)
1 parent 130a21a commit 246b7d3

34 files changed

+1222
-184
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* Added annotation and meta-annotation matching support for `trace_methods`
1818
* Improved servlet exception capture via attributes: `javax.servlet.error.exception`, `exception`, `org.springframework.web.servlet.DispatcherServlet.EXCEPTION`, `co.elastic.apm.exception`.
1919
Added instrumentation for DispatcherServlet#processHandlerException.
20+
* Deleted span for `DispatcherServlet#render`. Instead, added span for `View#render` [#829](https://github.com/elastic/apm-agent-java/pull/829)
2021

2122
## Bug Fixes
2223
* A warning in logs saying APM server is not available when using 1.8 with APM server 6.x

apm-agent-plugins/apm-spring-webmvc-plugin/pom.xml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,52 @@
5757
<version>1.3</version>
5858
<scope>test</scope>
5959
</dependency>
60+
<!--Freemarker template-->
61+
<dependency>
62+
<groupId>org.freemarker</groupId>
63+
<artifactId>freemarker</artifactId>
64+
<version>2.3.23</version>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.springframework</groupId>
69+
<artifactId>spring-context-support</artifactId>
70+
<version>${version.spring}</version>
71+
<scope>test</scope>
72+
</dependency>
73+
<!--MappingJackson2JsonView-->
74+
<dependency>
75+
<groupId>com.fasterxml.jackson.core</groupId>
76+
<artifactId>jackson-databind</artifactId>
77+
<version>2.9.9</version>
78+
<scope>test</scope>
79+
</dependency>
80+
<!--Thymeleaf-->
81+
<dependency>
82+
<groupId>org.thymeleaf</groupId>
83+
<artifactId>thymeleaf</artifactId>
84+
<version>3.0.7.RELEASE</version>
85+
<scope>provided</scope>
86+
</dependency>
87+
<dependency>
88+
<groupId>org.thymeleaf</groupId>
89+
<artifactId>thymeleaf-spring4</artifactId>
90+
<version>3.0.7.RELEASE</version>
91+
<scope>provided</scope>
92+
</dependency>
93+
<!--groovy templates-->
94+
<dependency>
95+
<groupId>org.codehaus.groovy</groupId>
96+
<artifactId>groovy-templates</artifactId>
97+
<version>2.4.12</version>
98+
<scope>test</scope>
99+
</dependency>
100+
<dependency>
101+
<groupId>de.neuland-bfi</groupId>
102+
<artifactId>spring-jade4j</artifactId>
103+
<version>1.2.5</version>
104+
<scope>test</scope>
105+
</dependency>
60106
</dependencies>
61107

62108

apm-agent-plugins/apm-spring-webmvc-plugin/src/main/java/co/elastic/apm/agent/spring/webmvc/DispatcherServletRenderInstrumentation.java

Lines changed: 0 additions & 96 deletions
This file was deleted.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2019 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.spring.webmvc;
26+
27+
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
28+
import co.elastic.apm.agent.bci.VisibleForAdvice;
29+
import co.elastic.apm.agent.impl.transaction.Span;
30+
import co.elastic.apm.agent.impl.transaction.TraceContextHolder;
31+
import net.bytebuddy.asm.Advice;
32+
import net.bytebuddy.description.NamedElement;
33+
import net.bytebuddy.description.method.MethodDescription;
34+
import net.bytebuddy.description.type.TypeDescription;
35+
import net.bytebuddy.matcher.ElementMatcher;
36+
import org.springframework.web.servlet.view.AbstractView;
37+
38+
import javax.annotation.Nullable;
39+
import java.util.Arrays;
40+
import java.util.Collection;
41+
import java.util.Map;
42+
import java.util.concurrent.ConcurrentHashMap;
43+
44+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
45+
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
46+
import static net.bytebuddy.matcher.ElementMatchers.named;
47+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
48+
49+
public class ViewRenderInstrumentation extends ElasticApmInstrumentation {
50+
51+
private static final String SPAN_TYPE = "template";
52+
private static final String SPAN_ACTION = "render";
53+
private static final String DISPATCHER_SERVLET_RENDER_METHOD = "View#render";
54+
private static Map<String, String> subTypeCache = new ConcurrentHashMap<>();
55+
56+
@Override
57+
public Class<?> getAdviceClass() {
58+
return ViewRenderAdviceService.class;
59+
}
60+
61+
public static class ViewRenderAdviceService {
62+
63+
@Advice.OnMethodEnter(suppress = Throwable.class)
64+
public static void beforeExecute(@Advice.Local("span") @Nullable Span span,
65+
@Advice.This @Nullable Object thiz) {
66+
if (tracer == null || tracer.getActive() == null) {
67+
return;
68+
}
69+
final TraceContextHolder<?> parent = tracer.getActive();
70+
71+
String className = thiz.getClass().getName();
72+
span = parent.createSpan()
73+
.withType(SPAN_TYPE)
74+
.withSubtype(getSubtype(className))
75+
.withAction(SPAN_ACTION)
76+
.withName(DISPATCHER_SERVLET_RENDER_METHOD);
77+
78+
if (thiz instanceof AbstractView) {
79+
AbstractView view = (AbstractView) thiz;
80+
span.appendToName(" ").appendToName(view.getBeanName());
81+
}
82+
span.activate();
83+
}
84+
85+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
86+
public static void afterExecute(@Advice.Local("span") @Nullable Span span,
87+
@Advice.Thrown @Nullable Throwable t) {
88+
if (span != null) {
89+
span.captureException(t)
90+
.deactivate()
91+
.end();
92+
}
93+
}
94+
95+
@VisibleForAdvice
96+
public static String getSubtype(String className) {
97+
switch (className) {
98+
case "org.springframework.web.servlet.view.groovy.GroovyMarkupView":
99+
return "GroovyMarkup";
100+
case "org.springframework.web.servlet.view.freemarker.FreeMarkerView":
101+
return "FreeMarker";
102+
case "org.springframework.web.servlet.view.json.MappingJackson2JsonView":
103+
return "MappingJackson2Json";
104+
case "de.neuland.jade4j.spring.view.JadeView":
105+
return "Jade";
106+
case "org.springframework.web.servlet.view.InternalResourceView":
107+
return "InternalResource";
108+
case "org.thymeleaf.spring4.view.ThymeleafView":
109+
return "Thymeleaf";
110+
default:
111+
String subType = subTypeCache.get(className);
112+
if (subType != null) {
113+
return subType;
114+
} else {
115+
int indexOfLastDot = className.lastIndexOf('.');
116+
int indexOfView = className.lastIndexOf("View");
117+
subType = className.substring(indexOfLastDot + 1, indexOfView > indexOfLastDot ? indexOfView : className.length());
118+
if (subTypeCache.size() < 1000) {
119+
subTypeCache.put(className, subType);
120+
}
121+
return subType;
122+
}
123+
}
124+
}
125+
}
126+
127+
@Override
128+
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
129+
return nameContains("View");
130+
}
131+
132+
@Override
133+
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
134+
return hasSuperType(named("org.springframework.web.servlet.View"));
135+
}
136+
137+
@Override
138+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
139+
return named("render")
140+
.and(takesArgument(0, named("java.util.Map")))
141+
.and(takesArgument(1, named("javax.servlet.http.HttpServletRequest")))
142+
.and(takesArgument(2, named("javax.servlet.http.HttpServletResponse")));
143+
}
144+
145+
@Override
146+
public Collection<String> getInstrumentationGroupNames() {
147+
return Arrays.asList("spring-view-render");
148+
}
149+
}
150+
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
co.elastic.apm.agent.spring.webmvc.SpringTransactionNameInstrumentation
2-
co.elastic.apm.agent.spring.webmvc.DispatcherServletRenderInstrumentation
2+
co.elastic.apm.agent.spring.webmvc.ViewRenderInstrumentation
33
co.elastic.apm.agent.spring.webmvc.SpringServiceNameInstrumentation
44
co.elastic.apm.agent.spring.webmvc.ExceptionHandlerInstrumentation

apm-agent-plugins/apm-spring-webmvc-plugin/src/test/java/co/elastic/apm/agent/spring/webmvc/DispatcherServletRenderInstrumentationTest.java

Lines changed: 0 additions & 79 deletions
This file was deleted.

0 commit comments

Comments
 (0)