/* * 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.httpclient3.interceptor; import java.util.HashMap; import java.util.Map; import com.navercorp.pinpoint.common.util.StringUtils; import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpConstants; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.EntityEnclosingMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.protocol.Protocol; import com.navercorp.pinpoint.bootstrap.config.DumpType; import com.navercorp.pinpoint.bootstrap.context.Header; import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor; import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder; import com.navercorp.pinpoint.bootstrap.context.Trace; import com.navercorp.pinpoint.bootstrap.context.TraceContext; import com.navercorp.pinpoint.bootstrap.context.TraceId; import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor; import com.navercorp.pinpoint.bootstrap.interceptor.annotation.Scope; import com.navercorp.pinpoint.bootstrap.interceptor.scope.ExecutionPolicy; import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope; import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScopeInvocation; 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.FixedByteArrayOutputStream; import com.navercorp.pinpoint.bootstrap.util.InterceptorUtils; import com.navercorp.pinpoint.bootstrap.util.SimpleSampler; import com.navercorp.pinpoint.bootstrap.util.SimpleSamplerFactory; import com.navercorp.pinpoint.common.trace.AnnotationKey; import com.navercorp.pinpoint.plugin.httpclient3.HttpClient3CallContext; import com.navercorp.pinpoint.plugin.httpclient3.HttpClient3CallContextFactory; import com.navercorp.pinpoint.plugin.httpclient3.HttpClient3Constants; import com.navercorp.pinpoint.plugin.httpclient3.HttpClient3PluginConfig; /** * @author Minwoo Jung * @author jaehong.kim */ @Scope(value = HttpClient3Constants.HTTP_CLIENT3_METHOD_BASE_SCOPE, executionPolicy = ExecutionPolicy.ALWAYS) public class HttpMethodBaseExecuteMethodInterceptor implements AroundInterceptor { private final PLogger logger = PLoggerFactory.getLogger(this.getClass()); private final boolean isDebug = logger.isDebugEnabled(); private static final int MAX_READ_SIZE = 1024; private static final Map<Integer, Integer> httpMethod_Index; static { httpMethod_Index = new HashMap<Integer, Integer>(); httpMethod_Index.put(1, 0); httpMethod_Index.put(2, 1); httpMethod_Index.put(3, 1); } private TraceContext traceContext; private MethodDescriptor descriptor; private InterceptorScope interceptorScope; private boolean param; private boolean cookie; private DumpType cookieDumpType; private SimpleSampler cookieSampler; private boolean entity; private DumpType entityDumpType; private SimpleSampler entitySampler; private boolean io; public HttpMethodBaseExecuteMethodInterceptor(TraceContext traceContext, MethodDescriptor methodDescriptor, InterceptorScope interceptorScope) { this.traceContext = traceContext; this.descriptor = methodDescriptor; this.interceptorScope = interceptorScope; final HttpClient3PluginConfig config = new HttpClient3PluginConfig(traceContext.getProfilerConfig()); this.param = config.isParam(); this.cookie = config.isCookie(); this.cookieDumpType = config.getCookieDumpType(); if (cookie) { this.cookieSampler = SimpleSamplerFactory.createSampler(cookie, config.getCookieSamplingRate()); } this.entity = config.isEntity(); this.entityDumpType = config.getEntityDumpType(); if (entity) { this.entitySampler = SimpleSamplerFactory.createSampler(entity, config.getEntitySamplingRate()); } this.io = config.isIo(); } @Override public void before(Object target, Object[] args) { if (isDebug) { logger.beforeInterceptor(target, args); } final Trace trace = traceContext.currentRawTraceObject(); if (trace == null) { return; } if (!trace.canSampled()) { // set http header. setHttpSampledHeader(target); return; } final SpanEventRecorder recorder = trace.traceBlockBegin(); // generate next trace id. final TraceId nextId = trace.getTraceId().getNextTraceId(); recorder.recordNextSpanId(nextId.getSpanId()); recorder.recordServiceType(HttpClient3Constants.HTTP_CLIENT_3); // set http header for trace. setHttpTraceHeader(target, args, nextId); // init attachment for io(read/write). initAttachment(); } @Override public void after(Object target, Object[] args, Object result, Throwable throwable) { if (isDebug) { logger.afterInterceptor(target, args); } final Trace trace = traceContext.currentTraceObject(); if (trace == null) { return; } try { final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); if (target instanceof HttpMethod) { HttpMethod httpMethod = (HttpMethod) target; recordDestination(trace, httpMethod, args); recordRequest(trace, httpMethod, throwable); } if (result != null) { recorder.recordAttribute(AnnotationKey.HTTP_STATUS_CODE, result); } recorder.recordApi(descriptor); recorder.recordException(throwable); final HttpClient3CallContext callContext = getAndCleanAttachment(); if (callContext != null) { recordIo(recorder, callContext); } } finally { trace.traceBlockEnd(); } } private void setHttpSampledHeader(final Object target) { if (isDebug) { logger.debug("set Sampling flag=false"); } if (target instanceof HttpMethod) { final HttpMethod httpMethod = (HttpMethod) target; httpMethod.setRequestHeader(Header.HTTP_SAMPLED.toString(), SamplingFlagUtils.SAMPLING_RATE_FALSE); } } private void setHttpTraceHeader(final Object target, final Object[] args, TraceId nextId) { if (target instanceof HttpMethod) { final HttpMethod httpMethod = (HttpMethod) target; httpMethod.setRequestHeader(Header.HTTP_TRACE_ID.toString(), nextId.getTransactionId()); httpMethod.setRequestHeader(Header.HTTP_SPAN_ID.toString(), String.valueOf(nextId.getSpanId())); httpMethod.setRequestHeader(Header.HTTP_PARENT_SPAN_ID.toString(), String.valueOf(nextId.getParentSpanId())); httpMethod.setRequestHeader(Header.HTTP_FLAGS.toString(), String.valueOf(nextId.getFlags())); httpMethod.setRequestHeader(Header.HTTP_PARENT_APPLICATION_NAME.toString(), traceContext.getApplicationName()); httpMethod.setRequestHeader(Header.HTTP_PARENT_APPLICATION_TYPE.toString(), Short.toString(traceContext.getServerTypeCode())); final String host = getHost(httpMethod, args); if (host != null) { httpMethod.setRequestHeader(Header.HTTP_HOST.toString(), host); } } } private String getHost(HttpMethod httpMethod, Object[] args) { try { final URI url = httpMethod.getURI(); if (url.isAbsoluteURI()) { return getEndpoint(url.getHost(), url.getPort()); } if (isDebug) { logger.debug("URI is not absolute. {}", url.getURI()); } // if not found schema, use httpConnection. final HttpConnection httpConnection = getHttpConnection(args); if (httpConnection != null) { final String host = httpConnection.getHost(); int port = httpConnection.getPort(); // if port is default port number. if (httpConnection.getProtocol() != null && port == httpConnection.getProtocol().getDefaultPort()) { port = -1; } return getEndpoint(host, port); } } catch (URIException e) { // unexpected error, perhaps of user fault. logger.error("[HttpClient3] Fail get URI", e); } return null; } private void initAttachment() { InterceptorScopeInvocation invocation = interceptorScope.getCurrentInvocation(); if (invocation != null) { invocation.getOrCreateAttachment(HttpClient3CallContextFactory.HTTPCLIENT3_CONTEXT_FACTORY); } } private HttpClient3CallContext getAndCleanAttachment() { final InterceptorScopeInvocation invocation = interceptorScope.getCurrentInvocation(); if (invocation != null && invocation.getAttachment() != null && invocation.getAttachment() instanceof HttpClient3CallContext) { return (HttpClient3CallContext) invocation.removeAttachment(); } return null; } private void recordDestination(final Trace trace, final HttpMethod httpMethod, final Object[] args) { final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); try { final URI uri = httpMethod.getURI(); final HttpConnection httpConnection = getHttpConnection(args); // if uri have schema or not found HttpConnection argument. if (uri.isAbsoluteURI() || httpConnection == null) { recorder.recordAttribute(AnnotationKey.HTTP_URL, InterceptorUtils.getHttpUrl(uri.getURI(), param)); recorder.recordDestinationId(getEndpoint(uri.getHost(), uri.getPort())); return; } if (isDebug) { logger.debug("URI is not absolute. {}", uri.getURI()); } // use HttpConnection argument. final String host = httpConnection.getHost(); int port = httpConnection.getPort(); final StringBuilder httpUrl = new StringBuilder(); final Protocol protocol = httpConnection.getProtocol(); if (protocol != null) { httpUrl.append(protocol.getScheme()).append("://"); httpUrl.append(httpConnection.getHost()); // if port is default port number. if (httpConnection.getPort() == protocol.getDefaultPort()) { port = -1; } else { httpUrl.append(":").append(port); } } httpUrl.append(uri.getURI()); recorder.recordAttribute(AnnotationKey.HTTP_URL, InterceptorUtils.getHttpUrl(httpUrl.toString(), param)); recorder.recordDestinationId(getEndpoint(host, port)); } catch (URIException e) { logger.error("Fail get URI", e); recorder.recordDestinationId("unknown"); } } private void recordIo(SpanEventRecorder recorder, HttpClient3CallContext callContext) { if (io) { final StringBuilder sb = new StringBuilder(); sb.append("write=").append(callContext.getWriteElapsedTime()); if (callContext.isWriteFail()) { sb.append("(fail)"); } sb.append(", read=").append(callContext.getReadElapsedTime()); if (callContext.isReadFail()) { sb.append("(fail)"); } recorder.recordAttribute(AnnotationKey.HTTP_IO, sb.toString()); } } private void recordRequest(Trace trace, HttpMethod httpMethod, Throwable throwable) { final boolean isException = InterceptorUtils.isThrowable(throwable); if (cookie) { if (DumpType.ALWAYS == cookieDumpType) { recordCookie(httpMethod, trace); } else if (DumpType.EXCEPTION == cookieDumpType && isException) { recordCookie(httpMethod, trace); } } if (entity) { if (DumpType.ALWAYS == entityDumpType) { recordEntity(httpMethod, trace); } else if (DumpType.EXCEPTION == entityDumpType && isException) { recordEntity(httpMethod, trace); } } } private void recordEntity(HttpMethod httpMethod, Trace trace) { if (httpMethod instanceof EntityEnclosingMethod) { final EntityEnclosingMethod entityEnclosingMethod = (EntityEnclosingMethod) httpMethod; final RequestEntity entity = entityEnclosingMethod.getRequestEntity(); if (entity != null && entity.isRepeatable() && entity.getContentLength() > 0) { if (entitySampler.isSampling()) { try { String entityValue; String charSet = entityEnclosingMethod.getRequestCharSet(); if (StringUtils.isEmpty(charSet)) { charSet = HttpConstants.DEFAULT_CONTENT_CHARSET; } if (entity instanceof ByteArrayRequestEntity || entity instanceof StringRequestEntity) { entityValue = entityUtilsToString(entity, charSet); } else { entityValue = entity.getClass() + " (ContentType:" + entity.getContentType() + ")"; } final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); recorder.recordAttribute(AnnotationKey.HTTP_PARAM_ENTITY, entityValue); } catch (Exception e) { logger.debug("HttpEntityEnclosingRequest entity record fail. Caused:{}", e.getMessage(), e); } } } } } private String entityUtilsToString(RequestEntity entity, String charSet) throws Exception { FixedByteArrayOutputStream outStream = new FixedByteArrayOutputStream(MAX_READ_SIZE); entity.writeRequest(outStream); String entityValue = outStream.toString(charSet); if (entity.getContentLength() > MAX_READ_SIZE) { StringBuilder sb = new StringBuilder(); sb.append(entityValue); sb.append(" (HTTP entity is large. length: "); sb.append(entity.getContentLength()); sb.append(" )"); return sb.toString(); } return entityValue; } private void recordCookie(HttpMethod httpMethod, Trace trace) { org.apache.commons.httpclient.Header cookie = httpMethod.getRequestHeader("Cookie"); if (cookie == null) { return; } final String value = cookie.getValue(); if (StringUtils.isNotEmpty(value)) { if (cookieSampler.isSampling()) { final SpanEventRecorder recorder = trace.currentSpanEventRecorder(); recorder.recordAttribute(AnnotationKey.HTTP_COOKIE, StringUtils.abbreviate(value, MAX_READ_SIZE)); } } } private String getEndpoint(String host, int port) { if (host == null) { return "unknown"; } if (port < 0) { return host; } StringBuilder sb = new StringBuilder(host.length() + 8); sb.append(host); sb.append(':'); sb.append(port); return sb.toString(); } private HttpConnection getHttpConnection(final Object[] args) { if (args != null && args.length > 1 && args[1] instanceof HttpConnection) { return (HttpConnection) args[1]; } return null; } }