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 static com.undefinedlabs.scope.agent.ScopeClassLoaderMatcher.hasClassesNamed;
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.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.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.Enumeration;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.commons.lang3.StringUtils;

public class HttpServletScopeAgentRule extends AbstractScopeAgentRule {

  @Override
  protected ElementMatcher typeMatcher() {
    return named("javax.servlet.http.HttpServlet");
  }

  @Override
  protected ElementMatcher classLoaderMatcher() {
    return hasClassesNamed("javax.servlet.http.HttpServlet");
  }

  @Override
  protected Map, String> transformers() {
    final Map, String> transformers = new HashMap<>();
    transformers.put(
        named("service").and(takesArgument(0, named("javax.servlet.http.HttpServletRequest"))),
        HttpServletScopeAgentRule.class.getName() + "$HttpServletServiceAdvice");
    return transformers;
  }

  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();
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy