All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.undefinedlabs.scope.rules.HttpServletScopeAgentRule Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 0.15.1-beta.2
Show newest version
package com.undefinedlabs.scope.rules;

import com.undefinedlabs.scope.ScopeGlobalTracer;
import com.undefinedlabs.scope.ScopeSpanContainer;
import com.undefinedlabs.scope.coverage.*;
import com.undefinedlabs.scope.coverage.extractor.model.ScopeCoverageData;
import com.undefinedlabs.scope.events.EventFieldsFactory;
import com.undefinedlabs.scope.events.exception.ThrowableEvent;
import com.undefinedlabs.scope.jdk.reflection.ReflectionContext;
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 net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import com.undefinedlabs.scope.deps.org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

public class HttpServletScopeAgentRule extends AbstractScopeAgentRule {

    @Override
    protected Iterable 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 Object servlet, @Advice.AllArguments 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));

                boolean useHeadersCtx = headersParentSpanCtx.equals(com.undefinedlabs.scope.SpanContext.EMPTY);
                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)) {
                    final Object inputStreamObj = getRequestInputStream.invoke(request);
                    final String requestStr = (inputStreamObj) != null ? ScopeIOUtils.getSubstring((InputStream) inputStreamObj) : "";
                    span.setTag(TagKeys.Network.HTTP_REQUEST_PAYLOAD, requestStr);
                    span.setTag(TagKeys.Network.HTTP_RESPONSE_PAYLOAD_UNAVAILABLE, (requestStr != null) ? null : TagValues.Network.HTTP_PAYLOAD_NOT_ACCESSIBLE);
                } else {
                    span.setTag(TagKeys.Network.HTTP_RESPONSE_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 (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class)
        public static void exit(@Advice.This Object servlet, @Advice.AllArguments Object[] args, @Advice.Enter Object scopeSpanContainerObj, @Advice.Thrown 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 (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 (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy