-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1951 from newrelic/jsp-4-instrumentation
JSP v4 module
- Loading branch information
Showing
11 changed files
with
737 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 `<head>` 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 `<head>` 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: '<head.*>' | ||
``` | ||
The regular expression will be compiled to be case-insensitive. If the defined regular expression is invalid it will default to `<head>`. | ||
|
||
|
||
### 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 `<head>` 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 `<head>` 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 (`<head>`) must be totally emitted in a single `print`/`println` call. For example, these are all valid: | ||
```java | ||
out.println("<head>"); | ||
out.print("<head>"); | ||
out.println("<head><title>Test Site</title></head>"); | ||
out.println("<head><title>"); | ||
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</title>" + | ||
"</head>"); | ||
} | ||
} | ||
``` | ||
|
||
##### JSP | ||
```html | ||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> | ||
<%@ taglib prefix="simple" uri="http://com.sun.simpletagexample"%> | ||
<!DOCTYPE html> | ||
<html> | ||
<simple:SimpleTag/> | ||
|
||
<body> | ||
<h1>Sample JSP</h1> | ||
</body> | ||
</html> | ||
``` | ||
|
||
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 | ||
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script type="text/javascript"><!-- RUM script here --></script> | ||
<meta charset="ISO-8859-1"> | ||
<title>Test Site</title></head> | ||
|
||
<body> | ||
<h1>Sample JSP</h1> | ||
</body> | ||
</html> | ||
``` | ||
|
||
### 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,)' | ||
} |
77 changes: 77 additions & 0 deletions
77
instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String> getAttributeNamesInScope(int scope) { | ||
return wrappedContext.getAttributeNamesInScope(scope); | ||
} | ||
|
||
@Override | ||
public JspWriter getOut() { | ||
return wrappedWriter; | ||
} | ||
|
||
@Override | ||
public jakarta.el.ELContext getELContext() { | ||
return wrappedContext.getELContext(); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = "<head>"; | ||
|
||
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; | ||
} | ||
} |
Oops, something went wrong.