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

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

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.propagation.carriers.ServletRequestAttrsInjectAdapter;
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 org.apache.commons.lang3.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.*;

import static net.bytebuddy.matcher.ElementMatchers.*;

public class CatalinaValveScopeAgentRule extends AbstractScopeAgentRule {

    public static final String HASH_ALGORITHM = "SHA1";
    public static final String SCOPE_TOMCAT_CATALINA_INVOKE_KEY = "scope_tomcat_catalina_invoke_key";

    @Override
    protected String instrumentedClassName() {
        return "org.apache.catalina.Valve";
    }

    @Override
    protected Iterable transformers() {
        return Collections.singleton(new AgentBuilder.Default()
            .ignore(none())
            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
            .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .type(isSubTypeOf(instrumentedClass()), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(CatalinaValveInvokeAdvice.class, named("invoke")))
        );
    }

    public static class CatalinaValveInvokeAdvice {

        @Advice.OnMethodEnter
        public static ScopeSpanContainer enter(@Advice.This Object valve, @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 Method getRequestAttribute = reflectionContext.getScopeMethod(requestClassName, "getAttribute", String.class);
                final Object requestInvokeKey = getRequestAttribute.invoke(request, SCOPE_TOMCAT_CATALINA_INVOKE_KEY);

                if(requestInvokeKey != null){
                    return null;
                }

                final Method getRequestMethodMethod = reflectionContext.getScopeMethod(requestClassName, "getMethod");
                final Method getQueryStringMethod = reflectionContext.getScopeMethod(requestClassName, "getQueryString");
                final Method getRequestURLMethod = reflectionContext.getScopeMethod(requestClassName, "getRequestURL");
                final Method getRemoteHostMethod = reflectionContext.getScopeMethod(requestClassName, "getRemoteHost");
                final Method getRemoteAddrMethod = reflectionContext.getScopeMethod(requestClassName, "getRemoteAddr");
                final Method getRemotePortMethod = reflectionContext.getScopeMethod(requestClassName, "getRemotePort");
                final Method getRequestInputStream = reflectionContext.getScopeMethod(requestClassName, "getInputStream");

                final Method getRequestHeaderNamesMethod  = reflectionContext.getScopeMethod(requestClassName, "getHeaderNames");
                final Method getRequestHeaderMethod = reflectionContext.getScopeMethod(requestClassName, "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 Tracer tracer = ScopeGlobalTracer.get();
                final SpanContext parentSpanCtx = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapCarrierAdapter(reqHeaders));

                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("Tomcat:Catalina %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.TOMCAT)
                        .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();

                SpanUtils.INSTANCE.setTagObject(span, TagKeys.Network.HTTP_REQUEST_HEADERS, HttpHeadersExtractorForSpan.DEFAULT.extract(new ServletHttpHeadersAdapter(reqHeaders)));

                if((boolean)ScopeSettingsResolver.INSTANCE.get().getSetting(ScopeSettings.SCOPE_INSTRUMENTATION_HTTP_PAYLOADS)){
                    final Object inputStreamObj = getRequestInputStream.invoke(request);
                    final String requestPayload = (inputStreamObj) != null ? ScopeIOUtils.getSubstring((InputStream) inputStreamObj) : null;
                    span.setTag(TagKeys.Network.HTTP_REQUEST_PAYLOAD, requestPayload);
                    span.setTag(TagKeys.Network.HTTP_REQUEST_PAYLOAD_UNAVAILABLE, (requestPayload != null) ? 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_COVERAGE);
                /*final CoverageSession currentCoverageSession = GlobalCoverageReporter.get().activeSession();*/
                if((coverageInPreviousSpan || coverageIsConfigured)/* && currentCoverageSession == null*/){
                    GlobalCoverageReporter.get().startSession();
                }

                tracer.activateSpan(span);

                final ScopeSpanContainer scopeSpanContainer = new ScopeSpanContainer(span, tracer.activateSpan(span));
                tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new ServletRequestAttrsInjectAdapter(request));


                final String valveClassName = valve.getClass().getName();
                final String valveClassNameHash = DatatypeConverter.printBase64Binary(MessageDigest.getInstance(HASH_ALGORITHM).digest(valveClassName.getBytes()));

                final Method setAttributeMethod = reflectionContext.getScopeMethod(requestClassName, "setAttribute", String.class, Object.class);
                setAttributeMethod.invoke(request, SCOPE_TOMCAT_CATALINA_INVOKE_KEY, valveClassNameHash);

                return scopeSpanContainer;
            } catch(Exception e){
                throw new RuntimeException(e);
            }
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class)
        public static void exit(@Advice.This Object valve, @Advice.AllArguments Object[] args, @Advice.Enter Object scopeSpanContainerObj, @Advice.Thrown Throwable throwable) {

            if(args.length == 0 || scopeSpanContainerObj == null){
                return;
            }


            try {
                final ReflectionContext reflectionContext = ReflectionContext.INSTANCE;
                final Object request = args[0];
                final String requestClassName = request.getClass().getName();


                final Method getRequestAttribute = reflectionContext.getScopeMethod(requestClassName, "getAttribute", String.class);
                final Object valveClassNameHashInRequest = getRequestAttribute.invoke(request, SCOPE_TOMCAT_CATALINA_INVOKE_KEY);

                if(valveClassNameHashInRequest == null){
                    return;
                }

                final String valveClassName = valve.getClass().getName();
                final String valveClassNameHash = DatatypeConverter.printBase64Binary(MessageDigest.getInstance(HASH_ALGORITHM).digest(valveClassName.getBytes()));

                if(!valveClassNameHashInRequest.equals(valveClassNameHash)){
                    return;
                }
                final Method getAttributeMethod = reflectionContext.getScopeMethod(requestClassName, "getAttribute", String.class);

                final Object response = args[1];
                final String responseClassName = response.getClass().getName();

                final ScopeSpanContainer scopeSpanContainer = (ScopeSpanContainer) scopeSpanContainerObj;
                final Span span = scopeSpanContainer.getSpan();
                final Scope scope = scopeSpanContainer.getScope();

                final Method getResponseHeaderNamesMethod  = reflectionContext.getScopeMethod(responseClassName, "getHeaderNames");
                final Method getResponseHeaderMethod = reflectionContext.getScopeMethod(responseClassName, "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.isEmpty() ? null : httpResHeaders);

                // #####
                // The http.response_payload tag cannot be extracted from ServletResponse. This object is only to write bytes.
                // #####

                if((boolean)ScopeSettingsResolver.INSTANCE.get().getSetting(ScopeSettings.SCOPE_INSTRUMENTATION_HTTP_PAYLOADS)){
                    span.setTag(TagKeys.Network.HTTP_RESPONSE_PAYLOAD_UNAVAILABLE, TagValues.Network.HTTP_PAYLOAD_NOT_ACCESSIBLE);
                } else {
                    span.setTag(TagKeys.Network.HTTP_RESPONSE_PAYLOAD_UNAVAILABLE, TagValues.Network.HTTP_PAYLOAD_DISABLED);
                }

                final Method getStatusMethod = reflectionContext.getScopeMethod(responseClassName, "getStatus");
                final int statusCode = (int) getStatusMethod.invoke(response);

                final Throwable requestThrowable = (Throwable) getAttributeMethod.invoke(request, "javax.servlet.error.exception");

                if(throwable == null && requestThrowable == null) {
                    span.setTag(TagKeys.Network.HTTP_STATUS_CODE, statusCode);
                    span.setTag(TagKeys.ERROR, (statusCode >= 400));
                } else {
                    span.setTag(TagKeys.Network.HTTP_STATUS_CODE, (statusCode >= 400) ? statusCode : 500);
                    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_COVERAGE);
                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();



                final Method removeRequestAttribute = reflectionContext.getScopeMethod(requestClassName, "removeAttribute", String.class);
                removeRequestAttribute.invoke(request, SCOPE_TOMCAT_CATALINA_INVOKE_KEY);
            } catch(Exception e){
                throw new RuntimeException(e);
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy