package org.skywalking.apm.plugin.dubbo; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcContext; import org.skywalking.apm.agent.core.context.ContextCarrier; import org.skywalking.apm.agent.core.context.ContextManager; import org.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext; import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodInvokeContext; import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.skywalking.apm.plugin.dubbox.BugFixActive; import org.skywalking.apm.plugin.dubbox.SWBaseBean; import org.skywalking.apm.trace.Span; import org.skywalking.apm.trace.tag.Tags; /** * {@link DubboInterceptor} define how to enhance class {@link com.alibaba.dubbo.monitor.support.MonitorFilter#invoke(Invoker, * Invocation)}. the trace context transport to the provider side by {@link RpcContext#attachments}.but all the version * of dubbo framework below 2.8.3 don't support {@link RpcContext#attachments}, we support another way to support it. it * is that all request parameters of dubbo service need to extend {@link SWBaseBean}, and {@link DubboInterceptor} will * inject the trace context data to the {@link SWBaseBean} bean and extract the trace context data from {@link * SWBaseBean}, or the trace context data will not transport to the provider side. * * @author zhangxin */ public class DubboInterceptor implements InstanceMethodsAroundInterceptor { public static final String ATTACHMENT_NAME_OF_CONTEXT_DATA = "SWTraceContext"; public static final String DUBBO_COMPONENT = "Dubbo"; /** * <h2>Consumer:</h2> The serialized trace context data will inject the first param that extend {@link SWBaseBean} * of dubbo service if the method {@link BugFixActive#active()} be called. or the serialized context data will * inject to the {@link RpcContext#attachments} for transport to provider side. * <p> * <h2>Provider:</h2> The serialized trace context data will extract from the first param that extend {@link * SWBaseBean} of dubbo service if the method {@link BugFixActive#active()} be called. or it will extract from * {@link RpcContext#attachments}. current trace segment will ref if the serialize context data is not null. */ @Override public void beforeMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, MethodInterceptResult result) { Object[] arguments = interceptorContext.allArguments(); Invoker invoker = (Invoker) arguments[0]; Invocation invocation = (Invocation) arguments[1]; RpcContext rpcContext = RpcContext.getContext(); boolean isConsumer = rpcContext.isConsumerSide(); URL requestURL = invoker.getUrl(); Span span = ContextManager.createSpan(generateOperationName(requestURL, invocation)); Tags.URL.set(span, generateRequestURL(requestURL, invocation)); Tags.COMPONENT.set(span, DUBBO_COMPONENT); Tags.SPAN_LAYER.asRPCFramework(span); Tags.PEER_HOST.set(span, requestURL.getHost()); Tags.PEER_PORT.set(span, requestURL.getPort()); if (isConsumer) { Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); ContextCarrier contextCarrier = new ContextCarrier(); ContextManager.inject(contextCarrier); if (!BugFixActive.isActive()) { //invocation.getAttachments().put("contextData", contextDataStr); //@see https://github.com/alibaba/dubbo/blob/dubbo-2.5.3/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/RpcInvocation.java#L154-L161 rpcContext.getAttachments().put(ATTACHMENT_NAME_OF_CONTEXT_DATA, contextCarrier.serialize()); } else { fix283SendNoAttachmentIssue(invocation, contextCarrier); } } else { Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_SERVER); ContextCarrier contextCarrier; if (!BugFixActive.isActive()) { contextCarrier = new ContextCarrier().deserialize(rpcContext.getAttachment(ATTACHMENT_NAME_OF_CONTEXT_DATA)); } else { contextCarrier = fix283RecvNoAttachmentIssue(invocation); } if (contextCarrier != null) { ContextManager.extract(contextCarrier); } } } /** * Execute after {@link com.alibaba.dubbo.monitor.support.MonitorFilter#invoke(Invoker, Invocation)}, * when dubbo instrumentation is active. Check {@link Result#getException()} , if not NULL, * log the exception and set tag error=true. */ @Override public Object afterMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, Object ret) { Result result = (Result) ret; if (result != null && result.getException() != null) { dealException(result.getException()); } ContextManager.stopSpan(); return ret; } @Override public void handleMethodException(Throwable t, EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext) { dealException(t); } /** * Log the throwable, which occurs in Dubbo RPC service. */ private void dealException(Throwable throwable) { Span span = ContextManager.activeSpan(); Tags.ERROR.set(span, true); span.log(throwable); } /** * Format operation name. e.g. org.skywalking.apm.plugin.test.Test.test(String) * * @return operation name. */ private String generateOperationName(URL requestURL, Invocation invocation) { StringBuilder operationName = new StringBuilder(); operationName.append(requestURL.getPath()); operationName.append("." + invocation.getMethodName() + "("); for (Class<?> classes : invocation.getParameterTypes()) { operationName.append(classes.getSimpleName() + ","); } if (invocation.getParameterTypes().length > 0) { operationName.delete(operationName.length() - 1, operationName.length()); } operationName.append(")"); return operationName.toString(); } /** * Format request url. * e.g. dubbo://127.0.0.1:20880/org.skywalking.apm.plugin.test.Test.test(String). * * @return request url. */ private String generateRequestURL(URL url, Invocation invocation) { StringBuilder requestURL = new StringBuilder(); requestURL.append(url.getProtocol() + "://"); requestURL.append(url.getHost()); requestURL.append(":" + url.getPort() + "/"); requestURL.append(generateOperationName(url, invocation)); return requestURL.toString(); } /** * Set the trace context. * * @param contextCarrier {@link ContextCarrier}. */ private void fix283SendNoAttachmentIssue(Invocation invocation, ContextCarrier contextCarrier) { for (Object parameter : invocation.getArguments()) { if (parameter instanceof SWBaseBean) { ((SWBaseBean) parameter).setTraceContext(contextCarrier.serialize()); return; } } } /** * Fetch the trace context by using {@link Invocation#getArguments()}. * * @return trace context data. */ private ContextCarrier fix283RecvNoAttachmentIssue(Invocation invocation) { for (Object parameter : invocation.getArguments()) { if (parameter instanceof SWBaseBean) { return new ContextCarrier().deserialize(((SWBaseBean) parameter).getTraceContext()); } } return null; } }