com.undefinedlabs.scope.rules.HttpServletScopeAgentRule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scope-rule-javax-servlet Show documentation
Show all versions of scope-rule-javax-servlet Show documentation
Scope is a APM for tests to give engineering teams unprecedented visibility into their CI process to quickly
identify, troubleshoot and fix failed builds.
This artifact contains the classes to instrument the Javax Servlet package.
package com.undefinedlabs.scope.rules;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.undefinedlabs.scope.ScopeGlobalTracer;
import com.undefinedlabs.scope.ScopeSpanContainer;
import com.undefinedlabs.scope.coverage.CoverageSession;
import com.undefinedlabs.scope.coverage.CoverageSessionAnalyzer;
import com.undefinedlabs.scope.coverage.CoverageSessionInfo;
import com.undefinedlabs.scope.coverage.CoverageSessionReport;
import com.undefinedlabs.scope.coverage.GlobalCoverageReporter;
import com.undefinedlabs.scope.coverage.ScopeCoverageExtractor;
import com.undefinedlabs.scope.coverage.extractor.model.ScopeCoverageData;
import com.undefinedlabs.scope.deps.org.apache.commons.lang3.StringUtils;
import com.undefinedlabs.scope.events.EventFieldsFactory;
import com.undefinedlabs.scope.events.exception.ThrowableEvent;
import com.undefinedlabs.scope.jdk.reflection.ReflectionContext;
import com.undefinedlabs.scope.logger.ScopeLogger;
import com.undefinedlabs.scope.logger.ScopeLoggerResolver;
import com.undefinedlabs.scope.propagation.carriers.TextMapCarrierAdapter;
import com.undefinedlabs.scope.rules.headers.ServletHttpHeadersAdapter;
import com.undefinedlabs.scope.rules.http.HttpHeadersExtractorForSpan;
import com.undefinedlabs.scope.rules.transformer.ScopeAgentAdvicedTransformer;
import com.undefinedlabs.scope.settings.ScopeSettings;
import com.undefinedlabs.scope.settings.ScopeSettingsResolver;
import com.undefinedlabs.scope.utils.ScopeIOUtils;
import com.undefinedlabs.scope.utils.SpanUtils;
import com.undefinedlabs.scope.utils.baggage.BaggageKeys;
import com.undefinedlabs.scope.utils.event.EventValues;
import com.undefinedlabs.scope.utils.sourcecode.ExceptionSourceCodeFactory;
import com.undefinedlabs.scope.utils.sourcecode.ExceptionSourceCodeFrame;
import com.undefinedlabs.scope.utils.tag.TagKeys;
import com.undefinedlabs.scope.utils.tag.TagValues;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.propagation.Format;
import io.opentracing.tag.Tags;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
public class HttpServletScopeAgentRule extends AbstractScopeAgentRule {
@Override
protected Iterable extends AgentBuilder> transformers() {
return Collections.singleton(
newAgentBuilder()
.type(named("javax.servlet.http.HttpServlet"))
.transform(
new ScopeAgentAdvicedTransformer(
HttpServletServiceAdvice.class,
named("service")
.and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))))));
}
public static class HttpServletServiceAdvice {
@Advice.OnMethodEnter
public static ScopeSpanContainer enter(
@Advice.This final Object servlet, @Advice.AllArguments final Object[] args) {
if (args.length == 0) {
return null;
}
try {
final ReflectionContext reflectionContext = ReflectionContext.INSTANCE;
final Object request = args[0];
final String requestClassName = request.getClass().getName();
final ClassLoader requestClassLoader = request.getClass().getClassLoader();
final Method getRequestMethodMethod =
reflectionContext.getScopeMethod(requestClassName, requestClassLoader, "getMethod");
final Method getQueryStringMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getQueryString");
final Method getRequestUrlMethod =
reflectionContext.getScopeMethod(requestClassName, requestClassLoader, "getRequestURL");
final Method getRemoteHostMethod =
reflectionContext.getScopeMethod(requestClassName, requestClassLoader, "getRemoteHost");
final Method getRemoteAddrMethod =
reflectionContext.getScopeMethod(requestClassName, requestClassLoader, "getRemoteAddr");
final Method getRemotePortMethod =
reflectionContext.getScopeMethod(requestClassName, requestClassLoader, "getRemotePort");
final Method getRequestInputStream =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getInputStream");
final Method getRequestHeaderNamesMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getHeaderNames");
final Method getRequestHeaderMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getHeader", String.class);
final Enumeration reqHeaderNames =
(Enumeration) getRequestHeaderNamesMethod.invoke(request);
final Map reqHeaders = new HashMap<>();
while (reqHeaderNames.hasMoreElements()) {
final String headerName = reqHeaderNames.nextElement();
reqHeaders.put(headerName, (String) getRequestHeaderMethod.invoke(request, headerName));
}
final Method getAttributesNamesMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getAttributeNames");
final Method gettAtributeMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getAttribute", String.class);
final Enumeration attributesNames =
(Enumeration) getAttributesNamesMethod.invoke(request);
final Map attributes = new HashMap<>();
while (attributesNames.hasMoreElements()) {
final String attributeName = attributesNames.nextElement();
final Object attributeValue = gettAtributeMethod.invoke(request, attributeName);
if (attributeValue instanceof String) {
attributes.put(attributeName, (String) attributeValue);
}
}
final Tracer tracer = ScopeGlobalTracer.get();
final SpanContext headersParentSpanCtx =
tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapCarrierAdapter(reqHeaders));
final SpanContext attributesParentSpanCtx =
tracer.extract(Format.Builtin.TEXT_MAP, new TextMapCarrierAdapter(attributes));
final boolean useHeadersCtx =
headersParentSpanCtx.equals(com.undefinedlabs.scope.SpanContext.EMPTY);
final boolean useAttrsCtx =
!attributesParentSpanCtx.equals(com.undefinedlabs.scope.SpanContext.EMPTY);
final SpanContext parentSpanCtx =
(useAttrsCtx) ? attributesParentSpanCtx : (useHeadersCtx) ? headersParentSpanCtx : null;
final String requestMethod = (String) getRequestMethodMethod.invoke(request);
final String queryString = (String) getQueryStringMethod.invoke(request);
final String requestUrl = ((StringBuffer) getRequestUrlMethod.invoke(request)).toString();
final String remoteHost = (String) getRemoteHostMethod.invoke(request);
final String remoteAddr = (String) getRemoteAddrMethod.invoke(request);
final int remotePort = (int) getRemotePortMethod.invoke(request);
final String operationName = String.format("HTTP %s", requestMethod);
final Tracer.SpanBuilder spanBuilder =
(parentSpanCtx == null)
? tracer.buildSpan(operationName)
: tracer.buildSpan(operationName).asChildOf(parentSpanCtx);
final Span span =
spanBuilder
.withTag(TagKeys.SPAN_KIND, Tags.SPAN_KIND_SERVER)
.withTag(TagKeys.COMPONENT, TagValues.Component.SERVLET)
.withTag(
TagKeys.Network.HTTP_URL,
(StringUtils.isNotEmpty(queryString)
? String.format("%s?%s", requestUrl, queryString)
: requestUrl))
.withTag(TagKeys.Network.HTTP_METHOD, requestMethod)
.withTag(TagKeys.Network.PEER_HOSTNAME, remoteHost)
.withTag(TagKeys.Network.PEER_IPV4, remoteAddr)
.withTag(
TagKeys.Network.PEER_ADDRESS,
String.format("%s:%s", remoteAddr, String.valueOf(remotePort)))
.withTag(TagKeys.Network.PEER_PORT, remotePort)
.withTag(TagKeys.Network.PEER_SERVICE, TagValues.Network.Service.HTTP)
.start();
final Map httpReqHeaders =
HttpHeadersExtractorForSpan.DEFAULT.extract(new ServletHttpHeadersAdapter(reqHeaders));
SpanUtils.INSTANCE.setTagObject(span, TagKeys.Network.HTTP_REQUEST_HEADERS, httpReqHeaders);
if ((boolean)
ScopeSettingsResolver.INSTANCE
.get()
.getSetting(ScopeSettings.SCOPE_INSTRUMENTATION_HTTP_PAYLOADS)) {
if ("GET".equalsIgnoreCase(requestMethod)) {
span.setTag(TagKeys.Network.HTTP_REQUEST_PAYLOAD, "");
} else {
final Method getRequestContentLengthLong =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getContentLengthLong");
final long contentLength = (long) getRequestContentLengthLong.invoke(request);
if (contentLength == 0) {
span.setTag(TagKeys.Network.HTTP_REQUEST_PAYLOAD, "");
} else {
final Object inputStreamObj = getRequestInputStream.invoke(request);
final String requestStr =
(inputStreamObj) != null && ((InputStream) inputStreamObj).markSupported()
? ScopeIOUtils.getSubstring((InputStream) inputStreamObj)
: "";
span.setTag(
TagKeys.Network.HTTP_REQUEST_PAYLOAD,
(StringUtils.isNoneEmpty(requestStr) ? requestStr : null));
span.setTag(
TagKeys.Network.HTTP_REQUEST_PAYLOAD_UNAVAILABLE,
StringUtils.isNotEmpty(requestStr)
? null
: TagValues.Network.HTTP_PAYLOAD_NOT_ACCESSIBLE);
}
}
} else {
span.setTag(
TagKeys.Network.HTTP_REQUEST_PAYLOAD_UNAVAILABLE,
TagValues.Network.HTTP_PAYLOAD_DISABLED);
}
final boolean coverageInPreviousSpan =
Boolean.parseBoolean(span.getBaggageItem(BaggageKeys.COVERAGE_ENABLED));
final boolean coverageIsConfigured =
(boolean)
ScopeSettingsResolver.INSTANCE
.get()
.getSetting(ScopeSettings.SCOPE_CODE_PATH_ENABLED);
if ((coverageInPreviousSpan || coverageIsConfigured)) {
GlobalCoverageReporter.get().startSession();
}
tracer.activateSpan(span);
return new ScopeSpanContainer(span, tracer.activateSpan(span));
} catch (final Exception e) {
final ScopeLogger logger = ScopeLoggerResolver.INSTANCE.get();
logger.error("Error on javax.servlet.http.HttpServlet span: ");
logger.error("\t\t Servlet: " + servlet + ", Request: " + args[0]);
Throwable cause = e;
while (cause != null) {
logger.error("\t\t Cause: " + cause);
cause = cause.getCause();
}
return null;
}
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void exit(
@Advice.This final Object servlet,
@Advice.AllArguments final Object[] args,
@Advice.Enter final Object scopeSpanContainerObj,
@Advice.Thrown final Throwable throwable) {
if (args.length == 0 || scopeSpanContainerObj == null) {
return;
}
try {
final ScopeSpanContainer scopeSpanContainer = (ScopeSpanContainer) scopeSpanContainerObj;
final Span span = scopeSpanContainer.getSpan();
final Scope scope = scopeSpanContainer.getScope();
final ReflectionContext reflectionContext = ReflectionContext.INSTANCE;
final Object request = args[0];
final String requestClassName = request.getClass().getName();
final ClassLoader requestClassLoader = request.getClass().getClassLoader();
final Method getAttributeMethod =
reflectionContext.getScopeMethod(
requestClassName, requestClassLoader, "getAttribute", String.class);
final Object response = args[1];
final String responseClassName = response.getClass().getName();
final ClassLoader responseClassLoader = response.getClass().getClassLoader();
final Method getResponseHeaderNamesMethod =
reflectionContext.getScopeMethod(
responseClassName, responseClassLoader, "getHeaderNames");
final Method getResponseHeaderMethod =
reflectionContext.getScopeMethod(
responseClassName, responseClassLoader, "getHeader", String.class);
final Collection resHeaderNames =
(Collection) getResponseHeaderNamesMethod.invoke(response);
final Map resHeaders = new HashMap<>();
for (final String resHeaderName : resHeaderNames) {
resHeaders.put(
resHeaderName, (String) getResponseHeaderMethod.invoke(response, resHeaderName));
}
final Map httpResHeaders =
HttpHeadersExtractorForSpan.DEFAULT.extract(new ServletHttpHeadersAdapter(resHeaders));
SpanUtils.INSTANCE.setTagObject(
span, TagKeys.Network.HTTP_RESPONSE_HEADERS, httpResHeaders);
// #####
// The http.response_payload tag cannot be extracted from ServletResponse. This object is
// only to write bytes.
// #####
span.setTag(
TagKeys.Network.HTTP_RESPONSE_PAYLOAD_UNAVAILABLE,
((boolean)
ScopeSettingsResolver.INSTANCE
.get()
.getSetting(ScopeSettings.SCOPE_INSTRUMENTATION_HTTP_PAYLOADS)
? TagValues.Network.HTTP_PAYLOAD_NOT_ACCESSIBLE
: TagValues.Network.HTTP_PAYLOAD_DISABLED));
final Method getStatusMethod =
reflectionContext.getScopeMethod(responseClassName, responseClassLoader, "getStatus");
final int statusCode = (int) getStatusMethod.invoke(response);
span.setTag(TagKeys.Network.HTTP_STATUS_CODE, statusCode);
final Throwable requestThrowable =
(Throwable) getAttributeMethod.invoke(request, "javax.servlet.error.exception");
if (throwable == null && requestThrowable == null) {
span.setTag(TagKeys.ERROR, (statusCode >= 400));
} else {
span.setTag(TagKeys.ERROR, true);
final ExceptionSourceCodeFrame exceptionSourceCodeFrame =
ExceptionSourceCodeFactory.INSTANCE.createFrame(
throwable != null ? throwable : requestThrowable);
final ThrowableEvent.Builder throwableEventBuilder = ThrowableEvent.newBuilder();
throwableEventBuilder
.withEventType(EventValues.General.ERROR)
.withThrowable(exceptionSourceCodeFrame.getUserThrowable())
.withSource(
exceptionSourceCodeFrame.getSourceCodeFrame().getLinkPathWithMethodLine());
span.log(EventFieldsFactory.INSTANCE.createFields(throwableEventBuilder.build()));
}
final boolean coverageInPreviousSpan =
Boolean.parseBoolean(span.getBaggageItem(BaggageKeys.COVERAGE_ENABLED));
final boolean coverageIsConfigured =
(boolean)
ScopeSettingsResolver.INSTANCE
.get()
.getSetting(ScopeSettings.SCOPE_CODE_PATH_ENABLED);
final CoverageSession currentCoverageSession = GlobalCoverageReporter.get().activeSession();
if ((coverageInPreviousSpan || coverageIsConfigured) && currentCoverageSession != null) {
final CoverageSessionInfo coverageSessionInfo = currentCoverageSession.close();
final CoverageSessionReport coverageReport =
CoverageSessionAnalyzer.INSTANCE.analyze(coverageSessionInfo);
final ScopeCoverageData coverageData =
ScopeCoverageExtractor.INSTANCE.analyze(coverageReport);
SpanUtils.INSTANCE.setTagObject(span, TagKeys.Service.SERVICE_COVERAGE, coverageData);
}
span.finish();
scope.close();
} catch (final Exception e) {
final ScopeLogger logger = ScopeLoggerResolver.INSTANCE.get();
logger.error("Error on creating javax.servlet.http.HttpServlet span: ");
logger.error("\t\t Servlet: " + servlet + ", Request: " + args[0]);
Throwable cause = e;
while (cause != null) {
logger.error("\t\t Cause: " + cause);
cause = cause.getCause();
}
}
}
}
}