/* * Copyright 2014 NAVER Corp. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.navercorp.pinpoint.plugin.jetty.interceptor; import com.navercorp.pinpoint.bootstrap.config.Filter; import com.navercorp.pinpoint.bootstrap.context.*; import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; import com.navercorp.pinpoint.bootstrap.logging.PLogger; import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory; import com.navercorp.pinpoint.bootstrap.sampler.SamplingFlagUtils; import com.navercorp.pinpoint.bootstrap.util.NetworkUtils; import com.navercorp.pinpoint.bootstrap.util.NumberUtils; import com.navercorp.pinpoint.common.trace.AnnotationKey; import com.navercorp.pinpoint.common.trace.ServiceType; import com.navercorp.pinpoint.common.util.StringUtils; import com.navercorp.pinpoint.plugin.jetty.JettyConstants; import com.navercorp.pinpoint.plugin.jetty.JettySyncMethodDescriptor; import org.eclipse.jetty.server.Request; import java.util.Enumeration; public abstract class AbstractServerHandleInterceptor implements AroundInterceptor { public static final JettySyncMethodDescriptor JETTY_SYNC_API_TAG = new JettySyncMethodDescriptor(); protected PLogger logger = PLoggerFactory.getLogger(this.getClass()); private final boolean isDebug = logger.isDebugEnabled(); private final boolean isTrace = logger.isTraceEnabled(); private final MethodDescriptor methodDescriptor; private final TraceContext traceContext; private final Filter<String> excludeUrlFilter; public AbstractServerHandleInterceptor(TraceContext traceContext, MethodDescriptor descriptor, Filter<String> excludeFilter) { this.traceContext = traceContext; this.methodDescriptor = descriptor; this.excludeUrlFilter = excludeFilter; traceContext.cacheApi(JETTY_SYNC_API_TAG); } protected abstract Request getRequest(Object[] args); @Override public void before(Object target, Object[] args) { if (isDebug) { logger.beforeInterceptor(target, args); } try { final Trace trace = createTrace(target, args); if (trace == null) { return; } // TODO STATDISABLE this logic was added to disable statistics tracing if (!trace.canSampled()) { return; } // ------------------------------------------------------ SpanEventRecorder recorder = trace.traceBlockBegin(); recorder.recordServiceType(JettyConstants.JETTY_METHOD); } catch (Throwable th) { if (logger.isWarnEnabled()) { logger.warn("before. Caused:{}", th.getMessage(), th); } } } private Trace createTrace(Object target, Object[] args) { final Request request = getRequest(args); final String requestURI = request.getRequestURI(); if (excludeUrlFilter.filter(requestURI)) { if (isTrace) { logger.trace("filter requestURI:{}", requestURI); } return null; } // check sampling flag from client. If the flag is false, do not sample this request. final boolean sampling = samplingEnable(request); if (!sampling) { // Even if this transaction is not a sampling target, we have to create Trace object to mark 'not sampling'. // For example, if this transaction invokes rpc call, we can add parameter to tell remote node 'don't sample this transaction' final Trace trace = traceContext.disableSampling(); if (isDebug) { logger.debug("remotecall sampling flag found. skip trace requestUrl:{}, remoteAddr:{}", request.getRequestURI(), request.getRemoteAddr()); } return trace; } final TraceId traceId = populateTraceIdFromRequest(request); if (traceId != null) { final Trace trace = traceContext.continueTraceObject(traceId); if (trace.canSampled()) { SpanRecorder recorder = trace.getSpanRecorder(); recordRootSpan(recorder, request); if (isDebug) { logger.debug("TraceID exist. continue trace. traceId:{}, requestUrl:{}, remoteAddr:{}", traceId, request.getRequestURI(), request.getRemoteAddr()); } } else { if (isDebug) { logger.debug("TraceID exist. camSampled is false. skip trace. traceId:{}, requestUrl:{}, remoteAddr:{}", traceId, request.getRequestURI(), request.getRemoteAddr()); } } return trace; } else { final Trace trace = traceContext.newTraceObject(); if (trace.canSampled()) { SpanRecorder recorder = trace.getSpanRecorder(); recordRootSpan(recorder, request); if (isDebug) { logger.debug("TraceID not exist. start new trace. requestUrl:{}, remoteAddr:{}", request.getRequestURI(), request.getRemoteAddr()); } } else { if (isDebug) { logger.debug("TraceID not exist. camSampled is false. skip trace. requestUrl:{}, remoteAddr:{}", request.getRequestURI(), request.getRemoteAddr()); } } return trace; } } @Override public void after(Object target, Object[] args, Object result, Throwable throwable) { if (isDebug) { logger.afterInterceptor(target, args, result, throwable); } final Trace trace = traceContext.currentRawTraceObject(); if (trace == null) { return; } // TODO STATDISABLE this logic was added to disable statistics tracing if (!trace.canSampled()) { traceContext.removeTraceObject(); return; } // ------------------------------------------------------ try { SpanEventRecorder recorder = trace.currentSpanEventRecorder(); final Request request = getRequest(args); final String parameters = getRequestParameter(request, 64, 512); if (StringUtils.isNotEmpty(parameters)) { recorder.recordAttribute(AnnotationKey.HTTP_PARAM, parameters); } recorder.recordApi(methodDescriptor); recorder.recordException(throwable); } catch (Throwable th) { if (logger.isWarnEnabled()) { logger.warn("after. Caused:{}", th.getMessage(), th); } } finally { traceContext.removeTraceObject(); deleteTrace(trace, target, args, result, throwable); } } private boolean samplingEnable(Request request) { // optional value final String samplingFlag = request.getHeader(Header.HTTP_SAMPLED.toString()); if (isDebug) { logger.debug("SamplingFlag:{}", samplingFlag); } return SamplingFlagUtils.isSamplingFlag(samplingFlag); } private String getRequestParameter(Request request, int eachLimit, int totalLimit) { Enumeration<?> attrs = request.getParameterNames(); final StringBuilder params = new StringBuilder(64); while (attrs.hasMoreElements()) { if (params.length() != 0) { params.append('&'); } // skip appending parameters if parameter size is bigger than totalLimit if (params.length() > totalLimit) { params.append("..."); return params.toString(); } String key = attrs.nextElement().toString(); params.append(StringUtils.abbreviate(key, eachLimit)); params.append("="); Object value = request.getParameter(key); if (value != null) { params.append(StringUtils.abbreviate(StringUtils.toString(value), eachLimit)); } } return params.toString(); } private void recordParentInfo(SpanRecorder recorder, Request request) { String parentApplicationName = request.getHeader(Header.HTTP_PARENT_APPLICATION_NAME.toString()); if (parentApplicationName != null) { final String host = request.getHeader(Header.HTTP_HOST.toString()); if (host != null) { recorder.recordAcceptorHost(host); } else { recorder.recordAcceptorHost(NetworkUtils.getHostFromURL(request.getRequestURL().toString())); } final String type = request.getHeader(Header.HTTP_PARENT_APPLICATION_TYPE.toString()); final short parentApplicationType = NumberUtils.parseShort(type, ServiceType.UNDEFINED.getCode()); recorder.recordParentApplication(parentApplicationName, parentApplicationType); } } private void recordRootSpan(final SpanRecorder recorder, final Request request) { // root recorder.recordServiceType(JettyConstants.JETTY); final String requestURL = request.getRequestURI(); recorder.recordRpcName(requestURL); final int port = request.getServerPort(); final String endPoint = request.getServerName() + ":" + port; recorder.recordEndPoint(endPoint); final String remoteAddr = request.getRemoteAddr(); recorder.recordRemoteAddress(remoteAddr); if (!recorder.isRoot()) { recordParentInfo(recorder, request); } recorder.recordApi(JETTY_SYNC_API_TAG); } /** * Populate source trace from HTTP Header. * * @param request * @return TraceId when it is possible to get a transactionId from Http header. if not possible return null */ private TraceId populateTraceIdFromRequest(Request request) { String transactionId = request.getHeader(Header.HTTP_TRACE_ID.toString()); if (transactionId != null) { long parentSpanID = NumberUtils.parseLong(request.getHeader(Header.HTTP_PARENT_SPAN_ID.toString()), SpanId.NULL); long spanID = NumberUtils.parseLong(request.getHeader(Header.HTTP_SPAN_ID.toString()), SpanId.NULL); short flags = NumberUtils.parseShort(request.getHeader(Header.HTTP_FLAGS.toString()), (short) 0); final TraceId id = traceContext.createTraceId(transactionId, parentSpanID, spanID, flags); if (isDebug) { logger.debug("TraceID exist. continue trace. {}", id); } return id; } else { return null; } } private void deleteTrace(Trace trace, Object target, Object[] args, Object result, Throwable throwable) { trace.traceBlockEnd(); trace.close(); } }