
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 extends AgentBuilder> 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