diff --git a/instrumentation/jsp-4/README.md b/instrumentation/jsp-4/README.md new file mode 100644 index 0000000000..c70175e7b8 --- /dev/null +++ b/instrumentation/jsp-4/README.md @@ -0,0 +1,99 @@ +# jsp-4 Instrumentation Module + +## Injection of the Real User Monitoring Script Via JSP Tag Libraries +Prior to the additions to this instrumentation module, the only way to inject the RUM script into JSPs was during the +compilation phase of the Jasper compiler, which injects the script into the HTML `` element, if present in the page +source. + +Some applications use custom JSP tag libraries to create the head tag (and other HTML page elements). In this scenario, the +RUM script will not be injected because of the way the Jasper compiler instrumentation detects the head tag. This +instrumentation module weaves the `SimpleTagSupport` and `TagSupport` classes to detect the creation of head elements via the +tag execution and inject the RUM script at that time. + +### Configuration +To enable tag library instrumentation, both of the following settings must be `true`: +``` + browser_monitoring: + auto_instrument: true + tag_lib_instrument: true +``` + +By default, the `tag_lib_instrument` setting is `false`. + +The instrumentation will use the regular expression pattern of `` to detect the start of HTML head elements. +If a tag library emits a more complex head start element, the regular expression can be modified via the `tag_lib_head_pattern` +config setting. For example: +``` + browser_monitoring: + tag_lib_head_pattern: '' +``` +The regular expression will be compiled to be case-insensitive. If the defined regular expression is invalid it will default to ``. + + +### Requirements for Script Injection +The following are the requirements for the RUM script to be injected from instrumented tag libraries: +- The application must utilize version 4 of the JSP libraries (jakarta namespace) +- Only the first instance of a `` string emitted by a tag library will be considered, regardless of context (in a comment, for example) +- The custom tag must extend either the [SimpleTagSupport](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/tagext/simpletagsupport) or +[TagSupport](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/tagext/tagsupport) classes +- The `` element must be emitted from the tag class via the `print(String s)` or `println(String s)` methods of the JspWriter +fetched from a [JspContext.getOut()](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/jspcontext) call +- The head start tag (``) must be totally emitted in a single `print`/`println` call. For example, these are all valid: +```java + out.println(""); + out.print(""); + out.println("Test Site"); + out.println(""); + out.println("<he" + "ad>") +``` + +### Example SimpleTagSupport Use Case +##### Tag Library Class +```java + // Package/imports omitted + // Also assumes the existence of a valid, corresponding tld file + public class SimpleTagExample extends SimpleTagSupport { + public void doTag() throws JspException, IOException { + JspWriter out = getJspContext().getOut(); + out.println("<head>\n" + + "<meta charset=\"ISO-8859-1\">\n" + + "<title>Test Site" + + ""); + } + } +``` + +##### JSP +```html +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> +<%@ taglib prefix="simple" uri="http://com.sun.simpletagexample"%> + + + + + +

Sample JSP

+ + +``` + +In this scenario, the `SimpleTagSupport` instrumentation will detect the output of the head tag and inject the RUM script +just like the Jasper compiler instrumentation, which will result in the following HTML: +```html + + + + + + +Test Site + + +

Sample JSP

+ + +``` + +### Example TagSupport Use Case +Tag libraries that extend the `TagSupport` class work in largely the same way as the `SimpleTagSupport` based tag libraries. The instrumentation +will detect head element generation via the `doStartTag` or `doEndTag` method calls and will inject the RUM script appropriately. diff --git a/instrumentation/jsp-4/build.gradle b/instrumentation/jsp-4/build.gradle new file mode 100644 index 0000000000..77e52d5dc8 --- /dev/null +++ b/instrumentation/jsp-4/build.gradle @@ -0,0 +1,30 @@ + +dependencies { + implementation(project(":agent-bridge")) + implementation("jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0") + implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") + implementation("jakarta.el:jakarta.el-api:6.0.0") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.jsp-4' } +} + +verifyInstrumentation { + passesOnly('jakarta.servlet.jsp:jakarta.servlet.jsp-api:[4.0.0,)') { + implementation("jakarta.el:jakarta.el-api:6.0.0") + implementation("jakarta.servlet:jakarta.servlet-api:6.1.0") + } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +site { + title 'JSP' + type 'Other' + versionOverride '[4.0,)' +} diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java new file mode 100644 index 0000000000..f8d866e14c --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java @@ -0,0 +1,77 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.jsp4; + +import jakarta.servlet.jsp.JspContext; +import jakarta.servlet.jsp.JspWriter; +import java.util.Enumeration; + +public class JspContextWrapper extends JspContext { + private final JspContext wrappedContext; + private final JspWriter wrappedWriter; + + public JspContextWrapper(JspContext originalContext, JspWriter wrappedWriter) { + this.wrappedContext = originalContext; + this.wrappedWriter = wrappedWriter; + } + + @Override + public void setAttribute(String name, Object value) { + wrappedContext.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Object value, int scope) { + wrappedContext.setAttribute(name, value, scope); + } + + @Override + public Object getAttribute(String name) { + return wrappedContext.getAttribute(name); + } + + @Override + public Object getAttribute(String name, int scope) { + return wrappedContext.getAttribute(name, scope); + } + + @Override + public Object findAttribute(String name) { + return wrappedContext.findAttribute(name); + } + + @Override + public void removeAttribute(String name) { + wrappedContext.removeAttribute(name); + } + + @Override + public void removeAttribute(String name, int scope) { + wrappedContext.removeAttribute(name, scope); + } + + @Override + public int getAttributesScope(String name) { + return wrappedContext.getAttributesScope(name); + } + + @Override + public Enumeration getAttributeNamesInScope(int scope) { + return wrappedContext.getAttributeNamesInScope(scope); + } + + @Override + public JspWriter getOut() { + return wrappedWriter; + } + + @Override + public jakarta.el.ELContext getELContext() { + return wrappedContext.getELContext(); + } +} diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java new file mode 100644 index 0000000000..5d09d2c9be --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java @@ -0,0 +1,76 @@ +/* + * + * * Copyright 2022 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.jsp4; + +import java.util.logging.Level; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.agent.bridge.TracedMethod; +import com.newrelic.agent.bridge.TransactionNamePriority; +import com.newrelic.api.agent.NewRelic; + +public class JspUtils { + public static final String JSP_CATEGORY = "JSP"; + public static final String ORG_APACHE_JSP = "org.apache.jsp."; + public static final Pattern JSP_PATTERN = Pattern.compile("_jsp$"); + public static final Pattern WEB_INF_PATTERN = Pattern.compile("WEB_002dINF"); + private static final String AUTO_INSTRUMENT_ENABLED_CONFIG = "browser_monitoring.auto_instrument"; + private static final String TAG_LIB_ENABLED_CONFIG = "browser_monitoring.tag_lib_instrument"; + private static final String TAG_LIB_HEAD_REGEX = "browser_monitoring.tag_lib_head_pattern"; + private static final String TAG_LIB_HEAD_REGEX_DEFAULT = ""; + + public static final Pattern START_HEAD_REGEX = generateStartHeadElementRegExPattern(); + + public static void setTransactionName(Class jspClass, TracedMethod timedMethod) { + String name = jspClass.getName(); + try { + if (name.startsWith(ORG_APACHE_JSP)) { + name = name.substring(ORG_APACHE_JSP.length()).replace('.', '/'); + name = WEB_INF_PATTERN.matcher(name).replaceFirst("WEB-INF"); + } else { + int index = name.lastIndexOf('.'); + if (index > 0) { + name = name.substring(index + 1); + } + } + name = JSP_PATTERN.matcher(name).replaceAll(".jsp"); + AgentBridge.getAgent().getTransaction().setTransactionName(TransactionNamePriority.JSP, false, + JSP_CATEGORY, name); + + timedMethod.setMetricName("Jsp", name); + } catch (Exception e) { + NewRelic.getAgent().getLogger().log(Level.FINER, "An error occurred formatting a jsp name : {0}", e); + } + } + + public static boolean isTagLibInstrumentationEnabled() { + return NewRelic.getAgent().getConfig().getValue(AUTO_INSTRUMENT_ENABLED_CONFIG, Boolean.FALSE) && + NewRelic.getAgent().getConfig().getValue(TAG_LIB_ENABLED_CONFIG, Boolean.FALSE); + } + + private static Pattern generateStartHeadElementRegExPattern() { + String regexString = NewRelic.getAgent().getConfig().getValue(TAG_LIB_HEAD_REGEX, TAG_LIB_HEAD_REGEX_DEFAULT); + Pattern pattern; + + try { + pattern = Pattern.compile(regexString, Pattern.CASE_INSENSITIVE + Pattern.MULTILINE); + } catch (PatternSyntaxException e) { + NewRelic.getAgent().getLogger().log(Level.WARNING, "Invalid pattern defined for tag lib start head regex: {0} Defaulting to: {1}", + regexString, TAG_LIB_HEAD_REGEX_DEFAULT); + pattern = Pattern.compile(TAG_LIB_HEAD_REGEX_DEFAULT, Pattern.CASE_INSENSITIVE + Pattern.MULTILINE); + } + + if (NewRelic.getAgent().getLogger().isLoggable(Level.FINEST)) { + NewRelic.getAgent().getLogger().log(Level.FINEST, "Tag lib start head regex to be used for RUM script injection: {0}", pattern.pattern()); + } + + return pattern; + } +} diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java new file mode 100644 index 0000000000..dea733a98f --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java @@ -0,0 +1,189 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.jsp4; + +import com.newrelic.api.agent.NewRelic; + +import jakarta.servlet.jsp.JspWriter; +import java.io.IOException; +import java.util.regex.Matcher; + +public class JspWriterWrapper extends JspWriter { + private JspWriter originalWriter; + + private boolean found = false; + + public JspWriterWrapper(JspWriter originalWriter) { + this(-1, false); + this.originalWriter = originalWriter; + } + + protected JspWriterWrapper(int bufferSize, boolean autoFlush) { + super(bufferSize, autoFlush); + } + + @Override + public void newLine() throws IOException { + originalWriter.newLine(); + } + + @Override + public void print(boolean b) throws IOException { + originalWriter.print(b); + } + + @Override + public void print(char c) throws IOException { + originalWriter.print(c); + } + + @Override + public void print(int i) throws IOException { + originalWriter.print(i); + } + + @Override + public void print(long l) throws IOException { + originalWriter.print(l); + } + + @Override + public void print(float f) throws IOException { + originalWriter.print(f); + } + + @Override + public void print(double d) throws IOException { + originalWriter.print(d); + } + + @Override + public void print(char[] s) throws IOException { + originalWriter.print(s); + } + + @Override + public void print(String str) throws IOException { + if (!found) { + originalWriter.print(detectAndModifyHeadElement(str)); + } else { + originalWriter.print(str); + } + } + + @Override + public void print(Object obj) throws IOException { + originalWriter.print(obj); + } + + @Override + public void println() throws IOException { + originalWriter.println(); + } + + @Override + public void println(boolean b) throws IOException { + originalWriter.println(b); + } + + @Override + public void println(char c) throws IOException { + originalWriter.println(c); + } + + @Override + public void println(int i) throws IOException { + originalWriter.println(i); + } + + @Override + public void println(long l) throws IOException { + originalWriter.println(l); + } + + @Override + public void println(float f) throws IOException { + originalWriter.println(f); + } + + @Override + public void println(double d) throws IOException { + originalWriter.println(d); + } + + @Override + public void println(char[] c) throws IOException { + originalWriter.println(c); + } + + @Override + public void println(String str) throws IOException { + if (!found) { + originalWriter.println(detectAndModifyHeadElement(str)); + } else { + originalWriter.println(str); + } + } + + @Override + public void println(Object o) throws IOException { + originalWriter.println(o); + } + + @Override + public void clear() throws IOException { + originalWriter.clear(); + } + + @Override + public void clearBuffer() throws IOException { + originalWriter.clearBuffer(); + } + + @Override + public void write(int c) throws IOException { + originalWriter.write(c); + } + + @Override + public void write(String s, int off, int len) throws IOException { + originalWriter.write(s, off, len); + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + originalWriter.write(cbuf, off, len); + } + + @Override + public void flush() throws IOException { + originalWriter.flush(); + } + + @Override + public void close() throws IOException { + originalWriter.close(); + } + + @Override + public int getRemaining() { + return originalWriter.getRemaining(); + } + + private String detectAndModifyHeadElement(String originalContent) { + String modifiedContent = null; + Matcher m = JspUtils.START_HEAD_REGEX.matcher(originalContent); + if (m.find()) { + // The getBrowserTimingHeader call performs the "does transaction exist" check so not duplicating that here + modifiedContent = originalContent.substring(0, m.end()) + NewRelic.getBrowserTimingHeader() + originalContent.substring(m.end()); + found = true; + } + + return modifiedContent != null ? modifiedContent : originalContent; + } +} diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java new file mode 100644 index 0000000000..075e2ee9f0 --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java @@ -0,0 +1,156 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package com.nr.agent.instrumentation.jsp4; + +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.jsp.JspWriter; +import jakarta.servlet.jsp.PageContext; +import java.io.IOException; +import java.util.Enumeration; + +public class PageContextWrapper extends PageContext { + private final PageContext wrappedContext; + private final JspWriter wrappedWriter; + + public PageContextWrapper(PageContext wrappedContext, JspWriter wrappedWriter) { + this.wrappedContext = wrappedContext; + this.wrappedWriter = wrappedWriter; + } + + @Override + public void setAttribute(String name, Object value) { + wrappedContext.setAttribute(name, value); + } + + @Override + public void setAttribute(String name, Object value, int scope) { + wrappedContext.setAttribute(name, value, scope); + } + + @Override + public Object getAttribute(String name) { + return wrappedContext.getAttribute(name); + } + + @Override + public Object getAttribute(String name, int scope) { + return wrappedContext.getAttribute(name, scope); + } + + @Override + public Object findAttribute(String name) { + return wrappedContext.findAttribute(name); + } + + @Override + public void removeAttribute(String name) { + wrappedContext.removeAttribute(name); + } + + @Override + public void removeAttribute(String name, int scope) { + wrappedContext.removeAttribute(name, scope); + } + + @Override + public int getAttributesScope(String name) { + return wrappedContext.getAttributesScope(name); + } + + @Override + public Enumeration getAttributeNamesInScope(int scope) { + return wrappedContext.getAttributeNamesInScope(scope); + } + + @Override + public JspWriter getOut() { + return wrappedWriter; + } + + @Override + public jakarta.el.ELContext getELContext() { + return wrappedContext.getELContext(); + } + + @Override + public void initialize(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize, + boolean autoFlush) throws IOException, IllegalStateException, IllegalArgumentException { + + } + + @Override + public void release() { + wrappedContext.release(); + } + + @Override + public HttpSession getSession() { + return wrappedContext.getSession(); + } + + @Override + public Object getPage() { + return wrappedContext.getPage(); + } + + @Override + public ServletRequest getRequest() { + return wrappedContext.getRequest(); + } + + @Override + public ServletResponse getResponse() { + return wrappedContext.getResponse(); + } + + @Override + public Exception getException() { + return wrappedContext.getException(); + } + + @Override + public ServletConfig getServletConfig() { + return wrappedContext.getServletConfig(); + } + + @Override + public ServletContext getServletContext() { + return wrappedContext.getServletContext(); + } + + @Override + public void forward(String relativeUrlPath) throws ServletException, IOException { + wrappedContext.forward(relativeUrlPath); + } + + @Override + public void include(String relativeUrlPath) throws ServletException, IOException { + wrappedContext.include(relativeUrlPath); + } + + @Override + public void include(String relativeUrlPath, boolean flush) throws ServletException, IOException { + wrappedContext.include(relativeUrlPath, flush); + } + + @Override + public void handlePageException(Exception e) throws ServletException, IOException { + wrappedContext.handlePageException(e); + } + + @Override + public void handlePageException(Throwable t) throws ServletException, IOException { + wrappedContext.handlePageException(t); + } +} diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java new file mode 100644 index 0000000000..8ca4bb7145 --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java @@ -0,0 +1,33 @@ +/* + * + * * Copyright 2022 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package jakarta.servlet.jsp; + +import com.newrelic.agent.bridge.AgentBridge; +import com.newrelic.api.agent.Trace; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; +import com.nr.agent.instrumentation.jsp4.JspUtils; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Weave(type = MatchType.Interface) +public class HttpJspPage { + + @Trace + public void _jspService(HttpServletRequest request, HttpServletResponse response) { + JspUtils.setTransactionName(getClass(), AgentBridge.getAgent().getTracedMethod()); + + Weaver.callOriginal(); + + Object exception = request.getAttribute("jakarta.servlet.jsp.jspException"); + if (exception instanceof Throwable) { + AgentBridge.privateApi.reportException((Throwable) exception); + } + } +} diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java new file mode 100644 index 0000000000..92baefab9b --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java @@ -0,0 +1,16 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ +package jakarta.servlet.jsp.el; + +import com.newrelic.api.agent.weaver.SkipIfPresent; + +/** + * Prevent the jsp-4 module from applying if this interface is present. + */ +@SkipIfPresent +public interface VariableResolver { +} diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java new file mode 100644 index 0000000000..0f09f41fc7 --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package jakarta.servlet.jsp.tagext; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.nr.agent.instrumentation.jsp4.JspContextWrapper; +import com.nr.agent.instrumentation.jsp4.JspUtils; +import com.nr.agent.instrumentation.jsp4.JspWriterWrapper; + +import jakarta.servlet.jsp.JspContext; + +@Weave(type = MatchType.ExactClass, originalName = "jakarta.servlet.jsp.tagext.SimpleTagSupport") +public class SimpleTagSupport_Instrumentation { + private JspContext jspContext; + + public void setJspContext(JspContext jspContext) { + if (JspUtils.isTagLibInstrumentationEnabled()) { + JspWriterWrapper wrapperWriter = new JspWriterWrapper(jspContext.getOut()); + this.jspContext = new JspContextWrapper(jspContext, wrapperWriter); + } else { + this.jspContext = jspContext; + } + } +} diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java new file mode 100644 index 0000000000..387a18f58a --- /dev/null +++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java @@ -0,0 +1,30 @@ +/* + * + * * Copyright 2024 New Relic Corporation. All rights reserved. + * * SPDX-License-Identifier: Apache-2.0 + * + */ + +package jakarta.servlet.jsp.tagext; + +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.nr.agent.instrumentation.jsp4.JspUtils; +import com.nr.agent.instrumentation.jsp4.JspWriterWrapper; +import com.nr.agent.instrumentation.jsp4.PageContextWrapper; + +import jakarta.servlet.jsp.PageContext; + +@Weave(type = MatchType.ExactClass, originalName = "jakarta.servlet.jsp.tagext.TagSupport") +public class TagSupport_Instrumentation { + protected transient PageContext pageContext; + + public void setPageContext(PageContext pageContext) { + if (JspUtils.isTagLibInstrumentationEnabled()) { + JspWriterWrapper wrapperWriter = new JspWriterWrapper(pageContext.getOut()); + this.pageContext = new PageContextWrapper(pageContext, wrapperWriter); + } else { + this.pageContext = pageContext; + } + } +} diff --git a/settings.gradle b/settings.gradle index b3838b6793..a81724e4e0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -224,6 +224,7 @@ include 'instrumentation:jms-1.1' include 'instrumentation:jms-3' include 'instrumentation:jsp-2.4' include 'instrumentation:jsp-3' +include 'instrumentation:jsp-4' include 'instrumentation:kafka-clients-config-1.1.0' include 'instrumentation:kafka-clients-heartbeat-0.10.1.0' include 'instrumentation:kafka-clients-heartbeat-2.1.0'