From 83d524830f65f4fb56e8444553f64b766c4827c5 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Thu, 6 Feb 2020 11:28:36 +0200 Subject: [PATCH 01/16] Refactoring- adding header set and get APIs --- .../TraceMethodInstrumentation.java | 7 +- .../apm/agent/impl/ElasticApmTracer.java | 122 +++++++++--- .../impl/transaction/BinaryHeaderGetter.java | 28 +++ .../impl/transaction/BinaryHeaderSetter.java | 44 +++++ .../agent/impl/transaction/HeaderGetter.java | 36 ++++ .../agent/impl/transaction/HeaderRemover.java | 30 +++ .../agent/impl/transaction/HeaderSetter.java | 30 +++ .../impl/transaction/TextHeaderGetter.java | 28 +++ .../impl/transaction/TextHeaderSetter.java | 28 +++ .../agent/impl/transaction/TraceContext.java | 131 ++++++++++--- .../agent/impl/transaction/Transaction.java | 21 +- .../TraceMethodInstrumentationTest.java | 8 +- .../agent/impl/BinaryHeaderMapAccessor.java | 68 +++++++ .../apm/agent/impl/ElasticApmTracerTest.java | 43 +++-- .../apm/agent/impl/ScopeManagementTest.java | 29 ++- .../apm/agent/impl/SpanTypeBreakdownTest.java | 32 ++-- .../apm/agent/impl/TextHeaderMapAccessor.java | 61 ++++++ .../impl/transaction/TraceContextTest.java | 179 ++++++++++++------ .../StacktraceSerializationTest.java | 7 +- .../ApacheHttpAsyncClientInstrumentation.java | 45 +++-- ...ttpAsyncClientRedirectInstrumentation.java | 26 +-- .../ApacheHttpClientInstrumentation.java | 30 +-- .../BaseApacheHttpClientInstrumentation.java | 68 +++++++ ...LegacyApacheHttpClientInstrumentation.java | 28 +-- .../ApacheHttpAsyncClientHelper.java | 11 +- .../ApacheHttpAsyncClientHelperImpl.java | 14 +- .../{ => helper}/FutureCallbackWrapper.java | 14 +- .../HttpAsyncRequestProducerWrapper.java | 20 +- .../helper/RequestHeaderAccessor.java | 65 +++++++ .../agent/httpclient/helper/package-info.java | 28 +++ .../api/AbstractSpanInstrumentation.java | 5 +- .../CaptureTransactionInstrumentation.java | 7 +- .../api/ElasticApmApiInstrumentation.java | 23 +-- .../plugin/api/HeaderExtractorBridge.java | 84 ++++++++ .../plugin/api/HeaderInjectorBridge.java | 73 +++++++ .../plugin/api/HeadersExtractorBridge.java | 98 ++++++++++ .../plugin/api/TracedInstrumentation.java | 10 +- .../plugin/api/SpanInstrumentationTest.java | 7 +- .../api/ElasticApmApiInstrumentationTest.java | 26 ++- ...bstractAsyncHttpClientInstrumentation.java | 47 ++++- .../helper/RequestHeaderAccessor.java | 53 ++++++ .../asynchttpclient/helper/package-info.java | 28 +++ ...stractErrorLoggingInstrumentationTest.java | 7 +- ...tClientInstrumentationIT_RealReporter.java | 7 +- .../AbstractEsClientInstrumentationTest.java | 7 +- .../HibernateSearch5InstrumentationTest.java | 7 +- .../HibernateSearch6InstrumentationTest.java | 7 +- .../http/client/HttpClientHelperTest.java | 7 +- ...AbstractHttpClientInstrumentationTest.java | 60 ++++-- .../concurrent/ExcludedExecutorClassTest.java | 7 +- .../ExecutorInstrumentationTest.java | 7 +- .../ExecutorServiceDoubleWrappingTest.java | 7 +- .../ExecutorServiceInstrumentationTest.java | 7 +- .../FailingExecutorInstrumentationTest.java | 7 +- ...xRsTransactionNameInstrumentationTest.java | 7 +- ...xWsTransactionNameInstrumentationTest.java | 7 +- .../jdbc/AbstractJdbcInstrumentationTest.java | 7 +- .../apm/agent/jms/BaseJmsInstrumentation.java | 7 +- .../agent/jms/JmsInstrumentationHelper.java | 5 + .../jms/JmsInstrumentationHelperImpl.java | 15 +- .../JmsMessageConsumerInstrumentation.java | 34 ++-- .../JmsMessageListenerInstrumentation.java | 41 ++-- .../agent/jms/JmsMessagePropertyAccessor.java | 92 +++++++++ .../apm/agent/jms/JmsInstrumentationIT.java | 7 +- .../apm/agent/jms/spring/SpringJmsTest.java | 7 +- .../apm/agent/kafka/KafkaLegacyClientIT.java | 3 +- .../BaseKafkaHeadersInstrumentation.java | 7 +- ...onsumerRecordsIteratorInstrumentation.java | 3 +- ...sumerRecordsRecordListInstrumentation.java | 3 +- ...ConsumerRecordsRecordsInstrumentation.java | 3 +- .../KafkaConsumerRecordsInstrumentation.java | 21 +- .../KafkaProducerHeadersInstrumentation.java | 14 +- .../ConsumerRecordsIteratorWrapper.java | 12 +- .../agent/kafka/helper/ElasticHeaderImpl.java | 44 ++++- .../KafkaInstrumentationHeadersHelper.java | 7 +- ...KafkaInstrumentationHeadersHelperImpl.java | 20 +- .../helper/KafkaRecordHeaderAccessor.java | 137 ++++++++++++++ .../co/elastic/apm/agent/kafka/KafkaIT.java | 8 +- .../apm/agent/kafka/KafkaLegacyBrokerIT.java | 2 +- ...bstractMongoClientInstrumentationTest.java | 7 +- .../AbstractOkHttp3ClientInstrumentation.java | 62 ++++++ .../AbstractOkHttpClientInstrumentation.java | 62 ++++++ .../OkHttp3ClientAsyncInstrumentation.java | 24 +-- .../okhttp/OkHttp3ClientInstrumentation.java | 28 +-- .../okhttp/OkHttp3RequestHeaderAccessor.java | 51 +++++ .../OkHttpClientAsyncInstrumentation.java | 23 ++- .../okhttp/OkHttpClientInstrumentation.java | 26 ++- .../okhttp/OkHttpRequestHeaderAccessor.java | 51 +++++ .../impl/ApmSpanBuilderInstrumentation.java | 15 +- .../ExternalSpanContextInstrumentation.java | 12 +- .../impl/OpenTracingTextMapBridge.java | 69 +++++++ .../impl/SpanContextInstrumentation.java | 6 +- .../CommonsExecAsyncInstrumentationTest.java | 7 +- .../quartz/job/JobTransactionNameAdvice.java | 9 +- ...heduledTransactionNameInstrumentation.java | 8 +- .../apm/agent/servlet/ServletApiAdvice.java | 24 ++- .../agent/servlet/ServletInstrumentation.java | 24 ++- .../servlet/ServletTransactionHelper.java | 63 +----- .../servlet/helper/RequestHeaderGetter.java | 93 +++++++++ .../ServletTransactionCreationHelperImpl.java | 85 +++++++++ .../servlet/TestRequestBodyCapturing.java | 7 +- .../slf4j/Slf4JMdcActivationListenerTest.java | 23 ++- .../SpringRestRequestHeaderSetter.java | 35 ++++ .../SpringRestTemplateInstrumentation.java | 87 ++++++--- .../HttpUrlConnectionInstrumentation.java | 4 +- .../UrlConnectionPropertyAccessor.java | 57 ++++++ .../opentracing/OpenTracingBridgeTest.java | 3 +- .../elastic/apm/opentracing/package-info.java | 28 +++ 108 files changed, 2662 insertions(+), 723 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderGetter.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderSetter.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderRemover.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderSetter.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderGetter.java create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderSetter.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java create mode 100644 apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/BaseApacheHttpClientInstrumentation.java rename apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/{ => helper}/ApacheHttpAsyncClientHelper.java (79%) rename apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/{ => helper}/ApacheHttpAsyncClientHelperImpl.java (91%) rename apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/{ => helper}/FutureCallbackWrapper.java (93%) rename apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/{ => helper}/HttpAsyncRequestProducerWrapper.java (85%) create mode 100644 apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java create mode 100644 apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/package-info.java create mode 100644 apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java create mode 100644 apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java create mode 100644 apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java create mode 100644 apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java create mode 100644 apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/package-info.java create mode 100644 apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java create mode 100644 apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java create mode 100644 apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java create mode 100644 apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java create mode 100644 apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestRequestHeaderSetter.java create mode 100644 apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java create mode 100644 apm-opentracing/src/test/java/co/elastic/apm/opentracing/package-info.java diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentation.java index e8aac1c239..f239500957 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentation.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.matcher.WildcardMatcher; import net.bytebuddy.asm.Advice; @@ -75,7 +74,7 @@ public static void onMethodEnter(@Advice.Origin Class clazz, if (tracer != null) { final TraceContextHolder parent = tracer.getActive(); if (parent == null) { - span = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()) + span = tracer.startRootTransaction(clazz.getClassLoader()) .withName(signature) .activate(); } else if (parent.isSampled()) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 0852f129bd..4bced16a61 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -36,7 +36,10 @@ import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.agent.impl.transaction.AbstractSpan; +import co.elastic.apm.agent.impl.transaction.BinaryHeaderGetter; +import co.elastic.apm.agent.impl.transaction.HeaderGetter; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; @@ -96,6 +99,7 @@ public class ElasticApmTracer { // Maintains a stack of all the activated spans // This way its easy to retrieve the bottom of the stack (the transaction) // Also, the caller does not have to keep a reference to the previously active span, as that is maintained by the stack + @SuppressWarnings({"Convert2Diamond", "AnonymousHasLambdaAlternative"}) private final ThreadLocal>> activeStack = new ThreadLocal>>() { @Override protected Deque> initialValue() { @@ -103,6 +107,7 @@ protected Deque> initialValue() { } }; + @SuppressWarnings({"AnonymousHasLambdaAlternative", "Convert2Diamond"}) private final ThreadLocal allowWrappingOnThread = new ThreadLocal() { @Override protected Boolean initialValue() { @@ -139,6 +144,7 @@ protected Boolean initialValue() { sampler = ProbabilitySampler.of(coreConfiguration.getSampleRate().get()); + //noinspection Convert2Lambda,Convert2Diamond coreConfiguration.getSampleRate().addChangeListener(new ConfigurationOption.ChangeListener() { @Override public void onChange(ConfigurationOption configurationOption, Double oldValue, Double newValue) { @@ -149,60 +155,119 @@ public void onChange(ConfigurationOption configurationOption, Double oldValue reporter.scheduleMetricReporting(metricRegistry, configurationRegistry.getConfig(ReporterConfiguration.class).getMetricsIntervalMs()); // sets the assertionsEnabled flag to true if indeed enabled + //noinspection AssertWithSideEffects assert assertionsEnabled = true; } - public Transaction startRootTransaction(@Nullable ClassLoader initiatingClassLoader) { - return startTransaction(TraceContext.asRoot(), null, initiatingClassLoader); + /** + * Starts a trace-root transaction + * + * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. + * Used to determine the service name. + * @return a transaction which is a child of the provided parent + */ + public Transaction startRootTransaction(@Nullable ClassLoader initiatingClassLoader) { + return startRootTransaction(sampler, -1, initiatingClassLoader); } /** - * Starts a transaction as a child of the provided parent + * Starts a trace-root transaction with a specified sampler and start timestamp * - * @param childContextCreator used to make the transaction a child of the provided parent - * @param parent the parent of the transaction. May be a traceparent header. + * @param sampler the {@link Sampler} instance which is responsible for determining the sampling decision if this is a root transaction + * @param epochMicros the start timestamp * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. - * Used to determine the service name. - * @param the type of the parent. {@code String} in case of a traceparent header. + * Used to determine the service name and to load application-scoped classes like the {@link org.slf4j.MDC}, + * for log correlation. * @return a transaction which is a child of the provided parent */ - public Transaction startTransaction(TraceContext.ChildContextCreator childContextCreator, @Nullable T parent, @Nullable ClassLoader initiatingClassLoader) { - return startTransaction(childContextCreator, parent, sampler, -1, initiatingClassLoader); + public Transaction startRootTransaction(Sampler sampler, long epochMicros, @Nullable ClassLoader initiatingClassLoader) { + Transaction transaction; + if (!coreConfiguration.isActive()) { + transaction = noopTransaction(); + } else { + transaction = createTransaction().start(TraceContext.asRoot(), null, epochMicros, sampler, initiatingClassLoader); + } + afterTransactionStart(initiatingClassLoader, transaction); + return transaction; } - public void avoidWrappingOnThread() { - allowWrappingOnThread.set(Boolean.FALSE); + /** + * Starts a transaction as a child of the context headers obtained through the provided {@link HeaderGetter} + * + * @param headerCarrier the Object from which context headers can be obtained, typically a request or a message + * @param textHeadersGetter provides the trace context headers required in order to create a child transaction + * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. + * Used to determine the service name. + * @return a transaction which is a child of the provided parent + */ + public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHeaderGetter textHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { + return startChildTransaction(headerCarrier, textHeadersGetter, sampler, -1, initiatingClassLoader); } - public void allowWrappingOnThread() { - allowWrappingOnThread.set(Boolean.TRUE); + /** + * Starts a transaction as a child of the context headers obtained through the provided {@link HeaderGetter} + * + * @param headerCarrier the Object from which context headers can be obtained, typically a request or a message + * @param textHeadersGetter provides the trace context headers required in order to create a child transaction + * @param sampler the {@link Sampler} instance which is responsible for determining the sampling decision if this is a root transaction + * @param epochMicros the start timestamp + * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. + * Used to determine the service name and to load application-scoped classes like the {@link org.slf4j.MDC}, + * for log correlation. + * @return a transaction which is a child of the provided parent + */ + public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHeaderGetter textHeadersGetter, Sampler sampler, + long epochMicros, @Nullable ClassLoader initiatingClassLoader) { + Transaction transaction; + if (!coreConfiguration.isActive()) { + transaction = noopTransaction(); + } else { + transaction = createTransaction().start(TraceContext.getFromTraceContextTextHeaders(), headerCarrier, + textHeadersGetter, epochMicros, sampler, initiatingClassLoader); + } + afterTransactionStart(initiatingClassLoader, transaction); + return transaction; } - public boolean isWrappingAllowedOnThread() { - return allowWrappingOnThread.get() == Boolean.TRUE; + /** + * Starts a transaction as a child of the context headers obtained through the provided {@link HeaderGetter} + * + * @param headerCarrier the Object from which context headers can be obtained, typically a request or a message + * @param binaryHeadersGetter provides the trace context headers required in order to create a child transaction + * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. + * Used to determine the service name. + * @return a transaction which is a child of the provided parent + */ + public Transaction startChildTransaction(@Nullable Object headerCarrier, BinaryHeaderGetter binaryHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { + return startChildTransaction(headerCarrier, binaryHeadersGetter, sampler, -1, initiatingClassLoader); } /** - * Starts a transaction as a child of the provided parent + * Starts a transaction as a child of the context headers obtained through the provided {@link HeaderGetter} * - * @param childContextCreator used to make the transaction a child of the provided parent - * @param parent the parent of the transaction. May be a traceparent header. + * @param headerCarrier the Object from which context headers can be obtained, typically a request or a message + * @param binaryHeadersGetter provides the trace context headers required in order to create a child transaction * @param sampler the {@link Sampler} instance which is responsible for determining the sampling decision if this is a root transaction * @param epochMicros the start timestamp * @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction. * Used to determine the service name and to load application-scoped classes like the {@link org.slf4j.MDC}, * for log correlation. - * @param the type of the parent. {@code String} in case of a traceparent header. * @return a transaction which is a child of the provided parent */ - public Transaction startTransaction(TraceContext.ChildContextCreator childContextCreator, @Nullable T parent, Sampler sampler, - long epochMicros, @Nullable ClassLoader initiatingClassLoader) { + public Transaction startChildTransaction(@Nullable Object headerCarrier, BinaryHeaderGetter binaryHeadersGetter, + Sampler sampler, long epochMicros, @Nullable ClassLoader initiatingClassLoader) { Transaction transaction; if (!coreConfiguration.isActive()) { transaction = noopTransaction(); } else { - transaction = createTransaction().start(childContextCreator, parent, epochMicros, sampler, initiatingClassLoader); + transaction = createTransaction().start(TraceContext.getFromTraceContextBinaryHeaders(), headerCarrier, + binaryHeadersGetter, epochMicros, sampler, initiatingClassLoader); } + afterTransactionStart(initiatingClassLoader, transaction); + return transaction; + } + + private void afterTransactionStart(@Nullable ClassLoader initiatingClassLoader, Transaction transaction) { if (logger.isDebugEnabled()) { logger.debug("startTransaction {} {", transaction); if (logger.isTraceEnabled()) { @@ -214,7 +279,18 @@ public Transaction startTransaction(TraceContext.ChildContextCreator chil if (serviceName != null) { transaction.getTraceContext().setServiceName(serviceName); } - return transaction; + } + + public void avoidWrappingOnThread() { + allowWrappingOnThread.set(Boolean.FALSE); + } + + public void allowWrappingOnThread() { + allowWrappingOnThread.set(Boolean.TRUE); + } + + public boolean isWrappingAllowedOnThread() { + return allowWrappingOnThread.get() == Boolean.TRUE; } public Transaction noopTransaction() { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderGetter.java new file mode 100644 index 0000000000..5cbf60edf2 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderGetter.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public interface BinaryHeaderGetter extends HeaderGetter { +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderSetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderSetter.java new file mode 100644 index 0000000000..590eba2010 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/BinaryHeaderSetter.java @@ -0,0 +1,44 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +import javax.annotation.Nullable; + +public interface BinaryHeaderSetter extends HeaderSetter { + + /** + * Since the implementation itself knows the intrinsics of the headers and carrier lifecycle and handling, it should + * be responsible for providing a byte array. This enables the implementation to cache byte arrays wherever required + * and possible. + *

+ * NOTE: if this method returns null, the tracer will allocate a buffer for each header. + * + * @param headerName the header name for which the byte array is required + * @param length the length of the required byte array + * @return a byte array with the requested length, or null if header-value-buffer is not supported. + */ + @Nullable + byte[] getFixedLengthByteArray(String headerName, int length); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java new file mode 100644 index 0000000000..bd809c1d0f --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java @@ -0,0 +1,36 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +import javax.annotation.Nullable; + +public interface HeaderGetter { + + @Nullable + T getFirstHeader(String headerName, C carrier); + + @Nullable + Iterable getHeaders(String headerName, C carrier); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderRemover.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderRemover.java new file mode 100644 index 0000000000..ec07316a7e --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderRemover.java @@ -0,0 +1,30 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public interface HeaderRemover { + + void remove(String headerName, C carrier); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderSetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderSetter.java new file mode 100644 index 0000000000..b392519174 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderSetter.java @@ -0,0 +1,30 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public interface HeaderSetter { + + void setHeader(String headerName, T headerValue, C carrier); +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderGetter.java new file mode 100644 index 0000000000..02c7da7ab0 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderGetter.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public interface TextHeaderGetter extends HeaderGetter { +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderSetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderSetter.java new file mode 100644 index 0000000000..0d44dca32b --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextHeaderSetter.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public interface TextHeaderSetter extends HeaderSetter { +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index 731cbe6318..ccd0299de0 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -104,25 +104,44 @@ public boolean asChildOf(TraceContext child, TraceContextHolder parent) { return true; } }; - private static final ChildContextCreator FROM_TRACEPARENT_TEXT_HEADER = new ChildContextCreator() { - @Override - public boolean asChildOf(TraceContext child, @Nullable String traceparent) { - if (traceparent != null) { - return child.asChildOf(traceparent); + private static final ChildContextCreatorTwoArg> FROM_TRACE_CONTEXT_TEXT_HEADERS = + new ChildContextCreatorTwoArg>() { + @Override + public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeaderGetter traceContextHeaderGetter) { + if (carrier == null) { + return false; + } + // This cast is required, otherwise the compiler can't guarantee that the carrier type is allowed by the + // TextHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the + // carrier type is the one expected in the provided TextHeaderGetter, we cannot enforce it through generics + // if we want to use this single ChildContextCreatorTwoArg instance for all types. + //noinspection unchecked + String traceparent = ((TextHeaderGetter) traceContextHeaderGetter).getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + if (traceparent != null) { + return child.asChildOf(traceparent); + } + return false; } - return false; - } - }; - private static final ChildContextCreator FROM_TRACEPARENT_BINARY_HEADER = new ChildContextCreator() { - @Override - public boolean asChildOf(TraceContext child, @Nullable byte[] traceparent) { - if (traceparent != null) { - return child.asChildOf(traceparent); + }; + private static final ChildContextCreatorTwoArg> FROM_TRACE_CONTEXT_BINARY_HEADERS = + new ChildContextCreatorTwoArg>() { + @Override + public boolean asChildOf(TraceContext child, @Nullable Object carrier, BinaryHeaderGetter traceContextHeaderGetter) { + if (carrier == null) { + return false; + } + // This cast is required, otherwise the compiler can't guarantee that the carrier type is allowed by the + // BinaryHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the + // carrier type is the one expected in the provided BinaryHeaderGetter, we cannot enforce it through generics + // if we want to use this single ChildContextCreatorTwoArg instance for all types. + //noinspection unchecked + byte[] traceparent = ((BinaryHeaderGetter) traceContextHeaderGetter).getFirstHeader(TRACE_PARENT_BINARY_HEADER_NAME, carrier); + if (traceparent != null) { + return child.asChildOf(traceparent); + } + return false; } - return false; - } - }; - + }; private static final ChildContextCreator FROM_ACTIVE = new ChildContextCreator() { @Override public boolean asChildOf(TraceContext child, ElasticApmTracer tracer) { @@ -140,6 +159,22 @@ public boolean asChildOf(TraceContext child, Object ignore) { return false; } }; + + public static boolean containsTraceContextTextHeaders(C carrier, TextHeaderGetter headerGetter) { + return headerGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier) != null; + } + + public static void removeTraceContextHeaders(C carrier, HeaderRemover headerRemover) { + headerRemover.remove(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + } + + public static void copyTextHeaders(S source, TextHeaderGetter headerGetter, D destination, TextHeaderSetter headerSetter) { + String elasticApmTraceParent = headerGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, source); + if (elasticApmTraceParent != null) { + headerSetter.setHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, elasticApmTraceParent, destination); + } + } + // ???????1 -> maybe recorded // ???????0 -> not recorded private static final byte FLAG_RECORDED = 0b0000_0001; @@ -183,18 +218,19 @@ public static TraceContext with64BitId(ElasticApmTracer tracer) { * Creates a new {@link TraceContext} with a 128 bit {@link #id}, * suitable for errors, * as those might not have a trace reference and therefore require a larger id in order to be globally unique. + * * @param tracer a valid tracer */ public static TraceContext with128BitId(ElasticApmTracer tracer) { return new TraceContext(tracer, Id.new128BitId()); } - public static ChildContextCreator fromTraceparentHeader() { - return FROM_TRACEPARENT_TEXT_HEADER; + public static ChildContextCreatorTwoArg> getFromTraceContextTextHeaders() { + return FROM_TRACE_CONTEXT_TEXT_HEADERS; } - public static ChildContextCreator fromTraceparentBinaryHeader() { - return FROM_TRACEPARENT_BINARY_HEADER; + public static ChildContextCreatorTwoArg> getFromTraceContextBinaryHeaders() { + return FROM_TRACE_CONTEXT_BINARY_HEADERS; } public static ChildContextCreator fromActive() { @@ -209,7 +245,7 @@ public static ChildContextCreator asRoot() { return AS_ROOT; } - public boolean asChildOf(String traceParentHeader) { + boolean asChildOf(String traceParentHeader) { traceParentHeader = traceParentHeader.trim(); try { if (traceParentHeader.length() < TEXT_HEADER_EXPECTED_LENGTH) { @@ -260,7 +296,7 @@ && noDashAtPosition(traceParentHeader, TEXT_HEADER_EXPECTED_LENGTH)) { } } - public boolean asChildOf(byte[] traceParentHeader) { + boolean asChildOf(byte[] traceParentHeader) { if (logger.isTraceEnabled()) { logger.trace("Binary header content UTF-8-decoded: {}", new String(traceParentHeader, StandardCharsets.UTF_8)); } @@ -428,10 +464,51 @@ String getIncomingTraceParentHeader() { return sb.toString(); } + /** + * Sets Trace context text headers, using this context as parent, on the provided carrier using the provided setter + * + * @param carrier the text headers carrier + * @param headerSetter a setter implementing the actual addition of headers to the headers carrier + * @param the header carrier type, for example - an HTTP request + */ + public void setOutgoingTraceContextHeaders(@Nullable C carrier, TextHeaderSetter headerSetter) { + if (carrier == null) { + logger.warn("Attempted to set a text header to a null carrier through {}. Distributed tracing cannot be supported.", headerSetter.getClass().getName()); + return; + } + headerSetter.setHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); + } + + /** + * Sets Trace context binary headers, using this context as parent, on the provided carrier using the provided setter + * + * @param carrier the binary headers carrier + * @param headerSetter a setter implementing the actual addition of headers to the headers carrier + * @param the header carrier type, for example - a Kafka record + * @return true if Trace Context headers were set; false otherwise + */ + public boolean setOutgoingTraceContextHeaders(@Nullable C carrier, BinaryHeaderSetter headerSetter) { + if (carrier == null) { + logger.warn("Attempted to set a binary header to a null carrier through {}. Distributed tracing cannot be supported.", headerSetter.getClass().getName()); + return false; + } + byte[] buffer = headerSetter.getFixedLengthByteArray(TRACE_PARENT_BINARY_HEADER_NAME, BINARY_FORMAT_EXPECTED_LENGTH); + if (buffer == null || buffer.length != BINARY_FORMAT_EXPECTED_LENGTH) { + logger.warn("Header setter {} failed to provide a byte buffer with the proper length. Allocating a buffer for each header.", + headerSetter.getClass().getName()); + buffer = new byte[BINARY_FORMAT_EXPECTED_LENGTH]; + } + boolean headerBufferFilled = fillOutgoingTraceParentBinaryHeader(buffer); + if (headerBufferFilled) { + headerSetter.setHeader(TRACE_PARENT_BINARY_HEADER_NAME, buffer, carrier); + } + return headerBufferFilled; + } + /** * Returns the value of the {@code traceparent} header for downstream services. */ - public StringBuilder getOutgoingTraceParentTextHeader() { + StringBuilder getOutgoingTraceParentTextHeader() { if (outgoingTextHeader.length() == 0) { // for unsampled traces, propagate the ID of the transaction in calls to downstream services // such that the parentID of those transactions point to a transaction that exists @@ -456,7 +533,7 @@ private void fillTraceParentHeader(StringBuilder sb, Id spanId) { * @param buffer buffer to fill * @return true if buffer was filled, false otherwise */ - public boolean fillOutgoingTraceParentBinaryHeader(byte[] buffer) { + private boolean fillOutgoingTraceParentBinaryHeader(byte[] buffer) { if (buffer.length < BINARY_FORMAT_EXPECTED_LENGTH) { logger.warn("Given byte array does not have the minimal required length - {}", BINARY_FORMAT_EXPECTED_LENGTH); return false; @@ -563,6 +640,10 @@ public interface ChildContextCreator { boolean asChildOf(TraceContext child, T parent); } + public interface ChildContextCreatorTwoArg { + boolean asChildOf(TraceContext child, @Nullable T parent, A arg); + } + public TraceContext copy() { final TraceContext copy; final int idLength = id.getLength(); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java index 669c2ecceb..1c548bf99f 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java @@ -11,9 +11,9 @@ * 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 @@ -98,7 +98,21 @@ public Transaction(ElasticApmTracer tracer) { public Transaction start(TraceContext.ChildContextCreator childContextCreator, @Nullable T parent, long epochMicros, Sampler sampler, @Nullable ClassLoader initiatingClassLoader) { traceContext.setApplicationClassLoader(initiatingClassLoader); - if (parent == null || !childContextCreator.asChildOf(traceContext, parent)) { + boolean startedAsChild = parent != null && childContextCreator.asChildOf(traceContext, parent); + onTransactionStart(startedAsChild, epochMicros, sampler); + return this; + } + + public Transaction start(TraceContext.ChildContextCreatorTwoArg childContextCreator, @Nullable T parent, A arg, + long epochMicros, Sampler sampler, @Nullable ClassLoader initiatingClassLoader) { + traceContext.setApplicationClassLoader(initiatingClassLoader); + boolean startedAsChild = childContextCreator.asChildOf(traceContext, parent, arg); + onTransactionStart(startedAsChild, epochMicros, sampler); + return this; + } + + private void onTransactionStart(boolean startedAsChild, long epochMicros, Sampler sampler) { + if (!startedAsChild) { traceContext.asRootSpan(sampler); } if (epochMicros >= 0) { @@ -107,7 +121,6 @@ public Transaction start(TraceContext.ChildContextCreator childContextCre setStartTimestamp(traceContext.getClock().getEpochMicros()); } onAfterStart(); - return this; } public Transaction startNoop() { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentationTest.java index e25cf6ec6a..daf3c54325 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentationTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/methodmatching/TraceMethodInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -33,8 +33,6 @@ import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; import co.elastic.apm.agent.impl.Scope; import co.elastic.apm.agent.impl.sampling.ConstantSampler; -import co.elastic.apm.agent.impl.sampling.Sampler; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import net.bytebuddy.agent.ByteBuddyAgent; @@ -104,7 +102,7 @@ void testTraceMethod() { @Test void testTraceMethodNonSampledTransaction() { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(false), 0, getClass().getClassLoader()); + Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(false), 0, getClass().getClassLoader()); transaction.withName("not sampled"); try (Scope scope = transaction.activateInScope()) { TestClass.traceMe(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java new file mode 100644 index 0000000000..9f4c83f50d --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java @@ -0,0 +1,68 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl; + +import co.elastic.apm.agent.impl.transaction.BinaryHeaderGetter; +import co.elastic.apm.agent.impl.transaction.BinaryHeaderSetter; +import co.elastic.apm.agent.impl.transaction.TraceContext; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BinaryHeaderMapAccessor implements BinaryHeaderGetter>, BinaryHeaderSetter> { + + public static final BinaryHeaderMapAccessor INSTANCE = new BinaryHeaderMapAccessor(); + + private final Map headerCache = new HashMap<>(); + + private BinaryHeaderMapAccessor() { + } + + @Nullable + @Override + public byte[] getFirstHeader(String headerName, Map headerMap) { + return headerMap.get(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Map headerMap) { + return List.of(headerMap.get(headerName)); + } + + @Nullable + @Override + public byte[] getFixedLengthByteArray(String headerName, int length) { + headerCache.computeIfAbsent(headerName, k -> new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH]); + return headerCache.get(headerName); + } + + @Override + public void setHeader(String headerName, byte[] headerValue, Map headerMap) { + headerMap.put(headerName, headerValue); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index 3ef655903d..a7ba08fe06 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java @@ -11,9 +11,9 @@ * 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 @@ -43,7 +43,9 @@ import org.stagemonitor.configuration.ConfigurationRegistry; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,7 +81,7 @@ void cleanupAndCheck() { @Test void testThreadLocalStorage() { - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { assertThat(tracerImpl.currentTransaction()).isSameAs(transaction); Span span = tracerImpl.getActive().createSpan(); @@ -97,7 +99,7 @@ void testThreadLocalStorage() { @Test void testNestedSpan() { - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { assertThat(tracerImpl.currentTransaction()).isSameAs(transaction); Span span = tracerImpl.getActive().createSpan(); @@ -122,7 +124,7 @@ void testNestedSpan() { @Test void testDisableStacktraces() { when(tracerImpl.getConfig(StacktraceConfiguration.class).getSpanFramesMinDurationMs()).thenReturn(0L); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { Span span = tracerImpl.getActive().createSpan(); try (Scope spanScope = span.activateInScope()) { @@ -136,7 +138,7 @@ void testDisableStacktraces() { @Test void testEnableStacktraces() throws InterruptedException { when(tracerImpl.getConfig(StacktraceConfiguration.class).getSpanFramesMinDurationMs()).thenReturn(-1L); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { Span span = tracerImpl.getActive().createSpan(); try (Scope spanScope = span.activateInScope()) { @@ -151,7 +153,7 @@ void testEnableStacktraces() throws InterruptedException { @Test void testDisableStacktracesForFastSpans() { when(tracerImpl.getConfig(StacktraceConfiguration.class).getSpanFramesMinDurationMs()).thenReturn(100L); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { Span span = tracerImpl.getActive().createSpan(); try (Scope spanScope = span.activateInScope()) { @@ -166,7 +168,7 @@ void testDisableStacktracesForFastSpans() { @Test void testEnableStacktracesForSlowSpans() throws InterruptedException { when(tracerImpl.getConfig(StacktraceConfiguration.class).getSpanFramesMinDurationMs()).thenReturn(1L); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { Span span = tracerImpl.getActive().createSpan(); try (Scope spanScope = span.activateInScope()) { @@ -226,7 +228,7 @@ void testRecordExceptionWithTraceNotSampled() { } private void innerRecordExceptionWithTrace(boolean sampled) { - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(sampled), -1, null); + Transaction transaction = tracerImpl.startRootTransaction(ConstantSampler.of(sampled), -1, null); transaction.withType("test-type"); try (Scope scope = transaction.activateInScope()) { transaction.getContext().getRequest() @@ -271,7 +273,7 @@ private ErrorCapture validateError(ErrorCapture error, AbstractSpan span, boo @Test void testEnableDropSpans() { when(tracerImpl.getConfig(CoreConfiguration.class).getTransactionMaxSpans()).thenReturn(1); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { Span span = tracerImpl.getActive().createSpan(); try (Scope spanScope = span.activateInScope()) { @@ -294,7 +296,7 @@ void testEnableDropSpans() { @Test void testDisable() { when(config.getConfig(CoreConfiguration.class).isActive()).thenReturn(false); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); // 1 + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); // 1 try (Scope scope = transaction.activateInScope()) { assertThat(tracerImpl.currentTransaction()).isSameAs(transaction); assertThat(transaction.isSampled()).isFalse(); @@ -314,7 +316,7 @@ void testDisable() { @Test void testDisableMidTransaction() { - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { assertThat(tracerImpl.currentTransaction()).isSameAs(transaction); Span span = tracerImpl.getActive().createSpan(); @@ -346,7 +348,7 @@ void testDisableMidTransaction() { @Test void testSamplingNone() throws IOException { config.getConfig(CoreConfiguration.class).getSampleRate().update(0.0, SpyConfiguration.CONFIG_SOURCE_NAME); - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, null).withType("request"); + Transaction transaction = tracerImpl.startRootTransaction(null).withType("request"); try (Scope scope = transaction.activateInScope()) { transaction.setUser("1", "jon.doe@example.com", "jondoe"); Span span = tracerImpl.getActive().createSpan(); @@ -399,8 +401,8 @@ public void stop() { @Test void testTransactionWithParentReference() { - final String traceContextHeader = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"; - final Transaction transaction = tracerImpl.startTransaction(TraceContext.fromTraceparentHeader(), traceContextHeader, ConstantSampler.of(false), 0, null); + final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final Transaction transaction = tracerImpl.startChildTransaction(headerMap, TextHeaderMapAccessor.INSTANCE, ConstantSampler.of(false), 0, null); // the traced flag in the header overrides the sampler assertThat(transaction.isSampled()).isTrue(); assertThat(transaction.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); @@ -412,8 +414,7 @@ void testTransactionWithParentReference() { @Test void testTimestamps() { - final Transaction transaction = tracerImpl.startTransaction(TraceContext.fromTraceparentHeader(), null, ConstantSampler.of(true), 0, null); - + final Transaction transaction = tracerImpl.startChildTransaction(new HashMap<>(), TextHeaderMapAccessor.INSTANCE, ConstantSampler.of(true), 0, null); final Span span = transaction.createSpan(10); span.end(20); @@ -427,7 +428,7 @@ void testTimestamps() { @Test void testStartSpanAfterTransactionHasEnded() { - final Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); final TraceContext transactionTraceContext = transaction.getTraceContext().copy(); transaction.end(); @@ -452,7 +453,7 @@ void testStartSpanAfterTransactionHasEnded() { @Test void testActivateDeactivateTwice() { - final Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); assertThat(tracerImpl.currentTransaction()).isNull(); tracerImpl.activate(transaction); assertThat(tracerImpl.currentTransaction()).isEqualTo(transaction); @@ -473,7 +474,7 @@ void testOverrideServiceNameWithoutExplicitServiceName() { .reporter(reporter) .build(); tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden"); - tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).end(); + tracer.startRootTransaction(getClass().getClassLoader()).end(); assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isEqualTo("overridden"); } @@ -486,7 +487,7 @@ void testOverrideServiceNameExplicitServiceName() { .build(); tracer.overrideServiceNameForClassLoader(getClass().getClassLoader(), "overridden"); - tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).end(); + tracer.startRootTransaction(getClass().getClassLoader()).end(); assertThat(reporter.getFirstTransaction().getTraceContext().getServiceName()).isNull(); } } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java index ba802526d8..1db3fc4724 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.MockReporter; import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -77,7 +76,7 @@ void runTestWithAssertionsDisabled(Runnable test) { @Test void testWrongDeactivationOrder() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); final Span span = transaction.createSpan().activate(); transaction.deactivate(); span.deactivate(); @@ -89,7 +88,7 @@ void testWrongDeactivationOrder() { @Test void testActivateTwice() { runTestWithAssertionsDisabled(() -> { - tracer.startTransaction(TraceContext.asRoot(), null, null) + tracer.startRootTransaction(null) .activate().activate() .deactivate().deactivate(); @@ -100,7 +99,7 @@ void testActivateTwice() { @Test void testRedundantActivation() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.createSpan().activate(); transaction.deactivate(); assertThat(tracer.getActive()).isEqualTo(transaction); @@ -112,7 +111,7 @@ void testRedundantActivation() { @Test void testContextAndSpanRunnableActivation() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withActive(transaction.withActive((Runnable) () -> assertThat(tracer.getActive()).isSameAs(transaction))).run(); transaction.deactivate(); @@ -124,7 +123,7 @@ void testContextAndSpanRunnableActivation() { @Test void testContextAndSpanCallableActivation() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); try { assertThat(transaction.withActive(transaction.withActive(() -> tracer.currentTransaction())).call()).isSameAs(transaction); } catch (Exception e) { @@ -139,7 +138,7 @@ void testContextAndSpanCallableActivation() { @Test void testSpanAndContextRunnableActivation() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Runnable runnable = transaction.withActive((Runnable) () -> assertThat(tracer.currentTransaction()).isSameAs(transaction)); transaction.withActive(runnable).run(); @@ -152,7 +151,7 @@ void testSpanAndContextRunnableActivation() { @Test void testSpanAndContextCallableActivation() { runTestWithAssertionsDisabled(() -> { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Callable callable = transaction.withActive(() -> tracer.currentTransaction()); try { assertThat(transaction.withActive(callable).call()).isSameAs(transaction); @@ -167,7 +166,7 @@ void testSpanAndContextCallableActivation() { @Test void testContextAndSpanRunnableActivationInDifferentThread() throws Exception { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Executors.newSingleThreadExecutor().submit(transaction.withActive(transaction.withActive(() -> { assertThat(tracer.getActive()).isSameAs(transaction); assertThat(tracer.currentTransaction()).isSameAs(transaction); @@ -179,7 +178,7 @@ void testContextAndSpanRunnableActivationInDifferentThread() throws Exception { @Test void testContextAndSpanCallableActivationInDifferentThread() throws Exception { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Future transactionFuture = Executors.newSingleThreadExecutor().submit(transaction.withActive(transaction.withActive(() -> { assertThat(tracer.getActive()).isSameAs(transaction); return tracer.currentTransaction(); @@ -192,7 +191,7 @@ void testContextAndSpanCallableActivationInDifferentThread() throws Exception { @Test void testSpanAndContextRunnableActivationInDifferentThread() throws Exception { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Runnable runnable = transaction.withActive(() -> { assertThat(tracer.currentTransaction()).isSameAs(transaction); assertThat(tracer.getActive()).isSameAs(transaction); @@ -205,7 +204,7 @@ void testSpanAndContextRunnableActivationInDifferentThread() throws Exception { @Test void testSpanAndContextCallableActivationInDifferentThread() throws Exception { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Callable callable = transaction.withActive(() -> { assertThat(tracer.currentTransaction()).isSameAs(transaction); return tracer.currentTransaction(); @@ -218,7 +217,7 @@ void testSpanAndContextCallableActivationInDifferentThread() throws Exception { @Test void testAsyncActivationAfterEnd() throws Exception { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + final Transaction transaction = tracer.startRootTransaction(null).activate(); Callable callable = transaction.withActive(() -> { assertThat(tracer.getActive()).isSameAs(transaction); return tracer.currentTransaction(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java index a555bdac6f..bcb3e6fcb9 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.metrics.Labels; @@ -47,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; +@SuppressWarnings("ConstantConditions") class SpanTypeBreakdownTest { private MockReporter reporter; @@ -70,7 +70,7 @@ void cleanup() { */ @Test void testBreakdown_noSpans() { - tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request") .end(30); @@ -89,7 +89,7 @@ void testBreakdown_noSpans() { @Test void testBreakdown_disabled() { when(tracer.getConfig(CoreConfiguration.class).isBreakdownMetricsEnabled()).thenReturn(false); - tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request") .end(30); @@ -108,7 +108,7 @@ void testBreakdown_disabled() { */ @Test void testBreakdown_singleDbSpan() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); transaction.createSpan(10).withType("db").withSubtype("mysql").end(20); @@ -132,7 +132,7 @@ void testBreakdown_singleDbSpan() { @Test void testBreakdown_singleDbSpan_breakdownMetricsDisabled() { tracer = MockTracer.createRealTracer(reporter, SpyConfiguration.createSpyConfig(SimpleSource.forTest("disable_metrics", "span.self_time"))); - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); transaction.createSpan(10).withType("db").withSubtype("mysql").end(20); @@ -153,7 +153,7 @@ void testBreakdown_singleDbSpan_breakdownMetricsDisabled() { */ @Test void testBreakdown_singleAppSpan() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); transaction.createSpan(10).withType("app").end(20); @@ -175,7 +175,7 @@ void testBreakdown_singleAppSpan() { */ @Test void testBreakdown_concurrentDbSpans_fullyOverlapping() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Span span1 = transaction.createSpan(10).withType("db").withSubtype("mysql"); @@ -202,7 +202,7 @@ void testBreakdown_concurrentDbSpans_fullyOverlapping() { */ @Test void testBreakdown_concurrentDbSpans_partiallyOverlapping() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Span span1 = transaction.createSpan(10).withType("db").withSubtype("mysql"); @@ -229,7 +229,7 @@ void testBreakdown_concurrentDbSpans_partiallyOverlapping() { */ @Test void testBreakdown_serialDbSpans_notOverlapping_withoutGap() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); transaction.createSpan(5).withType("db").withSubtype("mysql").end(15); @@ -254,7 +254,7 @@ void testBreakdown_serialDbSpans_notOverlapping_withoutGap() { */ @Test void testBreakdown_serialDbSpans_notOverlapping_withGap() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); transaction.createSpan(10).withType("db").withSubtype("mysql").end(15); @@ -279,7 +279,7 @@ void testBreakdown_serialDbSpans_notOverlapping_withGap() { */ @Test void testBreakdown_asyncGrandchildExceedsChild() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Span app = transaction.createSpan(10).withType("app"); @@ -309,7 +309,7 @@ void testBreakdown_asyncGrandchildExceedsChild() { */ @Test void testBreakdown_asyncGrandchildExceedsChildAndTransaction() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Span app = transaction.createSpan(10).withType("app"); @@ -347,7 +347,7 @@ void testBreakdown_asyncGrandchildExceedsChildAndTransaction() { */ @Test void testBreakdown_singleDbSpan_exceedingParent() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Span span = transaction.createSpan(10).withType("db").withSubtype("mysql"); @@ -377,7 +377,7 @@ void testBreakdown_singleDbSpan_exceedingParent() { */ @Test void testBreakdown_spanStartedAfterParentEnded() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader()) + final Transaction transaction = tracer.startRootTransaction(ConstantSampler.of(true), 0, getClass().getClassLoader()) .withName("test") .withType("request"); final Runnable runnable = transaction.withActive(() -> { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java new file mode 100644 index 0000000000..4c6c91b741 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java @@ -0,0 +1,61 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +public class TextHeaderMapAccessor implements TextHeaderGetter>, TextHeaderSetter> { + + public static final TextHeaderMapAccessor INSTANCE = new TextHeaderMapAccessor(); + + private TextHeaderMapAccessor() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Map headerMap) { + return headerMap.get(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Map headerMap) { + String value = headerMap.get(headerName); + if (value != null) { + return List.of(value); + } + return null; + } + + @Override + public void setHeader(String headerName, String headerValue, Map headerMap) { + headerMap.put(headerName, headerValue); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index 6d666b885f..bb563f78bf 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -25,10 +25,14 @@ package co.elastic.apm.agent.impl.transaction; import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.BinaryHeaderMapAccessor; +import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.util.HexUtils; import org.junit.jupiter.api.Test; +import java.util.HashMap; +import java.util.Map; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; @@ -36,13 +40,11 @@ class TraceContextTest { - private final byte[] outgoingBinaryTraceContext = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH]; - /** * Test flow: * 1. create a parent context from a fixed string * 2. create a child based on the string header - test {@link TraceContext#asChildOf(String)} - * 3. create a grandchild based on binary header - test {@link TraceContext#fillOutgoingTraceParentBinaryHeader(byte[])} + * 3. create a grandchild based on binary header - test {@link TraceContext#setOutgoingTraceContextHeaders(Object, BinaryHeaderSetter)} * and {@link TraceContext#asChildOf(byte[])} * 4. create a second grandchild based on text header - test both {@link TraceContext#getOutgoingTraceParentTextHeader()} * and {@link TraceContext#asChildOf(String)} @@ -51,9 +53,9 @@ class TraceContextTest { * @param isSampled whether to test context propagation of sampled trace or not */ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boolean isSampled) { - final String parentHeader = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-" + flagsValue; + Map textHeaderMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-" + flagsValue); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.fromTraceparentHeader().asChildOf(child, parentHeader)).isTrue(); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); @@ -61,8 +63,9 @@ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boo // create a grandchild to ensure proper regenerated trace context final TraceContext grandchild1 = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(child.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext)).isTrue(); - assertThat(TraceContext.fromTraceparentBinaryHeader().asChildOf(grandchild1, outgoingBinaryTraceContext)).isTrue(); + final Map binaryHeaderMap = new HashMap<>(); + assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.getFromTraceContextBinaryHeaders().asChildOf(grandchild1, binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(grandchild1.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(grandchild1.getTraceContext().getParentId().toString()).isEqualTo(child.getTraceContext().getId().toString()); assertThat(grandchild1.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getId()); @@ -95,20 +98,57 @@ void parseFromTraceParentHeaderUnsupportedFlag() { @Test void testBinaryHeaderSizeEnforcement() { - final String parentHeader = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"; + final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + final byte[] outgoingBinaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH - 1]; + assertThat(child.setOutgoingTraceContextHeaders(new HashMap<>(), new BinaryHeaderSetter>() { + @Override + public byte[] getFixedLengthByteArray(String headerName, int length) { + return outgoingBinaryHeader; + } + + @Override + public void setHeader(String headerName, byte[] headerValue, Map headerMap) { + // assert that the original byte array was not used due to its size limitation + assertThat(headerValue).isNotEqualTo(outgoingBinaryHeader); + } + })).isTrue(); + } + + @Test + void testBinaryHeaderCaching() { + final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.fromTraceparentHeader().asChildOf(child, parentHeader)).isTrue(); - byte[] outgoingBinaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH - 1]; - assertThat(child.fillOutgoingTraceParentBinaryHeader(outgoingBinaryHeader)).isFalse(); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + HashMap binaryHeaderMap = new HashMap<>(); + assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + byte[] outgoingHeader = binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); + assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isSameAs(outgoingHeader); } @Test - void testLongerBinaryHeader() { - final String parentHeader = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"; + void testBinaryHeader_CachingDisabled() { + final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.fromTraceparentHeader().asChildOf(child, parentHeader)).isTrue(); - byte[] outgoingBinaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH + 1]; - assertThat(child.fillOutgoingTraceParentBinaryHeader(outgoingBinaryHeader)).isTrue(); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + BinaryHeaderSetter> headerSetter = new BinaryHeaderSetter<>() { + @Override + public byte[] getFixedLengthByteArray(String headerName, int length) { + return null; + } + + @Override + public void setHeader(String headerName, byte[] headerValue, Map headerMap) { + headerMap.put(headerName, headerValue); + } + }; + HashMap binaryHeaderMap = new HashMap<>(); + assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, headerSetter)).isTrue(); + byte[] outgoingHeader = binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); + assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, headerSetter)).isTrue(); + assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isNotSameAs(outgoingHeader); } private void verifyTraceContextContents(String traceContext, String expectedTraceId, String expectedParentId, @@ -143,8 +183,10 @@ void outgoingHeader() { String parentId = traceContext.getId().toString(); verifyTraceContextContents(traceContext.getOutgoingTraceParentTextHeader().toString(), "0af7651916cd43dd8448eb211c80319c", parentId, "00", "03"); - assertThat(traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext)).isTrue(); - verifyTraceContextContents(outgoingBinaryTraceContext, "0af7651916cd43dd8448eb211c80319c", parentId, (byte) 0x00, (byte) 0x03); + Map headerMap = new HashMap<>(); + assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + verifyTraceContextContents(headerMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME), + "0af7651916cd43dd8448eb211c80319c", parentId, (byte) 0x00, (byte) 0x03); } @Test @@ -156,8 +198,9 @@ void outgoingHeaderRootSpan() { assertThat(outgoingStringHeader).hasSize(55); verifyTraceContextContents(outgoingStringHeader, traceContext.getTraceId().toString(), traceContext.getId().toString(), "00", "01"); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - verifyTraceContextContents(outgoingBinaryTraceContext, traceContext.getTraceId().toString(), + Map headerMap = new HashMap<>(); + assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + verifyTraceContextContents(headerMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME), traceContext.getTraceId().toString(), traceContext.getId().toString(), (byte) 0x00, (byte) 0x01); } @@ -189,42 +232,48 @@ void testResetOutgoingTextHeader() { @Test void testResetOutgoingBinaryHeader() { final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - byte[] tmp = new byte[outgoingBinaryTraceContext.length]; - System.arraycopy(outgoingBinaryTraceContext, 0, tmp, 0, 29); + Map headerMap = new HashMap<>(); + assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + byte[] outgoingByteHeader = headerMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); + byte[] tmp = new byte[outgoingByteHeader.length]; + System.arraycopy(outgoingByteHeader, 0, tmp, 0, outgoingByteHeader.length); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - assertThat(outgoingBinaryTraceContext).isNotEqualTo(tmp); + assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + // relies on the byte array caching in BinaryHeaderMapAccessor + assertThat(outgoingByteHeader).isNotEqualTo(tmp); traceContext.resetState(); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - assertThat(outgoingBinaryTraceContext).isEqualTo(tmp); + assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(outgoingByteHeader).isEqualTo(tmp); } @Test void testCopyFrom() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - - final TraceContext other = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - other.asChildOf("00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); - - assertThat(traceContext.getTraceId()).isNotEqualTo(other.getTraceId()); - assertThat(traceContext.getParentId()).isNotEqualTo(other.getParentId()); - assertThat(traceContext.isSampled()).isNotEqualTo(other.isSampled()); - assertThat(traceContext.getOutgoingTraceParentTextHeader()).isNotEqualTo(other.getOutgoingTraceParentTextHeader()); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - byte[] otherHeader = new byte[outgoingBinaryTraceContext.length]; - other.fillOutgoingTraceParentBinaryHeader(otherHeader); - assertThat(outgoingBinaryTraceContext).isNotEqualTo(otherHeader); - - other.copyFrom(traceContext); - assertThat(traceContext.getTraceId()).isEqualTo(other.getTraceId()); - assertThat(traceContext.getParentId()).isEqualTo(other.getParentId()); - assertThat(traceContext.isSampled()).isEqualTo(other.isSampled()); - assertThat(traceContext.getOutgoingTraceParentTextHeader().toString()).isEqualTo(other.getOutgoingTraceParentTextHeader().toString()); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - other.fillOutgoingTraceParentBinaryHeader(otherHeader); - assertThat(outgoingBinaryTraceContext).isEqualTo(otherHeader); + final TraceContext first = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + first.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + + final TraceContext second = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + second.asChildOf("00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); + + assertThat(first.getTraceId()).isNotEqualTo(second.getTraceId()); + assertThat(first.getParentId()).isNotEqualTo(second.getParentId()); + assertThat(first.isSampled()).isNotEqualTo(second.isSampled()); + assertThat(first.getOutgoingTraceParentTextHeader()).isNotEqualTo(second.getOutgoingTraceParentTextHeader()); + Map binaryHeaderMap = new HashMap<>(); + first.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + byte[] outgoingHeader = binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); + // We must copy because of the byte array caching in BinaryHeaderMapAccessor + byte[] firstOutgoingHeader = new byte[outgoingHeader.length]; + System.arraycopy(outgoingHeader, 0, firstOutgoingHeader, 0, outgoingHeader.length); + second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isNotEqualTo(firstOutgoingHeader); + + second.copyFrom(first); + assertThat(first.getTraceId()).isEqualTo(second.getTraceId()); + assertThat(first.getParentId()).isEqualTo(second.getParentId()); + assertThat(first.isSampled()).isEqualTo(second.isSampled()); + assertThat(first.getOutgoingTraceParentTextHeader().toString()).isEqualTo(second.getOutgoingTraceParentTextHeader().toString()); + second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isEqualTo(firstOutgoingHeader); } @Test @@ -248,7 +297,7 @@ void testSetSampled() { } @Test - void testPropagateTransactionIdForUnsampledSpan_TextFormat() { + void testPropagateTransactionIdForUnsampledSpan() { final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); rootContext.asRootSpan(ConstantSampler.of(false)); @@ -257,13 +306,14 @@ void testPropagateTransactionIdForUnsampledSpan_TextFormat() { verifyTraceContextContents(childContext.getOutgoingTraceParentTextHeader().toString(), childContext.getTraceId().toString(), rootContext.getId().toString(), "00", "00"); - childContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - verifyTraceContextContents(outgoingBinaryTraceContext, childContext.getTraceId().toString(), - rootContext.getId().toString(), (byte) 0x00, (byte) 0x00); + Map binaryHeaderMap = new HashMap<>(); + childContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + verifyTraceContextContents(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME), + childContext.getTraceId().toString(), rootContext.getId().toString(), (byte) 0x00, (byte) 0x00); } @Test - void testPropagateSpanIdForSampledSpan_TextFormat() { + void testPropagateSpanIdForSampledSpan() { final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); rootContext.asRootSpan(ConstantSampler.of(true)); @@ -272,9 +322,10 @@ void testPropagateSpanIdForSampledSpan_TextFormat() { verifyTraceContextContents(childContext.getOutgoingTraceParentTextHeader().toString(), childContext.getTraceId().toString(), childContext.getId().toString(), "00", "01"); - childContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - verifyTraceContextContents(outgoingBinaryTraceContext, childContext.getTraceId().toString(), - childContext.getId().toString(), (byte) 0x00, (byte) 0x01); + Map binaryHeaderMap = new HashMap<>(); + childContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + verifyTraceContextContents(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME), + childContext.getTraceId().toString(), childContext.getId().toString(), (byte) 0x00, (byte) 0x01); } @Test @@ -354,16 +405,18 @@ private void assertValid(String s) { private void assertValid(byte[] binaryHeader) { final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); assertThat(traceContext.asChildOf(binaryHeader)).isTrue(); - traceContext.fillOutgoingTraceParentBinaryHeader(outgoingBinaryTraceContext); - verifyTraceContextContents(outgoingBinaryTraceContext, traceContext.getTraceId().toString(), - traceContext.getId().toString(), (byte) 0x00, binaryHeader[28]); + Map binaryHeaderMap = new HashMap<>(); + traceContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + verifyTraceContextContents(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME), + traceContext.getTraceId().toString(), traceContext.getId().toString(), (byte) 0x00, binaryHeader[28]); } private byte[] convertToBinary(String textHeader) { final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); traceContext.asChildOf(textHeader); - byte[] binaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH]; - traceContext.fillOutgoingTraceParentBinaryHeader(binaryHeader); + Map binaryHeaderMap = new HashMap<>(); + traceContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); + byte[] binaryHeader = binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); // replace the version and parent ID HexUtils.decode(textHeader, 0, 2, binaryHeader, 0); HexUtils.decode(textHeader, 36, 16, binaryHeader, 19); diff --git a/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java b/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java index 3a6a3fc776..4d4ed78901 100644 --- a/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java +++ b/apm-agent-core/src/test/java/org/example/stacktrace/StacktraceSerializationTest.java @@ -11,9 +11,9 @@ * 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 @@ -28,7 +28,6 @@ import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.report.ApmServerClient; import co.elastic.apm.agent.report.serialize.DslJsonSerializer; @@ -149,7 +148,7 @@ void testStackTraceElementSerialization() throws IOException { private List getStackTrace() throws IOException { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()); final Span span = transaction.createSpan(); span.end(); transaction.end(); diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java index 0c888ce6c7..b6e5560004 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -24,24 +24,25 @@ */ package co.elastic.apm.agent.httpclient; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.httpclient.helper.ApacheHttpAsyncClientHelper; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.http.HttpRequest; import org.apache.http.concurrent.FutureCallback; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; import org.apache.http.protocol.HttpContext; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collection; import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; @@ -52,10 +53,11 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class ApacheHttpAsyncClientInstrumentation extends ElasticApmInstrumentation { +public class ApacheHttpAsyncClientInstrumentation extends BaseApacheHttpClientInstrumentation { // Referencing specific Apache HTTP client classes are allowed due to type erasure - public static HelperClassManager, HttpContext>> helperManager; + @VisibleForAdvice + public static HelperClassManager, HttpContext, HttpRequest>> asyncHelperManager; private static class ApacheHttpAsyncClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) @@ -74,11 +76,12 @@ private static void onBeforeExecute(@Advice.Argument(value = 0, readOnly = false .withSubtype(HttpClientHelper.HTTP_SUBTYPE) .activate(); - ApacheHttpAsyncClientHelper, HttpContext> helper = - helperManager.getForClassLoaderOfClass(HttpAsyncRequestProducer.class); - if (helper != null) { - requestProducer = helper.wrapRequestProducer(requestProducer, span); - futureCallback = helper.wrapFutureCallback(futureCallback, context, span); + ApacheHttpAsyncClientHelper, HttpContext, HttpRequest> asyncHelper = + asyncHelperManager.getForClassLoaderOfClass(HttpAsyncRequestProducer.class); + TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); + if (asyncHelper != null && headerSetter != null) { + requestProducer = asyncHelper.wrapRequestProducer(requestProducer, span, headerSetter); + futureCallback = asyncHelper.wrapFutureCallback(futureCallback, context, span); wrapped = true; } } @@ -102,12 +105,13 @@ public static void onAfterExecute(@Advice.Local("span") @Nullable Span span, } public ApacheHttpAsyncClientInstrumentation(ElasticApmTracer tracer) { - helperManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.httpclient.ApacheHttpAsyncClientHelperImpl", - "co.elastic.apm.agent.httpclient.HttpAsyncRequestProducerWrapper", - "co.elastic.apm.agent.httpclient.ApacheHttpAsyncClientHelperImpl$RequestProducerWrapperAllocator", - "co.elastic.apm.agent.httpclient.FutureCallbackWrapper", - "co.elastic.apm.agent.httpclient.ApacheHttpAsyncClientHelperImpl$FutureCallbackWrapperAllocator"); + super(tracer); + asyncHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.httpclient.helper.ApacheHttpAsyncClientHelperImpl", + "co.elastic.apm.agent.httpclient.helper.HttpAsyncRequestProducerWrapper", + "co.elastic.apm.agent.httpclient.helper.ApacheHttpAsyncClientHelperImpl$RequestProducerWrapperAllocator", + "co.elastic.apm.agent.httpclient.helper.FutureCallbackWrapper", + "co.elastic.apm.agent.httpclient.helper.ApacheHttpAsyncClientHelperImpl$FutureCallbackWrapperAllocator"); } @Override @@ -140,9 +144,4 @@ public ElementMatcher getMethodMatcher() { .and(takesArgument(2, named("org.apache.http.protocol.HttpContext"))) .and(takesArgument(3, named("org.apache.http.concurrent.FutureCallback"))); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "apache-httpclient"); - } } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java index 4f69bb19c8..55faae2e92 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java @@ -24,7 +24,9 @@ */ package co.elastic.apm.agent.httpclient; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.NamedElement; @@ -36,8 +38,6 @@ import javax.annotation.Nullable; import java.security.ProtectionDomain; -import java.util.Arrays; -import java.util.Collection; import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.implementationVersionLte; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; @@ -47,7 +47,11 @@ /** * In versions 4.0.1 or lower, the headers are not automatically copied to redirected HttpRequests, so this copies them over */ -public class ApacheHttpAsyncClientRedirectInstrumentation extends ElasticApmInstrumentation { +public class ApacheHttpAsyncClientRedirectInstrumentation extends BaseApacheHttpClientInstrumentation { + + public ApacheHttpAsyncClientRedirectInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } private static class ApacheHttpAsyncClientRedirectAdvice { @Advice.OnMethodExit(suppress = Throwable.class) @@ -57,10 +61,11 @@ private static void onAfterExecute(@Advice.Argument(value = 0) HttpRequest origi return; } // org.apache.http.HttpMessage#containsHeader implementations do not allocate iterator since 4.0.1 - if (original.containsHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME) && !redirect.containsHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME)) { - String traceContext = original.getFirstHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME).getValue(); - if (traceContext != null) { - redirect.setHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, traceContext); + TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); + TextHeaderGetter headerGetter = headerGetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); + if (headerGetter != null && headerSetter != null) { + if (TraceContext.containsTraceContextTextHeaders(original, headerGetter) && !TraceContext.containsTraceContextTextHeaders(redirect, headerGetter)) { + TraceContext.copyTextHeaders(original, headerGetter, redirect, headerSetter); } } } @@ -94,9 +99,4 @@ public ElementMatcher.Junction getImplementationVersionPostFil public ElementMatcher getMethodMatcher() { return named("getRedirect"); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "apache-httpclient"); - } } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java index ce88a712e5..ccce8ba71b 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java @@ -24,9 +24,12 @@ */ package co.elastic.apm.agent.httpclient; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.httpclient.helper.RequestHeaderAccessor; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; @@ -34,13 +37,12 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.apache.http.HttpRequest; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpRequestWrapper; import org.apache.http.conn.routing.HttpRoute; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collection; import static co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; @@ -53,7 +55,11 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @SuppressWarnings("Duplicates") -public class ApacheHttpClientInstrumentation extends ElasticApmInstrumentation { +public class ApacheHttpClientInstrumentation extends BaseApacheHttpClientInstrumentation { + + public ApacheHttpClientInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } private static class ApacheHttpClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) @@ -65,12 +71,17 @@ private static void onBeforeExecute(@Advice.Argument(0) HttpRoute route, } final TraceContextHolder parent = tracer.getActive(); span = HttpClientHelper.startHttpClientSpan(parent, request.getMethod(), request.getURI(), route.getTargetHost().getHostName()); + TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); + TextHeaderGetter headerGetter = headerGetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); if (span != null) { span.activate(); - request.addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); - } else if (!request.containsHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME) && parent != null) { + if (headerSetter != null) { + span.getTraceContext().setOutgoingTraceContextHeaders(request, new RequestHeaderAccessor()); + } + } else if (headerGetter != null && !TraceContext.containsTraceContextTextHeaders(request, headerGetter) + && headerSetter != null && parent != null) { // re-adds the header on redirects - request.addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, parent.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + parent.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); } } @@ -123,9 +134,4 @@ public ElementMatcher getMethodMatcher() { .and(takesArgument(2, hasSuperType(named("org.apache.http.client.protocol.HttpClientContext")))) .and(takesArgument(3, hasSuperType(named("org.apache.http.client.methods.HttpExecutionAware")))); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "apache-httpclient"); - } } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/BaseApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/BaseApacheHttpClientInstrumentation.java new file mode 100644 index 0000000000..0b2463ef8e --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/BaseApacheHttpClientInstrumentation.java @@ -0,0 +1,68 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.httpclient; + +import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import org.apache.http.HttpRequest; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; + +public abstract class BaseApacheHttpClientInstrumentation extends ElasticApmInstrumentation { + + // Referencing specific Apache HTTP client classes are allowed due to type erasure + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerGetterHelperClassManager; + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerSetterHelperClassManager; + + public BaseApacheHttpClientInstrumentation(ElasticApmTracer tracer) { + if (headerGetterHelperClassManager == null) { + synchronized (BaseApacheHttpClientInstrumentation.class) { + if (headerGetterHelperClassManager == null) { + headerGetterHelperClassManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.httpclient.helper.RequestHeaderAccessor" + ); + headerSetterHelperClassManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.httpclient.helper.RequestHeaderAccessor" + ); + } + } + } + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "apache-httpclient"); + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/LegacyApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/LegacyApacheHttpClientInstrumentation.java index 224e610712..8aa58e92e1 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/LegacyApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/LegacyApacheHttpClientInstrumentation.java @@ -24,9 +24,11 @@ */ package co.elastic.apm.agent.httpclient; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; @@ -40,8 +42,6 @@ import org.apache.http.client.methods.HttpUriRequest; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collection; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; import static net.bytebuddy.matcher.ElementMatchers.nameContains; @@ -51,7 +51,11 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @SuppressWarnings("Duplicates") -public class LegacyApacheHttpClientInstrumentation extends ElasticApmInstrumentation { +public class LegacyApacheHttpClientInstrumentation extends BaseApacheHttpClientInstrumentation { + + public LegacyApacheHttpClientInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } private static class LegacyApacheHttpClientAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) @@ -66,12 +70,17 @@ private static void onBeforeExecute(@Advice.Argument(0) HttpHost host, if (request instanceof HttpUriRequest) { HttpUriRequest uriRequest = (HttpUriRequest) request; span = HttpClientHelper.startHttpClientSpan(parent, uriRequest.getMethod(), uriRequest.getURI(), host.getHostName()); + TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); + TextHeaderGetter headerGetter = headerGetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); if (span != null) { span.activate(); - request.addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); - } else if (!request.containsHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME) && parent != null) { + if (headerSetter != null) { + span.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); + } + } else if (headerGetter != null && !TraceContext.containsTraceContextTextHeaders(request, headerGetter) + && headerSetter != null && parent != null) { // re-adds the header on redirects - request.addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, parent.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + parent.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); } } @@ -119,9 +128,4 @@ public ElementMatcher getMethodMatcher() { .and(takesArgument(1, hasSuperType(named("org.apache.http.HttpRequest")))) .and(takesArgument(2, hasSuperType(named("org.apache.http.protocol.HttpContext")))); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "apache-httpclient"); - } } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelper.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelper.java similarity index 79% rename from apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelper.java rename to apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelper.java index b53f5cbce2..580e7e5102 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelper.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelper.java @@ -11,9 +11,9 @@ * 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 @@ -22,12 +22,13 @@ * under the License. * #L% */ -package co.elastic.apm.agent.httpclient; +package co.elastic.apm.agent.httpclient.helper; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; -public interface ApacheHttpAsyncClientHelper { - P wrapRequestProducer(P requestProducer, Span span); +public interface ApacheHttpAsyncClientHelper { + P wrapRequestProducer(P requestProducer, Span span, TextHeaderSetter clientHelper); F wrapFutureCallback(F futureCallback, C context, Span span); } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelperImpl.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelperImpl.java similarity index 91% rename from apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelperImpl.java rename to apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelperImpl.java index 5cc9929015..33ff748737 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientHelperImpl.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/ApacheHttpAsyncClientHelperImpl.java @@ -11,9 +11,9 @@ * 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 @@ -22,12 +22,14 @@ * under the License. * #L% */ -package co.elastic.apm.agent.httpclient; +package co.elastic.apm.agent.httpclient.helper; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.objectpool.Allocator; import co.elastic.apm.agent.objectpool.ObjectPool; import co.elastic.apm.agent.objectpool.impl.QueueBasedObjectPool; +import org.apache.http.HttpRequest; import org.apache.http.concurrent.FutureCallback; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; import org.apache.http.protocol.HttpContext; @@ -35,7 +37,7 @@ import static org.jctools.queues.spec.ConcurrentQueueSpec.createBoundedMpmc; -public class ApacheHttpAsyncClientHelperImpl implements ApacheHttpAsyncClientHelper, HttpContext> { +public class ApacheHttpAsyncClientHelperImpl implements ApacheHttpAsyncClientHelper, HttpContext, HttpRequest> { private static final int MAX_POOLED_ELEMENTS = 256; @@ -67,8 +69,8 @@ public FutureCallbackWrapper createInstance() { } @Override - public HttpAsyncRequestProducer wrapRequestProducer(HttpAsyncRequestProducer requestProducer, Span span) { - return requestProducerWrapperObjectPool.createInstance().with(requestProducer, span); + public HttpAsyncRequestProducer wrapRequestProducer(HttpAsyncRequestProducer requestProducer, Span span, TextHeaderSetter headerSetter) { + return requestProducerWrapperObjectPool.createInstance().with(requestProducer, span, headerSetter); } @Override diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/FutureCallbackWrapper.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/FutureCallbackWrapper.java similarity index 93% rename from apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/FutureCallbackWrapper.java rename to apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/FutureCallbackWrapper.java index e0b47cf1e1..be00bade95 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/FutureCallbackWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/FutureCallbackWrapper.java @@ -11,9 +11,9 @@ * 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 @@ -22,7 +22,7 @@ * under the License. * #L% */ -package co.elastic.apm.agent.httpclient; +package co.elastic.apm.agent.httpclient.helper; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.objectpool.Recyclable; @@ -34,10 +34,12 @@ import javax.annotation.Nullable; -public class FutureCallbackWrapper implements FutureCallback, Recyclable { +class FutureCallbackWrapper implements FutureCallback, Recyclable { private final ApacheHttpAsyncClientHelperImpl helper; - @Nullable private FutureCallback delegate; - @Nullable private HttpContext context; + @Nullable + private FutureCallback delegate; + @Nullable + private HttpContext context; private volatile Span span; FutureCallbackWrapper(ApacheHttpAsyncClientHelperImpl helper) { diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/HttpAsyncRequestProducerWrapper.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/HttpAsyncRequestProducerWrapper.java similarity index 85% rename from apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/HttpAsyncRequestProducerWrapper.java rename to apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/HttpAsyncRequestProducerWrapper.java index 5bf5c3c5b9..88b7ccc352 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/HttpAsyncRequestProducerWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/HttpAsyncRequestProducerWrapper.java @@ -22,11 +22,11 @@ * under the License. * #L% */ -package co.elastic.apm.agent.httpclient; +package co.elastic.apm.agent.httpclient.helper; import co.elastic.apm.agent.http.client.HttpClientHelper; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.objectpool.Recyclable; import org.apache.http.HttpException; import org.apache.http.HttpHost; @@ -39,18 +39,20 @@ import java.io.IOException; -public class HttpAsyncRequestProducerWrapper implements HttpAsyncRequestProducer, Recyclable { - private final ApacheHttpAsyncClientHelperImpl helper; +class HttpAsyncRequestProducerWrapper implements HttpAsyncRequestProducer, Recyclable { + private final ApacheHttpAsyncClientHelperImpl asyncClientHelper; private volatile HttpAsyncRequestProducer delegate; private Span span; + private TextHeaderSetter headerSetter; HttpAsyncRequestProducerWrapper(ApacheHttpAsyncClientHelperImpl helper) { - this.helper = helper; + this.asyncClientHelper = helper; } - public HttpAsyncRequestProducerWrapper with(HttpAsyncRequestProducer delegate, Span span) { + public HttpAsyncRequestProducerWrapper with(HttpAsyncRequestProducer delegate, Span span, TextHeaderSetter headerSetter) { // Order is important due to visibility - write to delegate last on this (initiating) thread this.span = span; + this.headerSetter = headerSetter; this.delegate = delegate; return this; } @@ -72,8 +74,7 @@ public HttpRequest generateRequest() throws IOException, HttpException { span.withName(method).appendToName(" "); span.getContext().getHttp().withMethod(method).withUrl(requestLine.getUri()); } - - request.setHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + span.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); } HttpHost host = getTarget(); @@ -118,13 +119,14 @@ public void resetRequest() throws IOException { @Override public void close() throws IOException { delegate.close(); - helper.recycle(this); + asyncClientHelper.recycle(this); } @Override public void resetState() { // Order is important due to visibility - write to delegate last span = null; + headerSetter = null; delegate = null; } } diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java new file mode 100644 index 0000000000..eda60b1ad9 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java @@ -0,0 +1,65 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.httpclient.helper; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import org.apache.http.Header; +import org.apache.http.HttpRequest; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class RequestHeaderAccessor implements TextHeaderGetter, TextHeaderSetter { + @Override + public void setHeader(String headerName, String headerValue, HttpRequest request) { + request.setHeader(headerName, headerValue); + } + + @Nullable + @Override + public String getFirstHeader(String headerName, HttpRequest request) { + Header header = request.getFirstHeader(headerName); + if (header != null) { + return header.getValue(); + } + return null; + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, HttpRequest request) { + Header[] headers = request.getHeaders(headerName); + if (headers == null) { + return null; + } + List ret = new ArrayList<>(); + for (Header header : headers) { + ret.add(header.getValue()); + } + return ret; + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/package-info.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/package-info.java new file mode 100644 index 0000000000..32121d9716 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/package-info.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +@NonnullApi +package co.elastic.apm.agent.httpclient.helper; + +import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java index 3c14bda6f0..65e2611615 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java @@ -288,10 +288,11 @@ public InjectTraceHeadersInstrumentation() { @VisibleForAdvice @Advice.OnMethodExit(suppress = Throwable.class) public static void injectTraceHeaders(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) TraceContextHolder context, - @Advice.Argument(0) MethodHandle addHeader, + @Advice.Argument(0) MethodHandle addHeaderMethodHandle, @Advice.Argument(1) @Nullable Object headerInjector) throws Throwable { if (headerInjector != null) { - addHeader.invoke(headerInjector, TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, context.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + HeaderInjectorBridge.instance().setAddHeaderMethodHandle(addHeaderMethodHandle); + context.getTraceContext().setOutgoingTraceContextHeaders(headerInjector, HeaderInjectorBridge.instance()); } } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/CaptureTransactionInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/CaptureTransactionInstrumentation.java index 529e96fafd..41b530cc65 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/CaptureTransactionInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/CaptureTransactionInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.NamedElement; @@ -74,7 +73,7 @@ public static void onMethodEnter(@Advice.Origin Class clazz, if (tracer != null) { final Object active = tracer.getActive(); if (active == null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); + transaction = tracer.startRootTransaction(clazz.getClassLoader()); if (transactionName.isEmpty()) { transaction.withName(signature, PRIO_METHOD_SIGNATURE); } else { diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java index b70210ba9a..e74802c6ed 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java @@ -25,7 +25,6 @@ package co.elastic.apm.agent.plugin.api; import co.elastic.apm.agent.bci.VisibleForAdvice; -import co.elastic.apm.agent.impl.transaction.TraceContext; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -33,7 +32,6 @@ import javax.annotation.Nullable; import java.lang.invoke.MethodHandle; -import java.util.Iterator; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -68,7 +66,7 @@ public StartTransactionInstrumentation() { @Advice.OnMethodExit(suppress = Throwable.class) private static void doStartTransaction(@Advice.Origin Class clazz, @Advice.Return(readOnly = false) Object transaction) { if (tracer != null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); + transaction = tracer.startRootTransaction(clazz.getClassLoader()); } } } @@ -86,21 +84,18 @@ private static void doStartTransaction(@Advice.Origin Class clazz, @Advice.Argument(0) MethodHandle getFirstHeader, @Advice.Argument(1) @Nullable Object headerExtractor, @Advice.Argument(2) MethodHandle getAllHeaders, - @Advice.Argument(3) @Nullable Object headersExtractor) throws Throwable { + @Advice.Argument(3) @Nullable Object headersExtractor) { if (tracer != null) { if (headerExtractor != null) { - final String traceparentHeader = (String) getFirstHeader.invoke(headerExtractor, TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME); - transaction = tracer.startTransaction(TraceContext.fromTraceparentHeader(), traceparentHeader, clazz.getClassLoader()); + HeaderExtractorBridge headerExtractorBridge = HeaderExtractorBridge.instance(); + headerExtractorBridge.setGetHeaderMethodHandle(getFirstHeader); + transaction = tracer.startChildTransaction(headerExtractor, headerExtractorBridge, clazz.getClassLoader()); } else if (headersExtractor != null) { - final Iterable traceparentHeader = (Iterable) getAllHeaders.invoke(headersExtractor, TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME); - final Iterator iterator = traceparentHeader.iterator(); - if (iterator.hasNext()) { - transaction = tracer.startTransaction(TraceContext.fromTraceparentHeader(), iterator.next(), clazz.getClassLoader()); - } else { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); - } + HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.instance(); + headersExtractorBridge.setGetHeaderMethodHandle(getAllHeaders); + transaction = tracer.startChildTransaction(headersExtractor, headersExtractorBridge, clazz.getClassLoader()); } else { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); + transaction = tracer.startRootTransaction(clazz.getClassLoader()); } } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java new file mode 100644 index 0000000000..dc597d8c45 --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java @@ -0,0 +1,84 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.plugin.api; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; + +@VisibleForAdvice +public class HeaderExtractorBridge implements TextHeaderGetter { + + private static final Logger logger = LoggerFactory.getLogger(HeaderExtractorBridge.class); + + private static final HeaderExtractorBridge INSTANCE = new HeaderExtractorBridge(); + + @VisibleForAdvice + public static HeaderExtractorBridge instance() { + return INSTANCE; + } + + @Nullable + private MethodHandle getFirstHeaderMethod; + + private HeaderExtractorBridge() { + } + + @VisibleForAdvice + public void setGetHeaderMethodHandle(MethodHandle getHeaderMethodHandle) { + // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its + // reference a couple of times on startup. Cheaper than volatile access. + if (this.getFirstHeaderMethod == null) { + this.getFirstHeaderMethod = getHeaderMethodHandle; + } + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Object carrier) { + String value = null; + try { + if (getFirstHeaderMethod != null) { + value = (String) getFirstHeaderMethod.invoke(carrier, headerName); + } + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); + } + return value; + } + + /** + * Returns null. {@link HeadersExtractorBridge} should be used instead for this functionality + */ + @Nullable + @Override + public Iterable getHeaders(String headerName, Object carrier) { + return null; + } +} diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java new file mode 100644 index 0000000000..d289ed57f8 --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java @@ -0,0 +1,73 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.plugin.api; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; + +@VisibleForAdvice +public class HeaderInjectorBridge implements TextHeaderSetter { + + private static final Logger logger = LoggerFactory.getLogger(HeaderInjectorBridge.class); + + private static final HeaderInjectorBridge INSTANCE = new HeaderInjectorBridge(); + + @VisibleForAdvice + public static HeaderInjectorBridge instance() { + return INSTANCE; + } + + @Nullable + private MethodHandle addHeaderMethod; + + private HeaderInjectorBridge() { + } + + @VisibleForAdvice + public void setAddHeaderMethodHandle(MethodHandle addMethodHandle) { + // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its + // reference a couple of times on startup. Cheaper than volatile access. + if (this.addHeaderMethod == null) { + this.addHeaderMethod = addMethodHandle; + } + } + + @Override + public void setHeader(String headerName, String headerValue, Object carrier) { + try { + if (addHeaderMethod != null) { + addHeaderMethod.invoke(carrier, headerName, headerValue); + } + } catch (Throwable throwable) { + logger.error("Failed to add trace context headers", throwable); + } + } +} diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java new file mode 100644 index 0000000000..d25f9c73fa --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -0,0 +1,98 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.plugin.api; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.util.Iterator; + +@VisibleForAdvice +public class HeadersExtractorBridge implements TextHeaderGetter { + + private static final Logger logger = LoggerFactory.getLogger(HeadersExtractorBridge.class); + + private static final HeadersExtractorBridge INSTANCE = new HeadersExtractorBridge(); + + @VisibleForAdvice + public static HeadersExtractorBridge instance() { + return INSTANCE; + } + + @Nullable + private MethodHandle getAllHeadersMethod; + + private HeadersExtractorBridge() { + } + + @VisibleForAdvice + public void setGetHeaderMethodHandle(MethodHandle getAllHeadersMethodHandle) { + // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its + // reference a couple of times on startup. Cheaper than volatile access. + if (this.getAllHeadersMethod == null) { + this.getAllHeadersMethod = getAllHeadersMethodHandle; + } + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Object carrier) { + String value = null; + try { + if (getAllHeadersMethod != null) { + //noinspection unchecked + final Iterable headersIterable = (Iterable) getAllHeadersMethod.invoke(carrier, headerName); + if (headersIterable != null) { + final Iterator headersIterator = headersIterable.iterator(); + if (headersIterator.hasNext()) { + value = headersIterator.next(); + } + } + } + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); + } + return value; + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Object carrier) { + Iterable values = null; + if (getAllHeadersMethod != null) { + try { + //noinspection unchecked + values = (Iterable) getAllHeadersMethod.invoke(carrier, headerName); + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); + } + } + return values; + } +} diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TracedInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TracedInstrumentation.java index ba1e3a9997..33767c6b26 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TracedInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TracedInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -25,14 +25,12 @@ package co.elastic.apm.agent.plugin.api; import co.elastic.apm.agent.bci.ElasticApmInstrumentation; -import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.bci.bytebuddy.AnnotationValueOffsetMappingFactory; import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -41,8 +39,6 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.Arrays; @@ -86,7 +82,7 @@ public static void onMethodEnter( .activate(); abstractSpan = span; } else { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); + Transaction transaction = tracer.startRootTransaction(clazz.getClassLoader()); if (spanName.isEmpty()) { transaction.withName(signature, PRIO_METHOD_SIGNATURE); } else { diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java index d35e299b08..f9db33a377 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/agent/plugin/api/SpanInstrumentationTest.java @@ -25,6 +25,8 @@ package co.elastic.apm.agent.plugin.api; import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.TextHeaderMapAccessor; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.api.ElasticApm; import co.elastic.apm.api.Scope; @@ -33,7 +35,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.annotation.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -144,8 +148,7 @@ private void assertContainsTracingHeaders(Span span) { final Map tracingHeaders = new HashMap<>(); span.injectTraceHeaders(tracingHeaders::put); span.injectTraceHeaders(null); - final String traceparent = tracer.getActive().getTraceContext().getOutgoingTraceParentTextHeader().toString(); - assertThat(tracingHeaders).containsEntry(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, traceparent); + assertThat(TraceContext.containsTraceContextTextHeaders(tracingHeaders, TextHeaderMapAccessor.INSTANCE)).isTrue(); } } } diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java index b5fcabd842..0be97ff806 100644 --- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java +++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java @@ -27,11 +27,13 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.Scope; +import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.impl.transaction.TraceContext; import org.junit.jupiter.api.Test; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -92,7 +94,7 @@ void testTransactionWithError() { @Test void testCreateChildSpanOfCurrentTransaction() { - final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("transaction").activate(); + final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("transaction").activate(); final Span span = ElasticApm.currentSpan().startSpan("db", "mysql", "query"); span.setName("span"); span.end(); @@ -108,7 +110,7 @@ void testCreateChildSpanOfCurrentTransaction() { @Test void testLegacySpanCreationAndTyping() { - final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("transaction").activate(); + final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("transaction").activate(); final Span span = ElasticApm.currentSpan().createSpan(); span.setName("span"); span.setType("db.mysql.query.etc"); @@ -126,7 +128,7 @@ void testLegacySpanCreationAndTyping() { // https://github.com/elastic/apm-agent-java/issues/132 @Test void testAutomaticAndManualTransactions() { - final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("transaction").activate(); + final co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("transaction").activate(); final Transaction manualTransaction = ElasticApm.startTransaction(); manualTransaction.setName("manual transaction"); manualTransaction.setType("request"); @@ -137,7 +139,7 @@ void testAutomaticAndManualTransactions() { @Test void testGetId_distributedTracingEnabled() { - co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType(Transaction.TYPE_REQUEST); + co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startRootTransaction(null).withType(Transaction.TYPE_REQUEST); try (Scope scope = transaction.activateInScope()) { assertThat(ElasticApm.currentTransaction().getId()).isEqualTo(transaction.getTraceContext().getId().toString()); assertThat(ElasticApm.currentTransaction().getTraceId()).isEqualTo(transaction.getTraceContext().getTraceId().toString()); @@ -234,7 +236,7 @@ void testScopes() { @Test void testTraceContextScopes() { - co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + co.elastic.apm.agent.impl.transaction.Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()); tracer.activate(transaction.getTraceContext()); final Span span = ElasticApm.currentSpan(); assertThat(tracer.getActive()).isInstanceOf(TraceContext.class); @@ -261,7 +263,9 @@ void testEnsureParentId() { void testTransactionWithRemoteParentFunction() { final TraceContext parent = TraceContext.with64BitId(tracer); parent.asRootSpan(ConstantSampler.of(true)); - ElasticApm.startTransactionWithRemoteParent(key -> parent.getOutgoingTraceParentTextHeader().toString()).end(); + Map headerMap = new HashMap<>(); + parent.getTraceContext().setOutgoingTraceContextHeaders(headerMap, TextHeaderMapAccessor.INSTANCE); + ElasticApm.startTransactionWithRemoteParent(headerMap::get).end(); assertThat(reporter.getFirstTransaction().getTraceContext().isChildOf(parent)).isTrue(); } @@ -269,8 +273,9 @@ void testTransactionWithRemoteParentFunction() { void testTransactionWithRemoteParentFunctions() { final TraceContext parent = TraceContext.with64BitId(tracer); parent.asRootSpan(ConstantSampler.of(true)); - final Map map = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, parent.getOutgoingTraceParentTextHeader().toString()); - ElasticApm.startTransactionWithRemoteParent(map::get, key -> Collections.singletonList(map.get(key))).end(); + Map headerMap = new HashMap<>(); + parent.getTraceContext().setOutgoingTraceContextHeaders(headerMap, TextHeaderMapAccessor.INSTANCE); + ElasticApm.startTransactionWithRemoteParent(headerMap::get, key -> Collections.singletonList(headerMap.get(key))).end(); assertThat(reporter.getFirstTransaction().getTraceContext().isChildOf(parent)).isTrue(); } @@ -278,8 +283,9 @@ void testTransactionWithRemoteParentFunctions() { void testTransactionWithRemoteParentHeaders() { final TraceContext parent = TraceContext.with64BitId(tracer); parent.asRootSpan(ConstantSampler.of(true)); - final Map map = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, parent.getOutgoingTraceParentTextHeader().toString()); - ElasticApm.startTransactionWithRemoteParent(null, key -> Collections.singletonList(map.get(key))).end(); + Map headerMap = new HashMap<>(); + parent.getTraceContext().setOutgoingTraceContextHeaders(headerMap, TextHeaderMapAccessor.INSTANCE); + ElasticApm.startTransactionWithRemoteParent(null, key -> Collections.singletonList(headerMap.get(key))).end(); assertThat(reporter.getFirstTransaction().getTraceContext().isChildOf(parent)).isTrue(); } diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java index ef59b84413..c1b1406fc2 100644 --- a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java @@ -25,10 +25,12 @@ package co.elastic.apm.agent.asynchttpclient; import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; import net.bytebuddy.asm.Advice; @@ -54,9 +56,26 @@ public abstract class AbstractAsyncHttpClientInstrumentation extends ElasticApmInstrumentation { + // Referencing specific AsyncHttpClient classes are allowed due to type erasure + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerSetterManager; + @VisibleForAdvice public static final WeakConcurrentMap, Span> handlerSpanMap = new WeakConcurrentMap.WithInlinedExpunction<>(); + public AbstractAsyncHttpClientInstrumentation(ElasticApmTracer tracer) { + if (headerSetterManager == null) { + synchronized (AbstractAsyncHandlerInstrumentation.class) { + if (headerSetterManager == null) { + headerSetterManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.asynchttpclient.helper.RequestHeaderAccessor" + ); + } + } + } + } + @Override public Collection getInstrumentationGroupNames() { return Arrays.asList("http-client", "asynchttpclient"); @@ -69,6 +88,9 @@ public ElementMatcher.Junction getClassLoaderMatcher() { } public static class AsyncHttpClientInstrumentation extends AbstractAsyncHttpClientInstrumentation { + public AsyncHttpClientInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } @Advice.OnMethodEnter(suppress = Throwable.class) private static void onBeforeExecute(@Advice.Argument(value = 0) Request request, @@ -84,7 +106,13 @@ private static void onBeforeExecute(@Advice.Argument(value = 0) Request request, if (span != null) { span.activate(); - request.getHeaders().add(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + TextHeaderSetter headerSetter = null; + if (headerSetterManager != null) { + headerSetter = headerSetterManager.getForClassLoaderOfClass(Request.class); + } + if (headerSetter != null) { + span.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); + } handlerSpanMap.put(asyncHandler, span); } } @@ -119,7 +147,8 @@ public abstract static class AbstractAsyncHandlerInstrumentation extends Abstrac private final ElementMatcher methodMatcher; - protected AbstractAsyncHandlerInstrumentation(ElementMatcher methodMatcher) { + protected AbstractAsyncHandlerInstrumentation(ElasticApmTracer tracer, ElementMatcher methodMatcher) { + super(tracer); this.methodMatcher = methodMatcher; } @@ -136,8 +165,8 @@ public ElementMatcher getMethodMatcher() { public static class AsyncHandlerOnCompletedInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnCompletedInstrumentation() { - super(named("onCompleted").and(takesArguments(0))); + public AsyncHandlerOnCompletedInstrumentation(ElasticApmTracer tracer) { + super(tracer, named("onCompleted").and(takesArguments(0))); } @Advice.OnMethodEnter(suppress = Throwable.class) @@ -151,8 +180,8 @@ private static void onMethodEnter(@Advice.This AsyncHandler asyncHandler) { public static class AsyncHandlerOnThrowableInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnThrowableInstrumentation() { - super(named("onThrowable").and(takesArguments(Throwable.class))); + public AsyncHandlerOnThrowableInstrumentation(ElasticApmTracer tracer) { + super(tracer, named("onThrowable").and(takesArguments(Throwable.class))); } @Advice.OnMethodEnter(suppress = Throwable.class) @@ -166,8 +195,8 @@ private static void onMethodEnter(@Advice.This AsyncHandler asyncHandler, @Ad public static class AsyncHandlerOnStatusReceivedInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnStatusReceivedInstrumentation() { - super(named("onStatusReceived").and(takesArgument(0, named("org.asynchttpclient.HttpResponseStatus")))); + public AsyncHandlerOnStatusReceivedInstrumentation(ElasticApmTracer tracer) { + super(tracer, named("onStatusReceived").and(takesArgument(0, named("org.asynchttpclient.HttpResponseStatus")))); } @Advice.OnMethodEnter(suppress = Throwable.class) diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java new file mode 100644 index 0000000000..20cda7a56a --- /dev/null +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java @@ -0,0 +1,53 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.asynchttpclient.helper; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import org.asynchttpclient.Request; + +import javax.annotation.Nullable; + +@VisibleForAdvice +public class RequestHeaderAccessor implements TextHeaderGetter, TextHeaderSetter { + + @Override + public void setHeader(String headerName, String headerValue, Request request) { + request.getHeaders().set(headerName, headerValue); + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Request request) { + return request.getHeaders().get(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Request request) { + return request.getHeaders().getAll(headerName); + } +} diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/package-info.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/package-info.java new file mode 100644 index 0000000000..8b7696b059 --- /dev/null +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/package-info.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +@NonnullApi +package co.elastic.apm.agent.asynchttpclient.helper; + +import co.elastic.apm.agent.annotation.NonnullApi; diff --git a/apm-agent-plugins/apm-error-logging-plugin/src/test/java/co/elastic/apm/agent/error/logging/AbstractErrorLoggingInstrumentationTest.java b/apm-agent-plugins/apm-error-logging-plugin/src/test/java/co/elastic/apm/agent/error/logging/AbstractErrorLoggingInstrumentationTest.java index e4ed8d50d4..b70197b33a 100644 --- a/apm-agent-plugins/apm-error-logging-plugin/src/test/java/co/elastic/apm/agent/error/logging/AbstractErrorLoggingInstrumentationTest.java +++ b/apm-agent-plugins/apm-error-logging-plugin/src/test/java/co/elastic/apm/agent/error/logging/AbstractErrorLoggingInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -25,7 +25,6 @@ package co.elastic.apm.agent.error.logging; import co.elastic.apm.agent.AbstractInstrumentationTest; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -37,7 +36,7 @@ abstract class AbstractErrorLoggingInstrumentationTest extends AbstractInstrumen @BeforeEach void startTransaction() { reporter.reset(); - tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + tracer.startRootTransaction(null).activate(); } @AfterEach diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java index ff6fd8f97e..2682595921 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/test/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchRestClientInstrumentationIT_RealReporter.java @@ -11,9 +11,9 @@ * 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 @@ -35,7 +35,6 @@ import co.elastic.apm.agent.impl.payload.Service; import co.elastic.apm.agent.impl.payload.SystemInfo; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.report.ApmServerClient; import co.elastic.apm.agent.report.ApmServerReporter; @@ -171,7 +170,7 @@ public static void stopElasticsearchContainerAndClient() throws IOException { @Before public void startTransaction() { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("transaction"); transaction.withType("request"); transaction.withResult("success"); diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/es/restclient/AbstractEsClientInstrumentationTest.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/es/restclient/AbstractEsClientInstrumentationTest.java index b810f9529e..b1ea912283 100644 --- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/es/restclient/AbstractEsClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-common/src/test/java/co/elastic/apm/agent/es/restclient/AbstractEsClientInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.impl.context.Db; import co.elastic.apm.agent.impl.context.Http; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.After; import org.junit.Before; @@ -69,7 +68,7 @@ public static Iterable data() { @Before public void startTransaction() { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("ES Transaction"); transaction.withType("request"); transaction.withResultIfUnset("success"); diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/src/test/java/co/elastic/apm/agent/hibernate/search/v5_x/HibernateSearch5InstrumentationTest.java b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/src/test/java/co/elastic/apm/agent/hibernate/search/v5_x/HibernateSearch5InstrumentationTest.java index 51b62255ed..5133c2560c 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/src/test/java/co/elastic/apm/agent/hibernate/search/v5_x/HibernateSearch5InstrumentationTest.java +++ b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-5_x/src/test/java/co/elastic/apm/agent/hibernate/search/v5_x/HibernateSearch5InstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -26,7 +26,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.hibernate.search.DeleteFileVisitor; -import co.elastic.apm.agent.impl.transaction.TraceContext; import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; @@ -76,7 +75,7 @@ static void setUp() throws IOException { @BeforeEach void initSingleTest() { - tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + tracer.startRootTransaction(null).activate(); } @AfterEach diff --git a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/src/test/java/co/elastic/apm/agent/hibernate/search/v6_x/HibernateSearch6InstrumentationTest.java b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/src/test/java/co/elastic/apm/agent/hibernate/search/v6_x/HibernateSearch6InstrumentationTest.java index 3d4274f133..b0e2a8fd45 100644 --- a/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/src/test/java/co/elastic/apm/agent/hibernate/search/v6_x/HibernateSearch6InstrumentationTest.java +++ b/apm-agent-plugins/apm-hibernate-search-plugin/apm-hibernate-search-plugin-6_x/src/test/java/co/elastic/apm/agent/hibernate/search/v6_x/HibernateSearch6InstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -26,7 +26,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.hibernate.search.DeleteFileVisitor; -import co.elastic.apm.agent.impl.transaction.TraceContext; import org.hibernate.search.engine.search.dsl.query.SearchQueryContext; import org.hibernate.search.engine.search.query.SearchResult; import org.hibernate.search.mapper.orm.Search; @@ -67,7 +66,7 @@ static void setUp() throws IOException { @BeforeEach void initSingleTest() { - tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + tracer.startRootTransaction(null).activate(); } @AfterEach diff --git a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/http/client/HttpClientHelperTest.java b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/http/client/HttpClientHelperTest.java index b075b212af..d587c8d7b0 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/http/client/HttpClientHelperTest.java +++ b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/http/client/HttpClientHelperTest.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.context.Destination; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +42,7 @@ class HttpClientHelperTest extends AbstractInstrumentationTest { @BeforeEach void beforeTest() { - tracer.startTransaction(TraceContext.asRoot(), null, null) + tracer.startRootTransaction(null) .withName("Test HTTP client") .withType("test") .activate(); diff --git a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java index ed94f059a3..035c974b04 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java @@ -25,17 +25,28 @@ package co.elastic.apm.agent.httpclient; import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.context.Destination; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import javax.annotation.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.any; import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; @@ -65,7 +76,7 @@ public final void setUpWiremock() { .willReturn(seeOther("/"))); wireMockRule.stubFor(get(urlEqualTo("/circular-redirect")) .willReturn(seeOther("/circular-redirect"))); - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()); transaction.withType("request").activate(); } @@ -130,10 +141,19 @@ protected void verifyHttpSpan(String scheme, String host, int port, String path) assertThat(destination.getService().getName().toString()).isEqualTo(baseUrl); assertThat(destination.getService().getResource().toString()).isEqualTo(host + ":" + wireMockRule.port()); assertThat(destination.getService().getType()).isEqualTo("external"); + verifyTraceContextHeaders(reporter.getFirstSpan(), path); + } - final String traceParentHeader = reporter.getFirstSpan().getTraceContext().getOutgoingTraceParentTextHeader().toString(); - verify(anyRequestedFor(urlPathEqualTo(path)) - .withHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, equalTo(traceParentHeader))); + private void verifyTraceContextHeaders(Span span, String path) { + Map headerMap = new HashMap<>(); + span.getTraceContext().setOutgoingTraceContextHeaders(headerMap, TextHeaderMapAccessor.INSTANCE); + assertThat(headerMap).isNotEmpty(); + List loggedRequests = wireMockRule.findAll(anyRequestedFor(urlPathEqualTo(path))); + assertThat(loggedRequests).isNotEmpty(); + loggedRequests.forEach(request -> { + assertThat(TraceContext.containsTraceContextTextHeaders(request, new HeaderAccessor())).isTrue(); + headerMap.forEach((key, value) -> assertThat(request.getHeader(key)).isEqualTo(value)); + }); } @Test @@ -168,11 +188,8 @@ public void testHttpCallRedirect() throws Exception { assertThat(reporter.getSpans().get(0).getContext().getHttp().getUrl()).isEqualTo(getBaseUrl() + path); assertThat(reporter.getSpans().get(0).getContext().getHttp().getStatusCode()).isEqualTo(200); - final String traceParentHeader = reporter.getFirstSpan().getTraceContext().getOutgoingTraceParentTextHeader().toString(); - verify(getRequestedFor(urlPathEqualTo("/redirect")) - .withHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, equalTo(traceParentHeader))); - verify(getRequestedFor(urlPathEqualTo("/")) - .withHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, equalTo(traceParentHeader))); + verifyTraceContextHeaders(reporter.getFirstSpan(), "/redirect"); + verifyTraceContextHeaders(reporter.getFirstSpan(), "/"); } @Test @@ -187,9 +204,7 @@ public void testHttpCallCircularRedirect() throws Exception { assertThat(reporter.getFirstError().getException().getClass()).isNotNull(); assertThat(reporter.getSpans().get(0).getContext().getHttp().getUrl()).isEqualTo(getBaseUrl() + path); - final String traceParentHeader = reporter.getFirstSpan().getTraceContext().getOutgoingTraceParentTextHeader().toString(); - verify(getRequestedFor(urlPathEqualTo("/circular-redirect")) - .withHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, equalTo(traceParentHeader))); + verifyTraceContextHeaders(reporter.getFirstSpan(), "/circular-redirect"); } protected String getBaseUrl() { @@ -206,4 +221,25 @@ protected void performGetWithinTransaction(String path) { protected abstract void performGet(String path) throws Exception; + + private static class HeaderAccessor implements TextHeaderGetter { + @Nullable + @Override + public String getFirstHeader(String headerName, LoggedRequest loggedRequest) { + return loggedRequest.getHeader(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, LoggedRequest loggedRequest) { + HttpHeaders headers = loggedRequest.getHeaders(); + if (headers != null) { + HttpHeader header = headers.getHeader(headerName); + if (header != null) { + return header.values(); + } + } + return null; + } + } } diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExcludedExecutorClassTest.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExcludedExecutorClassTest.java index 7308925d44..7cd088dafb 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExcludedExecutorClassTest.java +++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExcludedExecutorClassTest.java @@ -11,9 +11,9 @@ * 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 @@ -25,7 +25,6 @@ package co.elastic.apm.agent.concurrent; import co.elastic.apm.agent.AbstractInstrumentationTest; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.After; import org.junit.Before; @@ -45,7 +44,7 @@ public class ExcludedExecutorClassTest extends AbstractInstrumentationTest { public void setUp() { executor = new ExecutorServiceWrapper(Executors.newFixedThreadPool(1)); ExecutorInstrumentation.excludedClasses.add(ExecutorServiceWrapper.class.getName()); - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withName("Transaction").activate(); + transaction = tracer.startRootTransaction(null).withName("Transaction").activate(); } @After diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentationTest.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentationTest.java index 29e97eaf5b..b76e8edb8a 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentationTest.java +++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -25,7 +25,6 @@ package co.elastic.apm.agent.concurrent; import co.elastic.apm.agent.AbstractInstrumentationTest; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.After; import org.junit.Before; @@ -59,7 +58,7 @@ public static Iterable> data() { @Before public void setUp() { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withName("Transaction").activate(); + transaction = tracer.startRootTransaction(null).withName("Transaction").activate(); } @After diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceDoubleWrappingTest.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceDoubleWrappingTest.java index 7785f53dbf..636e1b5e8c 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceDoubleWrappingTest.java +++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceDoubleWrappingTest.java @@ -11,9 +11,9 @@ * 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 @@ -26,7 +26,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.After; import org.junit.Before; @@ -46,7 +45,7 @@ public class ExecutorServiceDoubleWrappingTest extends AbstractInstrumentationTe @Before public void setUp() { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withName("Transaction").activate(); + transaction = tracer.startRootTransaction(null).withName("Transaction").activate(); } @After diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceInstrumentationTest.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceInstrumentationTest.java index 16ea3597cd..8926bc45c1 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceInstrumentationTest.java +++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -25,7 +25,6 @@ package co.elastic.apm.agent.concurrent; import co.elastic.apm.agent.AbstractInstrumentationTest; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.After; @@ -63,7 +62,7 @@ public static Iterable> data() { @Before public void setUp() { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withName("Transaction").activate(); + transaction = tracer.startRootTransaction(null).withName("Transaction").activate(); } @After diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/FailingExecutorInstrumentationTest.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/FailingExecutorInstrumentationTest.java index 9f185b4c83..f858a84cc3 100644 --- a/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/FailingExecutorInstrumentationTest.java +++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/FailingExecutorInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.async.SpanInScopeCallableWrapper; import co.elastic.apm.agent.impl.async.SpanInScopeRunnableWrapper; -import co.elastic.apm.agent.impl.transaction.TraceContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -78,7 +77,7 @@ public ForkJoinTask submit(Runnable task, T result) { throw new UnsupportedOperationException(); } }); - tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + tracer.startRootTransaction(null).activate(); runCounter = new AtomicInteger(); submitWithWrapperCounter = new AtomicInteger(); } diff --git a/apm-agent-plugins/apm-jaxrs-plugin/src/test/java/co/elastic/apm/agent/jaxrs/JaxRsTransactionNameInstrumentationTest.java b/apm-agent-plugins/apm-jaxrs-plugin/src/test/java/co/elastic/apm/agent/jaxrs/JaxRsTransactionNameInstrumentationTest.java index 3b2f3e6e9d..dcef9df260 100644 --- a/apm-agent-plugins/apm-jaxrs-plugin/src/test/java/co/elastic/apm/agent/jaxrs/JaxRsTransactionNameInstrumentationTest.java +++ b/apm-agent-plugins/apm-jaxrs-plugin/src/test/java/co/elastic/apm/agent/jaxrs/JaxRsTransactionNameInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.agent.ByteBuddyAgent; import org.glassfish.jersey.server.ResourceConfig; @@ -274,7 +273,7 @@ protected Application configure() { * @param path the path to make the get request against */ private void doRequest(String path) { - final Transaction request = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").activate(); + final Transaction request = tracer.startRootTransaction(null).withType("request").activate(); try { assertThat(getClient().target(getBaseUri()).path(path).request().buildGet().invoke(String.class)).isEqualTo("ok"); } finally { diff --git a/apm-agent-plugins/apm-jaxws-plugin/src/test/java/co/elastic/apm/agent/jaxws/JaxWsTransactionNameInstrumentationTest.java b/apm-agent-plugins/apm-jaxws-plugin/src/test/java/co/elastic/apm/agent/jaxws/JaxWsTransactionNameInstrumentationTest.java index 7efc304b65..d280f2342f 100644 --- a/apm-agent-plugins/apm-jaxws-plugin/src/test/java/co/elastic/apm/agent/jaxws/JaxWsTransactionNameInstrumentationTest.java +++ b/apm-agent-plugins/apm-jaxws-plugin/src/test/java/co/elastic/apm/agent/jaxws/JaxWsTransactionNameInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -26,7 +26,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.Scope; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,7 +47,7 @@ void setUp() { @Test void testTransactionName() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()); try (Scope scope = transaction.activateInScope()) { helloWorldService.sayHello(); } finally { diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java index 36292d03d9..79541be20a 100644 --- a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java +++ b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -28,7 +28,6 @@ import co.elastic.apm.agent.impl.context.Db; import co.elastic.apm.agent.impl.context.Destination; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.jdbc.signature.SignatureParser; import org.junit.After; @@ -73,7 +72,7 @@ public abstract class AbstractJdbcInstrumentationTest extends AbstractInstrument this.expectedDbVendor = expectedDbVendor; connection.createStatement().execute("CREATE TABLE ELASTIC_APM (FOO INT, BAR VARCHAR(255))"); connection.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (1, 'APM')"); - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + transaction = tracer.startRootTransaction(null).activate(); transaction.withName("transaction"); transaction.withType("request"); transaction.withResultIfUnset("success"); diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/BaseJmsInstrumentation.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/BaseJmsInstrumentation.java index 4f370fd325..f96603e16b 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/BaseJmsInstrumentation.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/BaseJmsInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -58,7 +58,8 @@ private synchronized static void init(ElasticApmTracer tracer) { if (jmsInstrHelperManager == null) { jmsInstrHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, "co.elastic.apm.agent.jms.JmsInstrumentationHelperImpl", - "co.elastic.apm.agent.jms.JmsInstrumentationHelperImpl$MessageListenerWrapper"); + "co.elastic.apm.agent.jms.JmsInstrumentationHelperImpl$MessageListenerWrapper", + "co.elastic.apm.agent.jms.JmsMessagePropertyAccessor"); } messagingConfiguration = tracer.getConfig(MessagingConfiguration.class); diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java index 137ff02e33..240ebe3526 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java @@ -28,6 +28,7 @@ import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.Transaction; import javax.annotation.Nullable; @@ -72,6 +73,10 @@ public interface JmsInstrumentationHelper { @Nullable Span startJmsSendSpan(D destination, M message); + Transaction startJmsTransaction(M parentMessage, Class instrumentedClass); + + void makeChildOf(Transaction childTransaction, M parentMessage); + @Nullable L wrapLambda(@Nullable L listener); diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java index fdfc7e1f92..6ed56f0b00 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java @@ -30,7 +30,9 @@ import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; +import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,7 +99,7 @@ public Span startJmsSendSpan(Destination destination, Message message) { .activate(); try { - message.setStringProperty(JMS_TRACE_PARENT_PROPERTY, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + span.getTraceContext().setOutgoingTraceContextHeaders(message, JmsMessagePropertyAccessor.instance()); if (span.isSampled()) { span.getContext().getDestination().getService() .withName("jms") @@ -112,13 +114,22 @@ public Span startJmsSendSpan(Destination destination, Message message) { } } } - } catch (JMSException e) { logger.error("Failed to capture JMS span", e); } return span; } + @Override + public Transaction startJmsTransaction(Message parentMessage, Class instrumentedClass) { + return tracer.startChildTransaction(parentMessage, JmsMessagePropertyAccessor.instance(), instrumentedClass.getClassLoader()); + } + + @Override + public void makeChildOf(Transaction childTransaction, Message parentMessage) { + TraceContext.getFromTraceContextTextHeaders().asChildOf(childTransaction.getTraceContext(), parentMessage, JmsMessagePropertyAccessor.instance()); + } + @VisibleForAdvice @Override @Nullable diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java index c9ce9de897..579c54ac4b 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -29,7 +29,6 @@ import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -46,7 +45,6 @@ import javax.jms.Message; import javax.jms.MessageListener; -import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.JMS_TRACE_PARENT_PROPERTY; import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.MESSAGE_HANDLING; import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.MESSAGE_POLLING; import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.MESSAGING_TYPE; @@ -150,7 +148,7 @@ public static AbstractSpan beforeReceive(@Advice.Origin Class clazz, .withSubtype("jms") .withAction("receive"); } else if (createPollingTransaction) { - createdSpan = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()) + createdSpan = tracer.startRootTransaction(clazz.getClassLoader()) .withType(MESSAGE_POLLING); } @@ -173,14 +171,12 @@ public static void afterReceive(@Advice.Origin Class clazz, JmsInstrumentationHelper helper = jmsInstrHelperManager.getForClassLoaderOfClass(MessageListener.class); - String messageSenderContext = null; Destination destination = null; String destinationName = null; boolean discard = false; boolean addDetails = true; if (message != null) { try { - messageSenderContext = message.getStringProperty(JMS_TRACE_PARENT_PROPERTY); destination = message.getJMSDestination(); if (helper != null) { destinationName = helper.extractDestinationName(message, destination); @@ -191,16 +187,13 @@ public static void afterReceive(@Advice.Origin Class clazz, } if (abstractSpan instanceof Transaction) { + Transaction transaction = (Transaction) abstractSpan; if (discard) { - ((Transaction) abstractSpan).ignoreTransaction(); - } else { - if (messageSenderContext != null) { - abstractSpan.getTraceContext().asChildOf(messageSenderContext); - } - ((Transaction) abstractSpan).withType(MESSAGING_TYPE); - if (helper != null) { - helper.addMessageDetails(message, abstractSpan); - } + transaction.ignoreTransaction(); + } else if (helper != null) { + helper.makeChildOf(transaction, message); + transaction.withType(MESSAGING_TYPE); + helper.addMessageDetails(message, abstractSpan); } } } else if (abstractSpan instanceof Transaction) { @@ -226,16 +219,16 @@ public static void afterReceive(@Advice.Origin Class clazz, } } - if (!discard && messageSenderContext != null && tracer != null && tracer.currentTransaction() == null - && messagingConfiguration != null + if (!discard && tracer != null && tracer.currentTransaction() == null && helper != null + && message != null && messagingConfiguration != null && messagingConfiguration.getMessagePollingTransactionStrategy() != MessagingConfiguration.Strategy.POLLING && !"receiveNoWait".equals(methodName)) { - Transaction messageHandlingTransaction = tracer.startTransaction(TraceContext.fromTraceparentHeader(), messageSenderContext, clazz.getClassLoader()) + Transaction messageHandlingTransaction = helper.startJmsTransaction(message, clazz) .withType(MESSAGE_HANDLING) .withName(RECEIVE_NAME_PREFIX); - if (helper != null && destinationName != null) { + if (destinationName != null) { messageHandlingTransaction.appendToName(" from "); helper.addDestinationDetails(message, destination, destinationName, messageHandlingTransaction); helper.addMessageDetails(message, messageHandlingTransaction); @@ -244,6 +237,7 @@ public static void afterReceive(@Advice.Origin Class clazz, messageHandlingTransaction.activate(); } + } } } diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageListenerInstrumentation.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageListenerInstrumentation.java index 6faa793f95..09ee7ef26d 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageListenerInstrumentation.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageListenerInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -26,7 +26,6 @@ import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -41,7 +40,6 @@ import javax.jms.Message; import javax.jms.MessageListener; -import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.JMS_TRACE_PARENT_PROPERTY; import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.MESSAGING_TYPE; import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.RECEIVE_NAME_PREFIX; import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; @@ -106,7 +104,11 @@ public static Transaction beforeOnMessage(@Advice.Argument(0) @Nullable final Me //noinspection ConstantConditions JmsInstrumentationHelper helper = jmsInstrHelperManager.getForClassLoaderOfClass(MessageListener.class); - if (helper != null && destination != null) { + if (helper == null) { + return null; + } + + if (destination != null) { destinationName = helper.extractDestinationName(message, destination); if (helper.ignoreDestination(destinationName)) { return null; @@ -114,30 +116,15 @@ public static Transaction beforeOnMessage(@Advice.Argument(0) @Nullable final Me } // Create a transaction - even if running on same JVM as the sender - Transaction transaction = null; + Transaction transaction = helper.startJmsTransaction(message, clazz) + .withType(MESSAGING_TYPE) + .withName(RECEIVE_NAME_PREFIX); - try { - String traceParentProperty = message.getStringProperty(JMS_TRACE_PARENT_PROPERTY); - if (traceParentProperty != null) { - transaction = tracer.startTransaction(TraceContext.fromTraceparentHeader(), - traceParentProperty, clazz.getClassLoader()); - } - } catch (JMSException e) { - logger.warn("Failed to retrieve trace context property from JMS message", e); - } - - if (transaction == null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()); - } - - transaction.withType(MESSAGING_TYPE).withName(RECEIVE_NAME_PREFIX); - if (helper != null) { - if (destinationName != null) { - helper.addDestinationDetails(message, destination, destinationName, transaction.appendToName(" from ")); - } - helper.addMessageDetails(message, transaction); - helper.setMessageAge(message, transaction); + if (destinationName != null) { + helper.addDestinationDetails(message, destination, destinationName, transaction.appendToName(" from ")); } + helper.addMessageDetails(message, transaction); + helper.setMessageAge(message, transaction); transaction.activate(); return transaction; diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java new file mode 100644 index 0000000000..9a3954d1e9 --- /dev/null +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java @@ -0,0 +1,92 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.jms; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import co.elastic.apm.agent.impl.transaction.TraceContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.jms.JMSException; +import javax.jms.Message; + +import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.JMS_TRACE_PARENT_PROPERTY; + +public class JmsMessagePropertyAccessor implements TextHeaderGetter, TextHeaderSetter { + + private static final Logger logger = LoggerFactory.getLogger(JmsMessagePropertyAccessor.class); + + private static final JmsMessagePropertyAccessor INSTANCE = new JmsMessagePropertyAccessor(); + + public static JmsMessagePropertyAccessor instance() { + return INSTANCE; + } + + private JmsMessagePropertyAccessor() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Message message) { + headerName = jmsifyHeaderName(headerName); + String value = null; + try { + value = message.getStringProperty(headerName); + } catch (JMSException e) { + logger.error("Failed to extract JMS message property", e); + } + return value; + } + + @Nonnull + private String jmsifyHeaderName(String headerName) { + if (headerName.equals(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME)) { + // replacing with the JMS equivalent + headerName = JMS_TRACE_PARENT_PROPERTY; + } + return headerName; + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Message message) { + // not supported for JMS + return null; + } + + @Override + public void setHeader(String headerName, String headerValue, Message message) { + headerName = jmsifyHeaderName(headerName); + try { + message.setStringProperty(headerName, headerValue); + } catch (JMSException e) { + logger.warn("Failed to set JMS message property. Distributed tracing cannot work without that."); + logger.debug("Detailed error: ", e); + } + } +} diff --git a/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/JmsInstrumentationIT.java b/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/JmsInstrumentationIT.java index 7d6882ebbe..69c9f7f3c2 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/JmsInstrumentationIT.java +++ b/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/JmsInstrumentationIT.java @@ -33,7 +33,6 @@ import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.impl.transaction.Id; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import org.junit.After; @@ -131,9 +130,9 @@ public void startTransaction() throws Exception { private void startAndActivateTransaction(@Nullable Sampler sampler) { Transaction transaction; if (sampler == null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + transaction = tracer.startRootTransaction(null).activate(); } else { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, sampler, -1, null).activate(); + transaction = tracer.startRootTransaction(sampler, -1, null).activate(); } transaction.withName("JMS-Test Transaction"); transaction.withType("request"); @@ -178,7 +177,7 @@ private void doTestSendReceiveOnTracedThread(Callable receiveMethod, Qu Transaction transaction = null; Message message = null; try { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null) + transaction = tracer.startRootTransaction(null) .withName("JMS-Test Receiver Transaction") .activate(); message = receiveMethod.call(); diff --git a/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/spring/SpringJmsTest.java b/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/spring/SpringJmsTest.java index 309dae26cd..4c511caef0 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/spring/SpringJmsTest.java +++ b/apm-agent-plugins/apm-jms-plugin/src/test/java/co/elastic/apm/agent/jms/spring/SpringJmsTest.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.transaction.Id; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQMapMessage; @@ -79,7 +78,7 @@ public void testSendListenSpringQueue() throws JMSException, InterruptedExceptio reporter.reset(); try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("JMS-Spring-Test Transaction"); transaction.withType("request"); transaction.withResult("success"); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyClientIT.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyClientIT.java index a299c7fd1c..0a4ec49207 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyClientIT.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyClientIT.java @@ -31,7 +31,6 @@ import co.elastic.apm.agent.impl.context.Message; import co.elastic.apm.agent.impl.context.SpanContext; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import org.apache.kafka.clients.consumer.ConsumerConfig; @@ -137,7 +136,7 @@ public static void tearDown() { @Before public void startTransaction() { reporter.reset(); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("Kafka-Test Transaction"); transaction.withType("request"); transaction.withResult("success"); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java index 210d0210ee..4d51810e41 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java @@ -31,7 +31,6 @@ import net.bytebuddy.matcher.ElementMatcher; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import javax.annotation.Nullable; @@ -43,7 +42,7 @@ public abstract class BaseKafkaHeadersInstrumentation extends BaseKafkaInstrumen @Nullable @VisibleForAdvice // Referencing Kafka classes is legal due to type erasure. The field must be public in order for it to be accessible from injected code - public static HelperClassManager> kafkaInstrHeadersHelperManager; + public static HelperClassManager> kafkaInstrHeadersHelperManager; private synchronized static void init(ElasticApmTracer tracer) { if (kafkaInstrHeadersHelperManager == null) { @@ -52,7 +51,9 @@ private synchronized static void init(ElasticApmTracer tracer) { "co.elastic.apm.agent.kafka.helper.ConsumerRecordsIteratorWrapper", "co.elastic.apm.agent.kafka.helper.ConsumerRecordsIterableWrapper", "co.elastic.apm.agent.kafka.helper.ConsumerRecordsListWrapper", - "co.elastic.apm.agent.kafka.helper.ElasticHeaderImpl"); + "co.elastic.apm.agent.kafka.helper.ElasticHeaderImpl", + "co.elastic.apm.agent.kafka.helper.KafkaRecordHeaderAccessor", + "co.elastic.apm.agent.kafka.helper.KafkaRecordHeaderAccessor$HeaderValuesIterator"); } } diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java index 86a31f77f6..6a6e4252ba 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java @@ -33,7 +33,6 @@ import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import javax.annotation.Nullable; import java.util.Iterator; @@ -74,7 +73,7 @@ public static void wrapIterator(@Nullable @Advice.Return(readOnly = false) Itera } //noinspection ConstantConditions,rawtypes - KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = + KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = kafkaInstrHeadersHelperManager.getForClassLoaderOfClass(KafkaProducer.class); if (iterator != null && kafkaInstrumentationHelper != null) { iterator = kafkaInstrumentationHelper.wrapConsumerRecordIterator(iterator); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java index 0c6eb484bc..d0e71ddc76 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java @@ -34,7 +34,6 @@ import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; -import org.apache.kafka.common.header.Header; import javax.annotation.Nullable; import java.util.List; @@ -75,7 +74,7 @@ public static void wrapRecordList(@Nullable @Advice.Return(readOnly = false) Lis } //noinspection ConstantConditions,rawtypes - KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = + KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = kafkaInstrHeadersHelperManager.getForClassLoaderOfClass(KafkaProducer.class); if (list != null && kafkaInstrumentationHelper != null) { list = kafkaInstrumentationHelper.wrapConsumerRecordList(list); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java index 496cba94e8..1b36a0a56a 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java @@ -33,7 +33,6 @@ import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import javax.annotation.Nullable; @@ -73,7 +72,7 @@ public static void wrapIterable(@Nullable @Advice.Return(readOnly = false) Itera } //noinspection ConstantConditions,rawtypes - KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = + KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = kafkaInstrHeadersHelperManager.getForClassLoaderOfClass(KafkaProducer.class); if (iterable != null && kafkaInstrumentationHelper != null) { iterable = kafkaInstrumentationHelper.wrapConsumerRecordIterable(iterable); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaConsumerRecordsInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaConsumerRecordsInstrumentation.java index d0fe8ec64d..e8457bcce3 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaConsumerRecordsInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaConsumerRecordsInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -25,27 +25,10 @@ package co.elastic.apm.agent.kafka; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHeadersHelper; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.TopicPartition; -import javax.annotation.Nullable; - -import java.util.Iterator; -import java.util.List; - -import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; public abstract class KafkaConsumerRecordsInstrumentation extends BaseKafkaHeadersInstrumentation { diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java index 9951b91b81..1b51d3ff67 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java @@ -27,7 +27,6 @@ import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHeadersHelper; import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHelper; import net.bytebuddy.asm.Advice; @@ -39,7 +38,6 @@ import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import org.apache.kafka.common.record.RecordBatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -107,11 +105,10 @@ public static Span beforeSend(@Advice.FieldValue("apiVersions") final ApiVersion if (apiVersions.maxUsableProduceMagic() >= RecordBatch.MAGIC_VALUE_V2 && headersSupported) { try { //noinspection ConstantConditions - KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = + KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = kafkaInstrHeadersHelperManager.getForClassLoaderOfClass(KafkaProducer.class); if (kafkaInstrumentationHelper != null) { - Header elasticHeader = kafkaInstrumentationHelper.getOutgoingTraceparentHeader(span); - record.headers().add(elasticHeader); + kafkaInstrumentationHelper.setOutgoingTraceContextHeaders(span, record); } } catch (final IllegalStateException e) { // headers are in a read-only state @@ -140,7 +137,12 @@ public static boolean afterSend(@Advice.Enter(readOnly = false) @Nullable Span s //noinspection unchecked record = new ProducerRecord(record.topic(), record.partition(), record.timestamp(), record.key(), record.value(), record.headers()); - record.headers().remove(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); + //noinspection ConstantConditions + KafkaInstrumentationHeadersHelper kafkaInstrumentationHelper = + kafkaInstrHeadersHelperManager.getForClassLoaderOfClass(KafkaProducer.class); + if (kafkaInstrumentationHelper != null) { + kafkaInstrumentationHelper.removeTraceContextHeader(record); + } span.deactivate(); span = null; headersSupported = false; diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ConsumerRecordsIteratorWrapper.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ConsumerRecordsIteratorWrapper.java index bb97385e3c..1439989bf7 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ConsumerRecordsIteratorWrapper.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ConsumerRecordsIteratorWrapper.java @@ -80,17 +80,7 @@ public ConsumerRecord next() { try { String topic = record.topic(); if (!WildcardMatcher.isAnyMatch(messagingConfiguration.getIgnoreMessageQueues(), topic)) { - Header traceParentHeader = record.headers().lastHeader(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); - Transaction transaction; - if (traceParentHeader != null) { - transaction = tracer.startTransaction( - TraceContext.fromTraceparentBinaryHeader(), - traceParentHeader.value(), - ConsumerRecordsIteratorWrapper.class.getClassLoader() - ); - } else { - transaction = tracer.startRootTransaction(ConsumerRecordsIteratorWrapper.class.getClassLoader()); - } + Transaction transaction = tracer.startChildTransaction(record, KafkaRecordHeaderAccessor.instance(), ConsumerRecordsIteratorWrapper.class.getClassLoader()); transaction.withType("messaging").withName("Kafka record from " + topic).activate(); Message message = transaction.getContext().getMessage(); message.withQueue(topic); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ElasticHeaderImpl.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ElasticHeaderImpl.java index 90221c0a69..af2b0e6aa8 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ElasticHeaderImpl.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/ElasticHeaderImpl.java @@ -1,6 +1,29 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ package co.elastic.apm.agent.kafka.helper; -import co.elastic.apm.agent.impl.transaction.TraceContext; import org.apache.kafka.common.header.Header; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,18 +36,19 @@ * it. If that's not the case, distributed tracing through Kafka may be impaired, therefore a warning is logged and * the returned value is null. */ -public class ElasticHeaderImpl implements Header { +class ElasticHeaderImpl implements Header { public static final Logger logger = LoggerFactory.getLogger(ElasticHeaderImpl.class); private final String key; - private final byte[] value; + @Nullable + byte[] value; private long settingThreadId; - public ElasticHeaderImpl(String key) { + public ElasticHeaderImpl(String key, int headerLength) { this.key = key; - value = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH]; + value = new byte[headerLength]; } @Override @@ -37,6 +61,7 @@ public String key() { * * @return the byte array representing the value */ + @Nullable public byte[] valueForSetting() { settingThreadId = Thread.currentThread().getId(); return value; @@ -50,9 +75,12 @@ public byte[] valueForSetting() { @Override @Nullable public byte[] value() { - if (Thread.currentThread().getId() != settingThreadId) { - logger.warn("The assumption of same thread setting and serializing the header is invalid. Distributed tracing will not work"); - return null; + if (Thread.currentThread().getId() != settingThreadId && value != null) { + // Our assumption that the same thread setting the value is the one serializing the header is invalid. + // We log this once and set the value of this header to null. Distributed tracing will still work but will + // allocate a byte array for every record + logger.warn("The assumption of same thread setting and serializing the header is invalid."); + value = null; } return value; } diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelper.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelper.java index 2f7d605acd..db8330befd 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelper.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelper.java @@ -27,12 +27,11 @@ import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.transaction.Span; -import javax.annotation.Nullable; import java.util.Iterator; import java.util.List; @VisibleForAdvice -public interface KafkaInstrumentationHeadersHelper { +public interface KafkaInstrumentationHeadersHelper { Iterator wrapConsumerRecordIterator(Iterator consumerRecordIterator); @@ -40,5 +39,7 @@ public interface KafkaInstrumentationHeadersHelper { List wrapConsumerRecordList(List consumerRecordList); - H getOutgoingTraceparentHeader(Span span); + void setOutgoingTraceContextHeaders(Span span, PR producerRecord); + + void removeTraceContextHeader(PR producerRecord); } diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelperImpl.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelperImpl.java index 5167bbf8e1..d2c4c5383b 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelperImpl.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaInstrumentationHeadersHelperImpl.java @@ -29,7 +29,6 @@ import co.elastic.apm.agent.impl.transaction.TraceContext; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,12 +36,10 @@ import java.util.List; @SuppressWarnings("rawtypes") -public class KafkaInstrumentationHeadersHelperImpl implements KafkaInstrumentationHeadersHelper { +public class KafkaInstrumentationHeadersHelperImpl implements KafkaInstrumentationHeadersHelper { public static final Logger logger = LoggerFactory.getLogger(KafkaInstrumentationHeadersHelperImpl.class); - public static final ThreadLocal traceParentHeader = new ThreadLocal<>(); - private final ElasticApmTracer tracer; public KafkaInstrumentationHeadersHelperImpl(ElasticApmTracer tracer) { @@ -80,13 +77,12 @@ public List wrapConsumerRecordList(List consumer } @Override - public Header getOutgoingTraceparentHeader(Span span) { - ElasticHeaderImpl header = traceParentHeader.get(); - if (header == null) { - header = new ElasticHeaderImpl(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); - traceParentHeader.set(header); - } - span.getTraceContext().fillOutgoingTraceParentBinaryHeader(header.valueForSetting()); - return header; + public void setOutgoingTraceContextHeaders(Span span, ProducerRecord producerRecord) { + span.getTraceContext().setOutgoingTraceContextHeaders(producerRecord, KafkaRecordHeaderAccessor.instance()); + } + + @Override + public void removeTraceContextHeader(ProducerRecord producerRecord) { + TraceContext.removeTraceContextHeaders(producerRecord, KafkaRecordHeaderAccessor.instance()); } } diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java new file mode 100644 index 0000000000..a47d565146 --- /dev/null +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java @@ -0,0 +1,137 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.kafka.helper; + +import co.elastic.apm.agent.impl.transaction.BinaryHeaderGetter; +import co.elastic.apm.agent.impl.transaction.BinaryHeaderSetter; +import co.elastic.apm.agent.impl.transaction.HeaderRemover; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.Header; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +@SuppressWarnings("rawtypes") +class KafkaRecordHeaderAccessor implements BinaryHeaderGetter, BinaryHeaderSetter, + HeaderRemover, Iterable { + + public static final Logger logger = LoggerFactory.getLogger(KafkaRecordHeaderAccessor.class); + + private static final KafkaRecordHeaderAccessor INSTANCE = new KafkaRecordHeaderAccessor(); + + private static final HeaderValuesIterator headerIteratorWrapper = new HeaderValuesIterator(); + + private static final ThreadLocal> threadLocalHeaderMap = new ThreadLocal<>(); + + public static KafkaRecordHeaderAccessor instance() { + return INSTANCE; + } + + @Nullable + @Override + public byte[] getFirstHeader(String headerName, ConsumerRecord record) { + Header traceParentHeader = record.headers().lastHeader(headerName); + if (traceParentHeader != null) { + return traceParentHeader.value(); + } + return null; + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, ConsumerRecord record) { + headerIteratorWrapper.headerIterator = record.headers().headers(headerName).iterator(); + return this; + } + + @Override + public Iterator iterator() { + return headerIteratorWrapper; + } + + @Override + @Nullable + public byte[] getFixedLengthByteArray(String headerName, int length) { + Map headerMap = threadLocalHeaderMap.get(); + if (headerMap == null) { + headerMap = new HashMap<>(); + threadLocalHeaderMap.set(headerMap); + } + ElasticHeaderImpl header = headerMap.get(headerName); + if (header == null) { + header = new ElasticHeaderImpl(headerName, length); + headerMap.put(headerName, header); + } + return header.valueForSetting(); + } + + @Override + public void setHeader(String headerName, byte[] headerValue, ProducerRecord record) { + ElasticHeaderImpl header = null; + Map headerMap = threadLocalHeaderMap.get(); + if (headerMap != null) { + header = headerMap.get(headerName); + } + // Not accessing the value through the method, as it checks the thread + if (header == null || header.value == null) { + logger.warn("No header cached for {}, allocating byte array for each record", headerName); + record.headers().add(headerName, headerValue); + } else { + record.headers().add(header); + } + } + + @Override + public void remove(String headerName, ProducerRecord carrier) { + carrier.headers().remove(headerName); + } + + private static final class HeaderValuesIterator implements Iterator { + @Nullable + private Iterator
headerIterator; + + @Override + public boolean hasNext() { + if (headerIterator != null) { + return headerIterator.hasNext(); + } + return false; + } + + @Override + public byte[] next() { + if (headerIterator != null) { + return headerIterator.next().value(); + } + throw new NoSuchElementException("Kafka record header iterator is not initialized"); + } + } +} diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaIT.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaIT.java index bade2f106e..a0f294d485 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaIT.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaIT.java @@ -11,9 +11,9 @@ * 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 @@ -150,9 +150,9 @@ public void startTransaction() { private void startAndActivateTransaction(@Nullable Sampler sampler) { Transaction transaction; if (sampler == null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + transaction = tracer.startRootTransaction(null).activate(); } else { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, sampler, -1, null).activate(); + transaction = tracer.startRootTransaction(sampler, -1, null).activate(); } transaction.withName("Kafka-Test Transaction"); transaction.withType("request"); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyBrokerIT.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyBrokerIT.java index 68b7c50b0e..2cb93024bc 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyBrokerIT.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/test/java/co/elastic/apm/agent/kafka/KafkaLegacyBrokerIT.java @@ -140,7 +140,7 @@ public static void tearDown() { @Before public void startTransaction() { reporter.reset(); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("Kafka-Test Transaction"); transaction.withType("request"); transaction.withResult("success"); diff --git a/apm-agent-plugins/apm-mongoclient-plugin/src/test/java/co/elastic/apm/agent/mongoclient/AbstractMongoClientInstrumentationTest.java b/apm-agent-plugins/apm-mongoclient-plugin/src/test/java/co/elastic/apm/agent/mongoclient/AbstractMongoClientInstrumentationTest.java index b5d9215400..08352e9992 100644 --- a/apm-agent-plugins/apm-mongoclient-plugin/src/test/java/co/elastic/apm/agent/mongoclient/AbstractMongoClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-mongoclient-plugin/src/test/java/co/elastic/apm/agent/mongoclient/AbstractMongoClientInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.impl.context.Destination; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import org.bson.Document; import org.junit.After; @@ -64,7 +63,7 @@ public static void stopContainer() { @Before public void startTransaction() throws Exception { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate(); + Transaction transaction = tracer.startRootTransaction(null).activate(); transaction.withName("Mongo Transaction"); transaction.withType("request"); transaction.withResultIfUnset("success"); diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java new file mode 100644 index 0000000000..62fdc9a968 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java @@ -0,0 +1,62 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import okhttp3.Request; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; + +public abstract class AbstractOkHttp3ClientInstrumentation extends ElasticApmInstrumentation { + + // We can refer OkHttp types thanks to type erasure + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerGetterHelperManager; + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerSetterHelperManager; + + public AbstractOkHttp3ClientInstrumentation(ElasticApmTracer tracer) { + headerGetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderAccessor" + ); + headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderAccessor" + ); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "okhttp"); + } +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java new file mode 100644 index 0000000000..fc5a1df096 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java @@ -0,0 +1,62 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import com.squareup.okhttp.Request; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; + +public abstract class AbstractOkHttpClientInstrumentation extends ElasticApmInstrumentation { + + // We can refer OkHttp types thanks to type erasure + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerGetterHelperManager; + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerSetterHelperManager; + + public AbstractOkHttpClientInstrumentation(ElasticApmTracer tracer) { + headerGetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderAccessor" + ); + headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderAccessor" + ); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "okhttp"); + } +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java index 263478dcaf..fca242fdea 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java @@ -24,13 +24,12 @@ */ package co.elastic.apm.agent.okhttp; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -40,21 +39,20 @@ import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; +import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class OkHttp3ClientAsyncInstrumentation extends ElasticApmInstrumentation { +public class OkHttp3ClientAsyncInstrumentation extends AbstractOkHttp3ClientInstrumentation { @VisibleForAdvice public static final Logger logger = LoggerFactory.getLogger(OkHttp3ClientAsyncInstrumentation.class); @@ -69,6 +67,7 @@ public Class getAdviceClass() { public static HelperClassManager> callbackWrapperCreator; public OkHttp3ClientAsyncInstrumentation(ElasticApmTracer tracer) { + super(tracer); callbackWrapperCreator = HelperClassManager.ForAnyClassLoader.of(tracer, OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator", OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator$CallbackWrapper"); @@ -99,7 +98,14 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, OkHttpClientHelper.computeHostName(url.host()), url.port()); if (span != null) { span.activate(); - originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()).build(); + if (headerSetterHelperManager != null) { + TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class); + if (headerSetter != null) { + Request.Builder builder = originalRequest.newBuilder(); + span.getTraceContext().setOutgoingTraceContextHeaders(builder, headerSetter); + originalRequest = builder.build(); + } + } callback = wrapperCreator.wrap(callback, span); } } @@ -162,10 +168,4 @@ public ElementMatcher getTypeMatcher() { public ElementMatcher getMethodMatcher() { return named("enqueue").and(takesArguments(1)).and(takesArgument(0, named("okhttp3.Callback"))).and(returns(void.class)); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "okhttp"); - } - } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java index ad60894f7a..96f20f240f 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java @@ -24,11 +24,11 @@ */ package co.elastic.apm.agent.okhttp; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; @@ -36,15 +36,18 @@ import net.bytebuddy.implementation.bytecode.assign.Assigner; import net.bytebuddy.matcher.ElementMatcher; import okhttp3.HttpUrl; +import okhttp3.Request; import javax.annotation.Nullable; -import java.util.Arrays; -import java.util.Collection; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; -public class OkHttp3ClientInstrumentation extends ElasticApmInstrumentation { +public class OkHttp3ClientInstrumentation extends AbstractOkHttp3ClientInstrumentation { + + public OkHttp3ClientInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } @Override public Class getAdviceClass() { @@ -75,7 +78,14 @@ private static void onBeforeExecute( @Advice.FieldValue(value = "originalRequest OkHttpClientHelper.computeHostName(url.host()), url.port()); if (span != null) { span.activate(); - originalRequest = ((okhttp3.Request) originalRequest).newBuilder().addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()).build(); + if (headerSetterHelperManager != null) { + TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class); + if (headerSetter != null) { + Request.Builder builder = ((okhttp3.Request) originalRequest).newBuilder(); + span.getTraceContext().setOutgoingTraceContextHeaders(builder, headerSetter); + originalRequest = builder.build(); + } + } } } } @@ -107,10 +117,4 @@ public ElementMatcher getTypeMatcher() { public ElementMatcher getMethodMatcher() { return named("execute").and(returns(named("okhttp3.Response"))); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "okhttp"); - } - } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java new file mode 100644 index 0000000000..be24fb7dd5 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java @@ -0,0 +1,51 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import okhttp3.Request; + +import javax.annotation.Nullable; + +public class OkHttp3RequestHeaderAccessor implements TextHeaderSetter, TextHeaderGetter { + + @Nullable + @Override + public String getFirstHeader(String headerName, Request request) { + return request.header(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Request request) { + return request.headers(headerName); + } + + @Override + public void setHeader(String headerName, String headerValue, Request.Builder requestBuilder) { + requestBuilder.addHeader(headerName, headerValue); + } +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java index a4e889e852..f08b6052ab 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java @@ -24,13 +24,12 @@ */ package co.elastic.apm.agent.okhttp; -import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import com.squareup.okhttp.Call; import com.squareup.okhttp.Callback; @@ -47,13 +46,11 @@ import javax.annotation.Nullable; import java.io.IOException; import java.net.URL; -import java.util.Arrays; -import java.util.Collection; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; -public class OkHttpClientAsyncInstrumentation extends ElasticApmInstrumentation { +public class OkHttpClientAsyncInstrumentation extends AbstractOkHttpClientInstrumentation { @VisibleForAdvice public static final Logger logger = LoggerFactory.getLogger(OkHttpClientAsyncInstrumentation.class); @@ -68,6 +65,7 @@ public Class getAdviceClass() { public static HelperClassManager> callbackWrapperCreator; public OkHttpClientAsyncInstrumentation(ElasticApmTracer tracer) { + super(tracer); callbackWrapperCreator = HelperClassManager.ForAnyClassLoader.of(tracer, OkHttpClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator", OkHttpClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator$CallbackWrapper"); @@ -98,7 +96,14 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, OkHttpClientHelper.computeHostName(url.getHost()), url.getPort()); if (span != null) { span.activate(); - originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()).build(); + if (headerSetterHelperManager != null) { + TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class); + if (headerSetter != null) { + Request.Builder builder = originalRequest.newBuilder(); + span.getTraceContext().setOutgoingTraceContextHeaders(builder, headerSetter); + originalRequest = builder.build(); + } + } callback = wrapperCreator.wrap(callback, span); } } @@ -161,10 +166,4 @@ public ElementMatcher getTypeMatcher() { public ElementMatcher getMethodMatcher() { return named("enqueue").and(returns(void.class)); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "okhttp"); - } - } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java index d9eeda888e..27b2706302 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java @@ -27,10 +27,13 @@ import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import com.squareup.okhttp.HttpUrl; +import com.squareup.okhttp.Request; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; @@ -44,7 +47,11 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; -public class OkHttpClientInstrumentation extends ElasticApmInstrumentation { +public class OkHttpClientInstrumentation extends AbstractOkHttpClientInstrumentation { + + public OkHttpClientInstrumentation(ElasticApmTracer tracer) { + super(tracer); + } @Override public Class getAdviceClass() { @@ -74,8 +81,14 @@ private static void onBeforeExecute(@Advice.FieldValue(value = "originalRequest" OkHttpClientHelper.computeHostName(httpUrl.host()), httpUrl.port()); if (span != null) { span.activate(); - originalRequest = ((com.squareup.okhttp.Request) originalRequest).newBuilder().addHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, - span.getTraceContext().getOutgoingTraceParentTextHeader().toString()).build(); + if (headerSetterHelperManager != null) { + TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class); + if (headerSetter != null) { + Request.Builder builder = ((com.squareup.okhttp.Request) originalRequest).newBuilder(); + span.getTraceContext().setOutgoingTraceContextHeaders(builder, headerSetter); + originalRequest = builder.build(); + } + } } } } @@ -107,11 +120,4 @@ public ElementMatcher getTypeMatcher() { public ElementMatcher getMethodMatcher() { return named("execute").and(returns(named("com.squareup.okhttp.Response"))); } - - @Override - public Collection getInstrumentationGroupNames() { - return Arrays.asList("http-client", "okhttp"); - } - - } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java new file mode 100644 index 0000000000..d3ee86e3c7 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java @@ -0,0 +1,51 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import com.squareup.okhttp.Request; + +import javax.annotation.Nullable; + +public class OkHttpRequestHeaderAccessor implements TextHeaderSetter, TextHeaderGetter { + + @Nullable + @Override + public String getFirstHeader(String headerName, Request request) { + return request.header(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Request request) { + return request.headers(headerName); + } + + @Override + public void setHeader(String headerName, String headerValue, Request.Builder requestBuilder) { + requestBuilder.addHeader(headerName, headerValue); + } +} diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java index 7f2c2fd45a..bcf9ad5395 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java @@ -116,22 +116,9 @@ private static AbstractSpan createTransaction(Map tags, Strin } else { sampler = tracer.getSampler(); } - return tracer.startTransaction(TraceContext.fromTraceparentHeader(), getTraceContextHeader(baggage), sampler, microseconds, classLoader); + return tracer.startChildTransaction(baggage, OpenTracingTextMapBridge.instance(), sampler, microseconds, classLoader); } } - - @Nullable - @VisibleForAdvice - static String getTraceContextHeader(@Nullable Iterable> baggage) { - if (baggage != null) { - for (Map.Entry entry : baggage) { - if (entry.getKey().equalsIgnoreCase(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME)) { - return entry.getValue(); - } - } - } - return null; - } } } diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java index f906d116f7..0015cd8d51 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java @@ -103,16 +103,12 @@ public static void toSpanId(@Advice.FieldValue(value = "textMap", typing = Assig @VisibleForAdvice @Nullable public static TraceContext parseTextMap(Iterable> textMap) { - TraceContext childTraceContext = null; if (tracer != null) { - for (Map.Entry next : textMap) { - if (TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME.equals(next.getKey())) { - childTraceContext = TraceContext.with64BitId(tracer); - TraceContext.fromTraceparentHeader().asChildOf(childTraceContext, next.getValue()); - break; - } + TraceContext childTraceContext = TraceContext.with64BitId(tracer); + if (TraceContext.getFromTraceContextTextHeaders().asChildOf(childTraceContext, textMap, OpenTracingTextMapBridge.instance())) { + return childTraceContext; } } - return childTraceContext; + return null; } } diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java new file mode 100644 index 0000000000..4c5902a785 --- /dev/null +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java @@ -0,0 +1,69 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.opentracing.impl; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; + +import javax.annotation.Nullable; +import java.util.Map; + +@VisibleForAdvice +public class OpenTracingTextMapBridge implements TextHeaderGetter>>, TextHeaderSetter> { + + private static final OpenTracingTextMapBridge INSTANCE = new OpenTracingTextMapBridge(); + + @VisibleForAdvice + public static OpenTracingTextMapBridge instance() { + return INSTANCE; + } + + private OpenTracingTextMapBridge() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Iterable> textMap) { + for (Map.Entry entry : textMap) { + if (entry.getKey().equalsIgnoreCase(headerName)) { + return entry.getValue(); + } + } + return null; + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, Iterable> textMap) { + // Not supported for OpenTracing TextMap + return null; + } + + @Override + public void setHeader(String headerName, String headerValue, Map textMap) { + textMap.put(headerName, headerValue); + } +} diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java index 095c51a9a5..a62065a8a6 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -81,7 +81,9 @@ public static void baggageItems(@Advice.FieldValue(value = "traceContext", typin @VisibleForAdvice public static Iterable> doGetBaggage(TraceContext traceContext) { - return Collections.singletonMap(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, traceContext.getOutgoingTraceParentTextHeader().toString()).entrySet(); + Map baggage = new HashMap(); + traceContext.setOutgoingTraceContextHeaders(baggage, OpenTracingTextMapBridge.instance()); + return baggage.entrySet(); } } diff --git a/apm-agent-plugins/apm-process-plugin/src/test/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentationTest.java b/apm-agent-plugins/apm-process-plugin/src/test/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentationTest.java index 9e3430cf57..8271d33e72 100644 --- a/apm-agent-plugins/apm-process-plugin/src/test/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentationTest.java +++ b/apm-agent-plugins/apm-process-plugin/src/test/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentationTest.java @@ -11,9 +11,9 @@ * 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 @@ -25,7 +25,6 @@ package co.elastic.apm.agent.process; import co.elastic.apm.agent.AbstractInstrumentationTest; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import org.apache.commons.exec.CommandLine; @@ -122,7 +121,7 @@ private static String getJavaBinaryPath() { } private static void startTransaction() { - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, CommonsExecAsyncInstrumentationTest.class.getClassLoader()); + Transaction transaction = tracer.startRootTransaction(CommonsExecAsyncInstrumentationTest.class.getClassLoader()); transaction.withType("request").activate(); } diff --git a/apm-agent-plugins/apm-quartz-job-plugin/src/main/java/co/elastic/apm/agent/quartz/job/JobTransactionNameAdvice.java b/apm-agent-plugins/apm-quartz-job-plugin/src/main/java/co/elastic/apm/agent/quartz/job/JobTransactionNameAdvice.java index a86022a641..44383ab830 100644 --- a/apm-agent-plugins/apm-quartz-job-plugin/src/main/java/co/elastic/apm/agent/quartz/job/JobTransactionNameAdvice.java +++ b/apm-agent-plugins/apm-quartz-job-plugin/src/main/java/co/elastic/apm/agent/quartz/job/JobTransactionNameAdvice.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.bci.ElasticApmInstrumentation; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -48,12 +47,12 @@ private static void setTransactionName(@Advice.Argument(value = 0) @Nullable Job TraceContextHolder active = ElasticApmInstrumentation.tracer.getActive(); if (context == null) { logger.warn("Cannot correctly name transaction for method {} because JobExecutionContext is null", signature); - transaction = ElasticApmInstrumentation.tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()) + transaction = ElasticApmInstrumentation.tracer.startRootTransaction(clazz.getClassLoader()) .withName(signature) .withType(JobTransactionNameInstrumentation.TRANSACTION_TYPE) .activate(); } else if (active == null) { - transaction = ElasticApmInstrumentation.tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()) + transaction = ElasticApmInstrumentation.tracer.startRootTransaction(clazz.getClassLoader()) .withName(context.getJobDetail().getKey().toString()) .withType(JobTransactionNameInstrumentation.TRANSACTION_TYPE) .activate(); diff --git a/apm-agent-plugins/apm-scheduled-annotation-plugin/src/main/java/co/elastic/apm/agent/spring/scheduled/ScheduledTransactionNameInstrumentation.java b/apm-agent-plugins/apm-scheduled-annotation-plugin/src/main/java/co/elastic/apm/agent/spring/scheduled/ScheduledTransactionNameInstrumentation.java index fafd41c3a1..4f3a115cd7 100644 --- a/apm-agent-plugins/apm-scheduled-annotation-plugin/src/main/java/co/elastic/apm/agent/spring/scheduled/ScheduledTransactionNameInstrumentation.java +++ b/apm-agent-plugins/apm-scheduled-annotation-plugin/src/main/java/co/elastic/apm/agent/spring/scheduled/ScheduledTransactionNameInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -29,7 +29,6 @@ import javax.annotation.Nullable; -import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +37,6 @@ import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -69,7 +67,7 @@ private static void setTransactionName(@SimpleMethodSignature String signature, if (tracer != null) { TraceContextHolder active = tracer.getActive(); if (active == null) { - transaction = tracer.startTransaction(TraceContext.asRoot(), null, clazz.getClassLoader()) + transaction = tracer.startRootTransaction(clazz.getClassLoader()) .withName(signature) .withType("scheduled") .activate(); diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java index 722628a088..b7eb930aa8 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java @@ -30,7 +30,6 @@ import co.elastic.apm.agent.impl.Scope; import co.elastic.apm.agent.impl.context.Request; import co.elastic.apm.agent.impl.context.Response; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -107,11 +106,24 @@ public static void onEnterServletService(@Advice.Argument(0) ServletRequest serv } final HttpServletRequest request = (HttpServletRequest) servletRequest; - transaction = servletTransactionHelper.onBefore( - request.getServletContext().getClassLoader(), - request.getServletPath(), request.getPathInfo(), - request.getHeader("User-Agent"), - request.getHeader(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME)); + if (ServletInstrumentation.servletTransactionCreationHelperManager != null) { + ServletInstrumentation.ServletTransactionCreationHelper helper = + ServletInstrumentation.servletTransactionCreationHelperManager.getForClassLoaderOfClass(HttpServletRequest.class); + if (helper != null) { + System.out.println("Helper is not null"); + transaction = helper.createAndActivateTransaction(request); + if (transaction != null) { + System.out.println("transaction.getTraceContext().isSampled() = " + transaction.getTraceContext().isSampled()); + System.out.println("transaction.isNoop() = " + transaction.isNoop()); + } else { + System.out.println("Transaction is null"); + } + } else { + System.out.println("Helper is null"); + } + new Exception().printStackTrace(); + } + if (transaction == null) { // if the request is excluded, avoid matching all exclude patterns again on each filter invocation excluded.set(Boolean.TRUE); diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java index ca91f290ac..4938c11036 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -25,12 +25,17 @@ package co.elastic.apm.agent.servlet; import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; import java.util.Collection; import java.util.Collections; @@ -55,8 +60,18 @@ public class ServletInstrumentation extends ElasticApmInstrumentation { static final String SERVLET_API = "servlet-api"; + @Nullable + @VisibleForAdvice + // referring to HttpServletRequest is legal because of type erasure + public static HelperClassManager> servletTransactionCreationHelperManager; + public ServletInstrumentation(ElasticApmTracer tracer) { ServletApiAdvice.init(tracer); + servletTransactionCreationHelperManager = HelperClassManager.ForSingleClassLoader.of(tracer, + "co.elastic.apm.agent.servlet.helper.ServletTransactionCreationHelperImpl", + "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter", + "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter$HeaderValuesIterator" + ); } @Override @@ -87,4 +102,9 @@ public Collection getInstrumentationGroupNames() { return Collections.singleton(SERVLET_API); } + @VisibleForAdvice + public interface ServletTransactionCreationHelper { + @Nullable + Transaction createAndActivateTransaction(R request); + } } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java index c3335e8aaf..b464d1cce5 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java @@ -11,9 +11,9 @@ * 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 @@ -32,7 +32,6 @@ import co.elastic.apm.agent.impl.context.Response; import co.elastic.apm.agent.impl.context.TransactionContext; import co.elastic.apm.agent.impl.context.Url; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.matcher.WildcardMatcher; import co.elastic.apm.agent.impl.context.web.ResultUtil; @@ -45,7 +44,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -109,44 +107,6 @@ public static void determineServiceName(@Nullable String servletContextName, Cla } } - /* - * As much of the request information as possible should be set before the request processing starts. - * - * That way, when recording an error, - * we can copy the transaction context to the error context. - * - * This has the advantage that we don't have to create the context for the error again. - * As creating the context is framework specific, - * this also means less effort when adding support for new frameworks, - * because the creating the context is handled in one central place. - * - * Furthermore, it is not trivial to create an error context at an arbitrary location - * (when the user calls ElasticApm.captureException()), - * as we don't necessarily have access to the framework's request and response objects. - * - * Additionally, we only have access to the classes of the instrumented classes inside advice methods. - * - * Currently, there is no configuration option to disable tracing but to still enable error tracking. - * But even when introducing that, the approach of copying the transaction context can still work. - * We will then capture the transaction but not report it. - * As the capturing of the transaction is garbage free, this should not add a significant overhead. - * Also, this setting would be rather niche, as we are a APM solution after all. - */ - @Nullable - @VisibleForAdvice - public Transaction onBefore(ClassLoader classLoader, String servletPath, @Nullable String pathInfo, - @Nullable String userAgentHeader, - @Nullable String traceContextHeader) { - if (coreConfiguration.isActive() && - // only create a transaction if there is not already one - tracer.currentTransaction() == null && - !isExcluded(servletPath, pathInfo, userAgentHeader)) { - return tracer.startTransaction(TraceContext.fromTraceparentHeader(), traceContextHeader, classLoader).activate(); - } else { - return null; - } - } - @VisibleForAdvice public void fillRequestContext(Transaction transaction, String protocol, String method, boolean secure, String scheme, String serverName, int serverPort, String requestURI, String queryString, @@ -276,25 +236,6 @@ && hasBody(contentTypeHeader, method) && WildcardMatcher.isAnyMatch(webConfiguration.getCaptureContentTypes(), contentTypeHeader); } - private boolean isExcluded(String servletPath, @Nullable String pathInfo, @Nullable String userAgentHeader) { - final WildcardMatcher excludeUrlMatcher = WildcardMatcher.anyMatch(webConfiguration.getIgnoreUrls(), servletPath, pathInfo); - if (excludeUrlMatcher != null && logger.isDebugEnabled()) { - logger.debug("Not tracing this request as the URL {}{} is ignored by the matcher {}", - servletPath, Objects.toString(pathInfo, ""), excludeUrlMatcher); - } - final WildcardMatcher excludeAgentMatcher = userAgentHeader != null ? WildcardMatcher.anyMatch(webConfiguration.getIgnoreUserAgents(), userAgentHeader) : null; - if (excludeAgentMatcher != null) { - logger.debug("Not tracing this request as the User-Agent {} is ignored by the matcher {}", - userAgentHeader, excludeAgentMatcher); - } - boolean isExcluded = excludeUrlMatcher != null || excludeAgentMatcher != null; - if (!isExcluded && logger.isTraceEnabled()) { - logger.trace("No matcher found for excluding this request with servlet-path: {}, path-info: {} and User-Agent: {}", - servletPath, pathInfo, userAgentHeader); - } - return isExcluded; - } - private void fillResponse(Response response, boolean committed, int status) { response.withFinished(true); response.withHeadersSent(committed); diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java new file mode 100644 index 0000000000..bb7c3449ed --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java @@ -0,0 +1,93 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.servlet.helper; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; + +class RequestHeaderGetter implements TextHeaderGetter, Iterable { + + // Caching in ThreadLocal in order to reuse the Enumeration-based iterator + private static final ThreadLocal threadLocalRequestHeaderGetter = new ThreadLocal<>(); + + static RequestHeaderGetter getInstance() { + RequestHeaderGetter requestHeaderGetter = threadLocalRequestHeaderGetter.get(); + if (requestHeaderGetter == null) { + requestHeaderGetter = new RequestHeaderGetter(); + threadLocalRequestHeaderGetter.set(requestHeaderGetter); + } + return requestHeaderGetter; + } + + private final HeaderValuesIterator headerValuesIterator = new HeaderValuesIterator(); + + private RequestHeaderGetter() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, HttpServletRequest request) { + return request.getHeader(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, HttpServletRequest request) { + headerValuesIterator.headerValues = request.getHeaders(headerName); + return this; + } + + @Override + public Iterator iterator() { + return headerValuesIterator; + } + + private static final class HeaderValuesIterator implements Iterator { + + @Nullable + private Enumeration headerValues; + + @Override + public boolean hasNext() { + if (headerValues != null) { + return headerValues.hasMoreElements(); + } + return false; + } + + @Override + public String next() { + if (headerValues != null) { + return headerValues.nextElement(); + } + throw new NoSuchElementException("Header values Enumeration is not initialized"); + } + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java new file mode 100644 index 0000000000..230139985a --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java @@ -0,0 +1,85 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.servlet.helper; + +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.context.web.WebConfiguration; +import co.elastic.apm.agent.impl.transaction.Transaction; +import co.elastic.apm.agent.matcher.WildcardMatcher; +import co.elastic.apm.agent.servlet.ServletInstrumentation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +public class ServletTransactionCreationHelperImpl implements ServletInstrumentation.ServletTransactionCreationHelper { + + private static final Logger logger = LoggerFactory.getLogger(ServletTransactionCreationHelperImpl.class); + + private final ElasticApmTracer tracer; + private final CoreConfiguration coreConfiguration; + private final WebConfiguration webConfiguration; + + public ServletTransactionCreationHelperImpl(ElasticApmTracer tracer) { + this.tracer = tracer; + coreConfiguration = tracer.getConfig(CoreConfiguration.class); + webConfiguration = tracer.getConfig(WebConfiguration.class); + } + + @Override + @Nullable + public Transaction createAndActivateTransaction(HttpServletRequest request) { + if (coreConfiguration.isActive() && + // only create a transaction if there is not already one + tracer.currentTransaction() == null && + !isExcluded(request.getServletPath(), request.getPathInfo(), request.getHeader("User-Agent"))) { + return tracer.startChildTransaction(request, RequestHeaderGetter.getInstance(), request.getServletContext().getClassLoader()).activate(); + } else { + return null; + } + } + + private boolean isExcluded(String servletPath, @Nullable String pathInfo, @Nullable String userAgentHeader) { + final WildcardMatcher excludeUrlMatcher = WildcardMatcher.anyMatch(webConfiguration.getIgnoreUrls(), servletPath, pathInfo); + if (excludeUrlMatcher != null && logger.isDebugEnabled()) { + logger.debug("Not tracing this request as the URL {}{} is ignored by the matcher {}", + servletPath, Objects.toString(pathInfo, ""), excludeUrlMatcher); + } + final WildcardMatcher excludeAgentMatcher = userAgentHeader != null ? WildcardMatcher.anyMatch(webConfiguration.getIgnoreUserAgents(), userAgentHeader) : null; + if (excludeAgentMatcher != null) { + logger.debug("Not tracing this request as the User-Agent {} is ignored by the matcher {}", + userAgentHeader, excludeAgentMatcher); + } + boolean isExcluded = excludeUrlMatcher != null || excludeAgentMatcher != null; + if (!isExcluded && logger.isTraceEnabled()) { + logger.trace("No matcher found for excluding this request with servlet-path: {}, path-info: {} and User-Agent: {}", + servletPath, pathInfo, userAgentHeader); + } + return isExcluded; + } +} diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/TestRequestBodyCapturing.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/TestRequestBodyCapturing.java index 4fbf7e933e..f89dc54abc 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/TestRequestBodyCapturing.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/TestRequestBodyCapturing.java @@ -11,9 +11,9 @@ * 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 @@ -27,7 +27,6 @@ import co.elastic.apm.agent.AbstractInstrumentationTest; import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.error.ErrorCapture; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.report.serialize.DslJsonSerializer; import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; @@ -250,7 +249,7 @@ void testTrackPostParamsDisabled() throws IOException, ServletException { @Test void testNoExplicitEndOfInput() { - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + final Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()); transaction.getContext().getRequest().withBodyBuffer(); transaction.end(); assertThat(reporter.getFirstTransaction().getContext().getRequest().getBody().toString()).isEqualTo(""); diff --git a/apm-agent-plugins/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/Slf4JMdcActivationListenerTest.java b/apm-agent-plugins/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/Slf4JMdcActivationListenerTest.java index 08bb528624..3311bce750 100644 --- a/apm-agent-plugins/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/Slf4JMdcActivationListenerTest.java +++ b/apm-agent-plugins/apm-slf4j-plugin/src/test/java/co/elastic/apm/agent/slf4j/Slf4JMdcActivationListenerTest.java @@ -11,9 +11,9 @@ * 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 @@ -30,7 +30,6 @@ import co.elastic.apm.agent.impl.Scope; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; import co.elastic.apm.agent.logging.LoggingConfiguration; import org.junit.jupiter.api.BeforeEach; @@ -60,7 +59,7 @@ void setUp() { @Test void testMdcIntegration() { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); try (Scope scope = transaction.activateInScope()) { assertMdcIsSet(transaction); @@ -83,7 +82,7 @@ void testMdcIntegration() { void testDisabledWhenInactive() { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); when(coreConfiguration.isActive()).thenReturn(false); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); try (Scope scope = transaction.activateInScope()) { assertMdcIsEmpty(); @@ -99,7 +98,7 @@ void testDisabledWhenInactive() { @Test void testInactivationWhileMdcIsSet() { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); try (Scope scope = transaction.activateInScope()) { assertMdcIsSet(transaction); @@ -118,7 +117,7 @@ void testInactivationWhileMdcIsSet() { @Test void testMdcIntegrationTransactionScopeInDifferentThread() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); final CompletableFuture result = new CompletableFuture<>(); Thread thread = new Thread(() -> { @@ -139,7 +138,7 @@ void testMdcIntegrationTransactionScopeInDifferentThread() throws Exception { @Test void testNoopWhenClassLoaderCantLoadMdc() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("test"); assertMdcIsEmpty(); final CompletableFuture result = new CompletableFuture<>(); Thread thread = new Thread(() -> { @@ -160,7 +159,7 @@ void testNoopWhenClassLoaderCantLoadMdc() throws Exception { @Test void testWithNullContextClassLoader() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); final CompletableFuture result = new CompletableFuture<>(); Thread thread = new Thread(() -> { @@ -182,7 +181,7 @@ void testWithNullContextClassLoader() throws Exception { @Test void testWithNullApplicationClassLoaderFallbackPlatformCL() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("test"); assertMdcIsEmpty(); final CompletableFuture result = new CompletableFuture<>(); Thread thread = new Thread(() -> { @@ -205,7 +204,7 @@ void testWithNullApplicationClassLoaderFallbackPlatformCL() throws Exception { @Test void testWithNullApplicationClassLoaderFallbackCL() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).withType("request").withName("test"); + Transaction transaction = tracer.startRootTransaction(null).withType("request").withName("test"); assertMdcIsEmpty(); final CompletableFuture result = new CompletableFuture<>(); Thread thread = new Thread(() -> { @@ -227,7 +226,7 @@ void testWithNullApplicationClassLoaderFallbackCL() throws Exception { @Test void testMdcIntegrationContextScopeInDifferentThread() throws Exception { when(loggingConfiguration.isLogCorrelationEnabled()).thenReturn(true); - final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()).withType("request").withName("test"); + final Transaction transaction = tracer.startRootTransaction(getClass().getClassLoader()).withType("request").withName("test"); assertMdcIsEmpty(); try (Scope scope = transaction.activateInScope()) { assertMdcIsSet(transaction); diff --git a/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestRequestHeaderSetter.java b/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestRequestHeaderSetter.java new file mode 100644 index 0000000000..82145ecb39 --- /dev/null +++ b/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestRequestHeaderSetter.java @@ -0,0 +1,35 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.resttemplate; + +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import org.springframework.http.HttpRequest; + +public class SpringRestRequestHeaderSetter implements TextHeaderSetter { + @Override + public void setHeader(String headerName, String headerValue, HttpRequest request) { + request.getHeaders().add(headerName, headerValue); + } +} diff --git a/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentation.java b/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentation.java index cd0863067d..b1a2bb7b48 100644 --- a/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentation.java +++ b/apm-agent-plugins/apm-spring-resttemplate-plugin/src/main/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentation.java @@ -25,14 +25,18 @@ package co.elastic.apm.agent.resttemplate; import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; @@ -53,36 +57,15 @@ public class SpringRestTemplateInstrumentation extends ElasticApmInstrumentation { - @Advice.OnMethodEnter(suppress = Throwable.class) - private static void beforeExecute(@Advice.This ClientHttpRequest request, - @Advice.Local("span") Span span) { - if (tracer == null || tracer.getActive() == null) { - return; - } - final TraceContextHolder parent = tracer.getActive(); - span = HttpClientHelper.startHttpClientSpan(parent, Objects.toString(request.getMethod()), request.getURI(), - request.getURI().getHost()); - if (span != null) { - span.activate(); - request.getHeaders().add(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); - } - } + // We can refer Spring type thanks to type erasure + @VisibleForAdvice + @Nullable + public static HelperClassManager> headerSetterHelperManager; - @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) - private static void afterExecute(@Advice.Return @Nullable ClientHttpResponse clientHttpResponse, - @Advice.Local("span") @Nullable Span span, - @Advice.Thrown @Nullable Throwable t) throws IOException { - if (span != null) { - try { - if (clientHttpResponse != null) { - int statusCode = clientHttpResponse.getRawStatusCode(); - span.getContext().getHttp().withStatusCode(statusCode); - } - span.captureException(t); - } finally { - span.deactivate().end(); - } - } + public SpringRestTemplateInstrumentation(ElasticApmTracer tracer) { + headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.resttemplate.SpringRestRequestHeaderSetter" + ); } @Override @@ -101,8 +84,52 @@ public ElementMatcher getMethodMatcher() { .and(returns(hasSuperType(named("org.springframework.http.client.ClientHttpResponse")))); } + @Override + public Class getAdviceClass() { + return SpringRestTemplateAdvice.class; + } + @Override public Collection getInstrumentationGroupNames() { return Arrays.asList("http-client", "spring-resttemplate"); } + + public static class SpringRestTemplateAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + private static void beforeExecute(@Advice.This ClientHttpRequest request, + @Advice.Local("span") Span span) { + if (tracer == null || tracer.getActive() == null) { + return; + } + final TraceContextHolder parent = tracer.getActive(); + span = HttpClientHelper.startHttpClientSpan(parent, Objects.toString(request.getMethod()), request.getURI(), + request.getURI().getHost()); + if (span != null) { + span.activate(); + if (headerSetterHelperManager != null) { + TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(HttpRequest.class); + if (headerSetter != null) { + span.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); + } + } + } + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + private static void afterExecute(@Advice.Return @Nullable ClientHttpResponse clientHttpResponse, + @Advice.Local("span") @Nullable Span span, + @Advice.Thrown @Nullable Throwable t) throws IOException { + if (span != null) { + try { + if (clientHttpResponse != null) { + int statusCode = clientHttpResponse.getRawStatusCode(); + span.getContext().getHttp().withStatusCode(statusCode); + } + span.captureException(t); + } finally { + span.deactivate().end(); + } + } + } + } } diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java index c13105bcf4..edbd87b682 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/HttpUrlConnectionInstrumentation.java @@ -84,8 +84,8 @@ public static void enter(@Advice.This HttpURLConnection thiz, final URL url = thiz.getURL(); span = HttpClientHelper.startHttpClientSpan(tracer.getActive(), thiz.getRequestMethod(), url.toString(), url.getProtocol(), url.getHost(), url.getPort()); if (span != null) { - if (thiz.getRequestProperty(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME) == null) { - thiz.addRequestProperty(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, span.getTraceContext().getOutgoingTraceParentTextHeader().toString()); + if (!TraceContext.containsTraceContextTextHeaders(thiz, UrlConnectionPropertyAccessor.instance())) { + span.getTraceContext().setOutgoingTraceContextHeaders(thiz, UrlConnectionPropertyAccessor.instance()); } } } diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java new file mode 100644 index 0000000000..74cce038af --- /dev/null +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java @@ -0,0 +1,57 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.urlconnection; + +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; + +import javax.annotation.Nullable; +import java.net.HttpURLConnection; + +public class UrlConnectionPropertyAccessor implements TextHeaderSetter, TextHeaderGetter { + + private static final UrlConnectionPropertyAccessor INSTANCE = new UrlConnectionPropertyAccessor(); + + public static UrlConnectionPropertyAccessor instance() { + return INSTANCE; + } + + @Override + public void setHeader(String headerName, String headerValue, HttpURLConnection urlConnection) { + urlConnection.addRequestProperty(headerName, headerValue); + } + + @Nullable + @Override + public String getFirstHeader(String headerName, HttpURLConnection urlConnection) { + return urlConnection.getRequestProperty(headerName); + } + + @Nullable + @Override + public Iterable getHeaders(String headerName, HttpURLConnection urlConnection) { + return urlConnection.getRequestProperties().get(headerName); + } +} diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java index dc9e651fe4..c7e8502f2b 100644 --- a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java +++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java @@ -25,6 +25,7 @@ package co.elastic.apm.opentracing; import co.elastic.apm.agent.AbstractInstrumentationTest; +import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.transaction.Id; import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.Transaction; @@ -456,7 +457,7 @@ void testInjectExtract() { final HashMap map = new HashMap<>(); apmTracer.inject(otSpan.context(), Format.Builtin.TEXT_MAP, new TextMapAdapter(map)); final TraceContext injectedContext = TraceContext.with64BitId(tracer); - assertThat(injectedContext.asChildOf(map.get(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME))).isTrue(); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(injectedContext, map, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(injectedContext.getTraceId().toString()).isEqualTo(traceId); assertThat(injectedContext.getParentId()).isEqualTo(transaction.getTraceContext().getId()); assertThat(injectedContext.isSampled()).isTrue(); diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/package-info.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/package-info.java new file mode 100644 index 0000000000..b710f24f0c --- /dev/null +++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/package-info.java @@ -0,0 +1,28 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +@NonnullApi +package co.elastic.apm.opentracing; + +import co.elastic.apm.agent.annotation.NonnullApi; From 519a2c62e94281fbe47b57c3f21ee5dc076bdc23 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Thu, 6 Feb 2020 12:06:15 +0200 Subject: [PATCH 02/16] Merging AsyncHttpClient --- ...bstractAsyncHttpClientInstrumentation.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java index c680378016..059bf4c6a7 100644 --- a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java @@ -29,7 +29,6 @@ import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.http.client.HttpClientHelper; -import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; @@ -72,7 +71,7 @@ public abstract class AbstractAsyncHttpClientInstrumentation extends ElasticApmI AsyncHandlerOnThrowableInstrumentation.class, AsyncHandlerOnStatusReceivedInstrumentation.class); - public AbstractAsyncHttpClientInstrumentation(ElasticApmTracer tracer) { + public AbstractAsyncHttpClientInstrumentation() { if (headerSetterManager == null) { synchronized (AbstractAsyncHandlerInstrumentation.class) { if (headerSetterManager == null) { @@ -96,9 +95,6 @@ public ElementMatcher.Junction getClassLoaderMatcher() { } public static class AsyncHttpClientInstrumentation extends AbstractAsyncHttpClientInstrumentation { - public AsyncHttpClientInstrumentation(ElasticApmTracer tracer) { - super(tracer); - } @Advice.OnMethodEnter(suppress = Throwable.class) private static void onBeforeExecute(@Advice.Argument(value = 0) Request request, @@ -156,8 +152,7 @@ public abstract static class AbstractAsyncHandlerInstrumentation extends Abstrac private final ElementMatcher methodMatcher; - protected AbstractAsyncHandlerInstrumentation(ElasticApmTracer tracer, ElementMatcher methodMatcher) { - super(tracer); + protected AbstractAsyncHandlerInstrumentation(ElementMatcher methodMatcher) { this.methodMatcher = methodMatcher; } @@ -178,8 +173,8 @@ public ElementMatcher getMethodMatcher() { public static class AsyncHandlerOnCompletedInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnCompletedInstrumentation(ElasticApmTracer tracer) { - super(tracer, named("onCompleted").and(takesArguments(0))); + public AsyncHandlerOnCompletedInstrumentation() { + super(named("onCompleted").and(takesArguments(0))); } @Advice.OnMethodEnter(suppress = Throwable.class) @@ -193,8 +188,8 @@ private static void onMethodEnter(@Advice.This AsyncHandler asyncHandler) { public static class AsyncHandlerOnThrowableInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnThrowableInstrumentation(ElasticApmTracer tracer) { - super(tracer, named("onThrowable").and(takesArguments(Throwable.class))); + public AsyncHandlerOnThrowableInstrumentation() { + super(named("onThrowable").and(takesArguments(Throwable.class))); } @Advice.OnMethodEnter(suppress = Throwable.class) @@ -208,8 +203,8 @@ private static void onMethodEnter(@Advice.This AsyncHandler asyncHandler, @Ad public static class AsyncHandlerOnStatusReceivedInstrumentation extends AbstractAsyncHandlerInstrumentation { - public AsyncHandlerOnStatusReceivedInstrumentation(ElasticApmTracer tracer) { - super(tracer, named("onStatusReceived").and(takesArgument(0, named("org.asynchttpclient.HttpResponseStatus")))); + public AsyncHandlerOnStatusReceivedInstrumentation() { + super(named("onStatusReceived").and(takesArgument(0, named("org.asynchttpclient.HttpResponseStatus")))); } @Advice.OnMethodEnter(suppress = Throwable.class) From 2bf09df30ffee17f9f0c928d9a389244f26a58d6 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Thu, 6 Feb 2020 13:14:00 +0200 Subject: [PATCH 03/16] Generics enhancements --- .../apm/agent/impl/ElasticApmTracer.java | 12 +++++----- .../StatementNotSupportingUpdateCount.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index 4bced16a61..fd044d64f2 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -200,7 +200,7 @@ public Transaction startRootTransaction(Sampler sampler, long epochMicros, @Null * Used to determine the service name. * @return a transaction which is a child of the provided parent */ - public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHeaderGetter textHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { + public Transaction startChildTransaction(@Nullable C headerCarrier, TextHeaderGetter textHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { return startChildTransaction(headerCarrier, textHeadersGetter, sampler, -1, initiatingClassLoader); } @@ -216,8 +216,8 @@ public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHea * for log correlation. * @return a transaction which is a child of the provided parent */ - public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHeaderGetter textHeadersGetter, Sampler sampler, - long epochMicros, @Nullable ClassLoader initiatingClassLoader) { + public Transaction startChildTransaction(@Nullable C headerCarrier, TextHeaderGetter textHeadersGetter, Sampler sampler, + long epochMicros, @Nullable ClassLoader initiatingClassLoader) { Transaction transaction; if (!coreConfiguration.isActive()) { transaction = noopTransaction(); @@ -238,7 +238,7 @@ public Transaction startChildTransaction(@Nullable Object headerCarrier, TextHea * Used to determine the service name. * @return a transaction which is a child of the provided parent */ - public Transaction startChildTransaction(@Nullable Object headerCarrier, BinaryHeaderGetter binaryHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { + public Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGetter binaryHeadersGetter, @Nullable ClassLoader initiatingClassLoader) { return startChildTransaction(headerCarrier, binaryHeadersGetter, sampler, -1, initiatingClassLoader); } @@ -254,8 +254,8 @@ public Transaction startChildTransaction(@Nullable Object headerCarrier, BinaryH * for log correlation. * @return a transaction which is a child of the provided parent */ - public Transaction startChildTransaction(@Nullable Object headerCarrier, BinaryHeaderGetter binaryHeadersGetter, - Sampler sampler, long epochMicros, @Nullable ClassLoader initiatingClassLoader) { + public Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHeaderGetter binaryHeadersGetter, + Sampler sampler, long epochMicros, @Nullable ClassLoader initiatingClassLoader) { Transaction transaction; if (!coreConfiguration.isActive()) { transaction = noopTransaction(); diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/StatementNotSupportingUpdateCount.java b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/StatementNotSupportingUpdateCount.java index caa367d3ff..10fdf9d25f 100644 --- a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/StatementNotSupportingUpdateCount.java +++ b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/StatementNotSupportingUpdateCount.java @@ -1,3 +1,27 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ package co.elastic.apm.agent.jdbc; import java.sql.Connection; From 66058db930abc247c7031d4f7329eb25d41708ac Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 7 Feb 2020 10:20:40 +0100 Subject: [PATCH 04/16] More generics --- .../apm/agent/impl/ElasticApmTracer.java | 4 +-- .../agent/impl/transaction/TraceContext.java | 26 +++++++++---------- .../impl/transaction/TraceContextTest.java | 12 ++++----- .../jms/JmsInstrumentationHelperImpl.java | 2 +- .../ExternalSpanContextInstrumentation.java | 3 +-- .../opentracing/OpenTracingBridgeTest.java | 6 +++-- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index fd044d64f2..1bf1e2209a 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -222,7 +222,7 @@ public Transaction startChildTransaction(@Nullable C headerCarrier, TextHead if (!coreConfiguration.isActive()) { transaction = noopTransaction(); } else { - transaction = createTransaction().start(TraceContext.getFromTraceContextTextHeaders(), headerCarrier, + transaction = createTransaction().start(TraceContext.getFromTraceContextTextHeaders(), headerCarrier, textHeadersGetter, epochMicros, sampler, initiatingClassLoader); } afterTransactionStart(initiatingClassLoader, transaction); @@ -260,7 +260,7 @@ public Transaction startChildTransaction(@Nullable C headerCarrier, BinaryHe if (!coreConfiguration.isActive()) { transaction = noopTransaction(); } else { - transaction = createTransaction().start(TraceContext.getFromTraceContextBinaryHeaders(), headerCarrier, + transaction = createTransaction().start(TraceContext.getFromTraceContextBinaryHeaders(), headerCarrier, binaryHeadersGetter, epochMicros, sampler, initiatingClassLoader); } afterTransactionStart(initiatingClassLoader, transaction); diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index ccd0299de0..f2a051c540 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -104,10 +104,10 @@ public boolean asChildOf(TraceContext child, TraceContextHolder parent) { return true; } }; - private static final ChildContextCreatorTwoArg> FROM_TRACE_CONTEXT_TEXT_HEADERS = - new ChildContextCreatorTwoArg>() { + private static final ChildContextCreatorTwoArg FROM_TRACE_CONTEXT_TEXT_HEADERS = + new ChildContextCreatorTwoArg>() { @Override - public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeaderGetter traceContextHeaderGetter) { + public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeaderGetter traceContextHeaderGetter) { if (carrier == null) { return false; } @@ -115,18 +115,17 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeade // TextHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the // carrier type is the one expected in the provided TextHeaderGetter, we cannot enforce it through generics // if we want to use this single ChildContextCreatorTwoArg instance for all types. - //noinspection unchecked - String traceparent = ((TextHeaderGetter) traceContextHeaderGetter).getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + String traceparent = traceContextHeaderGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); if (traceparent != null) { return child.asChildOf(traceparent); } return false; } }; - private static final ChildContextCreatorTwoArg> FROM_TRACE_CONTEXT_BINARY_HEADERS = - new ChildContextCreatorTwoArg>() { + private static final ChildContextCreatorTwoArg FROM_TRACE_CONTEXT_BINARY_HEADERS = + new ChildContextCreatorTwoArg>() { @Override - public boolean asChildOf(TraceContext child, @Nullable Object carrier, BinaryHeaderGetter traceContextHeaderGetter) { + public boolean asChildOf(TraceContext child, @Nullable Object carrier, BinaryHeaderGetter traceContextHeaderGetter) { if (carrier == null) { return false; } @@ -134,8 +133,7 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, BinaryHea // BinaryHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the // carrier type is the one expected in the provided BinaryHeaderGetter, we cannot enforce it through generics // if we want to use this single ChildContextCreatorTwoArg instance for all types. - //noinspection unchecked - byte[] traceparent = ((BinaryHeaderGetter) traceContextHeaderGetter).getFirstHeader(TRACE_PARENT_BINARY_HEADER_NAME, carrier); + byte[] traceparent = traceContextHeaderGetter.getFirstHeader(TRACE_PARENT_BINARY_HEADER_NAME, carrier); if (traceparent != null) { return child.asChildOf(traceparent); } @@ -225,12 +223,12 @@ public static TraceContext with128BitId(ElasticApmTracer tracer) { return new TraceContext(tracer, Id.new128BitId()); } - public static ChildContextCreatorTwoArg> getFromTraceContextTextHeaders() { - return FROM_TRACE_CONTEXT_TEXT_HEADERS; + public static ChildContextCreatorTwoArg> getFromTraceContextTextHeaders() { + return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_TEXT_HEADERS; } - public static ChildContextCreatorTwoArg> getFromTraceContextBinaryHeaders() { - return FROM_TRACE_CONTEXT_BINARY_HEADERS; + public static ChildContextCreatorTwoArg> getFromTraceContextBinaryHeaders() { + return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_BINARY_HEADERS; } public static ChildContextCreator fromActive() { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index bb563f78bf..64addf7780 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -24,8 +24,8 @@ */ package co.elastic.apm.agent.impl.transaction; -import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.BinaryHeaderMapAccessor; +import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.util.HexUtils; @@ -55,7 +55,7 @@ class TraceContextTest { private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boolean isSampled) { Map textHeaderMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-" + flagsValue); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); @@ -65,7 +65,7 @@ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boo final TraceContext grandchild1 = TraceContext.with64BitId(mock(ElasticApmTracer.class)); final Map binaryHeaderMap = new HashMap<>(); assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); - assertThat(TraceContext.getFromTraceContextBinaryHeaders().asChildOf(grandchild1, binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextBinaryHeaders().asChildOf(grandchild1, binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(grandchild1.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(grandchild1.getTraceContext().getParentId().toString()).isEqualTo(child.getTraceContext().getId().toString()); assertThat(grandchild1.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getId()); @@ -100,7 +100,7 @@ void parseFromTraceParentHeaderUnsupportedFlag() { void testBinaryHeaderSizeEnforcement() { final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); final byte[] outgoingBinaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH - 1]; assertThat(child.setOutgoingTraceContextHeaders(new HashMap<>(), new BinaryHeaderSetter>() { @Override @@ -120,7 +120,7 @@ public void setHeader(String headerName, byte[] headerValue, Map void testBinaryHeaderCaching() { final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); HashMap binaryHeaderMap = new HashMap<>(); assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); byte[] outgoingHeader = binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); @@ -132,7 +132,7 @@ void testBinaryHeaderCaching() { void testBinaryHeader_CachingDisabled() { final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); BinaryHeaderSetter> headerSetter = new BinaryHeaderSetter<>() { @Override public byte[] getFixedLengthByteArray(String headerName, int length) { diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java index 6ed56f0b00..b9f509ee0a 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelperImpl.java @@ -127,7 +127,7 @@ public Transaction startJmsTransaction(Message parentMessage, Class instrumen @Override public void makeChildOf(Transaction childTransaction, Message parentMessage) { - TraceContext.getFromTraceContextTextHeaders().asChildOf(childTransaction.getTraceContext(), parentMessage, JmsMessagePropertyAccessor.instance()); + TraceContext.getFromTraceContextTextHeaders().asChildOf(childTransaction.getTraceContext(), parentMessage, JmsMessagePropertyAccessor.instance()); } @VisibleForAdvice diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java index 0015cd8d51..5b72b19ee1 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java @@ -35,7 +35,6 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; - import java.util.Map; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -105,7 +104,7 @@ public static void toSpanId(@Advice.FieldValue(value = "textMap", typing = Assig public static TraceContext parseTextMap(Iterable> textMap) { if (tracer != null) { TraceContext childTraceContext = TraceContext.with64BitId(tracer); - if (TraceContext.getFromTraceContextTextHeaders().asChildOf(childTraceContext, textMap, OpenTracingTextMapBridge.instance())) { + if (TraceContext.>>getFromTraceContextTextHeaders().asChildOf(childTraceContext, textMap, OpenTracingTextMapBridge.instance())) { return childTraceContext; } } diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java index c7e8502f2b..990d7e59c9 100644 --- a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java +++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java @@ -34,7 +34,9 @@ import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.log.Fields; -import io.opentracing.propagation.*; +import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMap; +import io.opentracing.propagation.TextMapAdapter; import io.opentracing.tag.Tags; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -457,7 +459,7 @@ void testInjectExtract() { final HashMap map = new HashMap<>(); apmTracer.inject(otSpan.context(), Format.Builtin.TEXT_MAP, new TextMapAdapter(map)); final TraceContext injectedContext = TraceContext.with64BitId(tracer); - assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(injectedContext, map, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(injectedContext, map, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(injectedContext.getTraceId().toString()).isEqualTo(traceId); assertThat(injectedContext.getParentId()).isEqualTo(transaction.getTraceContext().getId()); assertThat(injectedContext.isSampled()).isTrue(); From 66bc6a2cf62c9018526fb2bdd7d05a87aaa929a5 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 7 Feb 2020 10:39:14 +0100 Subject: [PATCH 05/16] Suggestion to improve Header*Bridge Improvements - MethodHandles are not @Nullable - Both getFirstHeader and getAllHeaders method handles are used in HeadersExtractorBridge --- .../api/AbstractSpanInstrumentation.java | 4 +- .../api/ElasticApmApiInstrumentation.java | 14 ++-- .../plugin/api/HeaderExtractorBridge.java | 28 +++---- .../plugin/api/HeaderInjectorBridge.java | 28 +++---- .../plugin/api/HeadersExtractorBridge.java | 79 ++++++++++--------- 5 files changed, 68 insertions(+), 85 deletions(-) diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java index 65e2611615..1f5398f557 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java @@ -27,7 +27,6 @@ import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.transaction.AbstractSpan; import co.elastic.apm.agent.impl.transaction.Span; -import co.elastic.apm.agent.impl.transaction.TraceContext; import co.elastic.apm.agent.impl.transaction.TraceContextHolder; import co.elastic.apm.agent.impl.transaction.Transaction; import net.bytebuddy.asm.Advice; @@ -291,8 +290,7 @@ public static void injectTraceHeaders(@Advice.FieldValue(value = "span", typing @Advice.Argument(0) MethodHandle addHeaderMethodHandle, @Advice.Argument(1) @Nullable Object headerInjector) throws Throwable { if (headerInjector != null) { - HeaderInjectorBridge.instance().setAddHeaderMethodHandle(addHeaderMethodHandle); - context.getTraceContext().setOutgoingTraceContextHeaders(headerInjector, HeaderInjectorBridge.instance()); + context.getTraceContext().setOutgoingTraceContextHeaders(headerInjector, HeaderInjectorBridge.get(addHeaderMethodHandle)); } } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java index e74802c6ed..73a720611b 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java @@ -86,14 +86,12 @@ private static void doStartTransaction(@Advice.Origin Class clazz, @Advice.Argument(2) MethodHandle getAllHeaders, @Advice.Argument(3) @Nullable Object headersExtractor) { if (tracer != null) { - if (headerExtractor != null) { - HeaderExtractorBridge headerExtractorBridge = HeaderExtractorBridge.instance(); - headerExtractorBridge.setGetHeaderMethodHandle(getFirstHeader); - transaction = tracer.startChildTransaction(headerExtractor, headerExtractorBridge, clazz.getClassLoader()); - } else if (headersExtractor != null) { - HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.instance(); - headersExtractorBridge.setGetHeaderMethodHandle(getAllHeaders); - transaction = tracer.startChildTransaction(headersExtractor, headersExtractorBridge, clazz.getClassLoader()); + if (headersExtractor != null) { + HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders); + transaction = tracer.startChildTransaction(new HeadersExtractorBridge.Extractor(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader()); + } else if (headerExtractor != null) { + HeaderExtractorBridge headersExtractorBridge = HeaderExtractorBridge.get(getFirstHeader); + transaction = tracer.startChildTransaction(headerExtractor, headersExtractorBridge, clazz.getClassLoader()); } else { transaction = tracer.startRootTransaction(clazz.getClassLoader()); } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java index dc597d8c45..d12e8884df 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java @@ -37,26 +37,20 @@ public class HeaderExtractorBridge implements TextHeaderGetter { private static final Logger logger = LoggerFactory.getLogger(HeaderExtractorBridge.class); - private static final HeaderExtractorBridge INSTANCE = new HeaderExtractorBridge(); - - @VisibleForAdvice - public static HeaderExtractorBridge instance() { - return INSTANCE; - } - @Nullable - private MethodHandle getFirstHeaderMethod; + private static HeaderExtractorBridge INSTANCE; + + private final MethodHandle getFirstHeaderMethod; - private HeaderExtractorBridge() { + private HeaderExtractorBridge(MethodHandle getFirstHeaderMethod) { + this.getFirstHeaderMethod = getFirstHeaderMethod; } - @VisibleForAdvice - public void setGetHeaderMethodHandle(MethodHandle getHeaderMethodHandle) { - // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its - // reference a couple of times on startup. Cheaper than volatile access. - if (this.getFirstHeaderMethod == null) { - this.getFirstHeaderMethod = getHeaderMethodHandle; + public static HeaderExtractorBridge get(MethodHandle getFirstHeaderMethod) { + if (INSTANCE == null) { + INSTANCE = new HeaderExtractorBridge(getFirstHeaderMethod); } + return INSTANCE; } @Nullable @@ -64,9 +58,7 @@ public void setGetHeaderMethodHandle(MethodHandle getHeaderMethodHandle) { public String getFirstHeader(String headerName, Object carrier) { String value = null; try { - if (getFirstHeaderMethod != null) { - value = (String) getFirstHeaderMethod.invoke(carrier, headerName); - } + value = (String) getFirstHeaderMethod.invoke(carrier, headerName); } catch (Throwable throwable) { logger.error("Failed to extract trace context headers", throwable); } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java index d289ed57f8..e459f3aa1c 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.java @@ -25,7 +25,6 @@ package co.elastic.apm.agent.plugin.api; import co.elastic.apm.agent.bci.VisibleForAdvice; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,34 +37,27 @@ public class HeaderInjectorBridge implements TextHeaderSetter { private static final Logger logger = LoggerFactory.getLogger(HeaderInjectorBridge.class); - private static final HeaderInjectorBridge INSTANCE = new HeaderInjectorBridge(); + @Nullable + private static HeaderInjectorBridge INSTANCE; @VisibleForAdvice - public static HeaderInjectorBridge instance() { + public static HeaderInjectorBridge get(MethodHandle addHeaderMethod) { + if (INSTANCE == null) { + INSTANCE = new HeaderInjectorBridge(addHeaderMethod); + } return INSTANCE; } - @Nullable - private MethodHandle addHeaderMethod; + private final MethodHandle addHeaderMethod; - private HeaderInjectorBridge() { - } - - @VisibleForAdvice - public void setAddHeaderMethodHandle(MethodHandle addMethodHandle) { - // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its - // reference a couple of times on startup. Cheaper than volatile access. - if (this.addHeaderMethod == null) { - this.addHeaderMethod = addMethodHandle; - } + private HeaderInjectorBridge(MethodHandle addHeaderMethod) { + this.addHeaderMethod = addHeaderMethod; } @Override public void setHeader(String headerName, String headerValue, Object carrier) { try { - if (addHeaderMethod != null) { - addHeaderMethod.invoke(carrier, headerName, headerValue); - } + addHeaderMethod.invoke(carrier, headerName, headerValue); } catch (Throwable throwable) { logger.error("Failed to add trace context headers", throwable); } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java index d25f9c73fa..866e57e7e6 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -34,64 +34,67 @@ import java.util.Iterator; @VisibleForAdvice -public class HeadersExtractorBridge implements TextHeaderGetter { +public class HeadersExtractorBridge implements TextHeaderGetter { private static final Logger logger = LoggerFactory.getLogger(HeadersExtractorBridge.class); + @Nullable + private static HeadersExtractorBridge INSTANCE; - private static final HeadersExtractorBridge INSTANCE = new HeadersExtractorBridge(); + public static class Extractor { + @Nullable + private final Object headerExtractor; + private final Object headersExtractor; - @VisibleForAdvice - public static HeadersExtractorBridge instance() { - return INSTANCE; + public Extractor(@Nullable Object headerExtractor, Object headersExtractor) { + this.headerExtractor = headerExtractor; + this.headersExtractor = headersExtractor; + } } - @Nullable - private MethodHandle getAllHeadersMethod; + private final MethodHandle getFirstHeaderMethod; + private final MethodHandle getAllHeadersMethod; - private HeadersExtractorBridge() { + public static HeadersExtractorBridge get(MethodHandle getFirstHeaderMethod, MethodHandle getAllHeadersMethod) { + if (INSTANCE == null) { + INSTANCE = new HeadersExtractorBridge(getFirstHeaderMethod, getAllHeadersMethod); + } + return INSTANCE; } - @VisibleForAdvice - public void setGetHeaderMethodHandle(MethodHandle getAllHeadersMethodHandle) { - // No need to make thread-safe - only one methodHandle can be set in practice, so we don't care replacing its - // reference a couple of times on startup. Cheaper than volatile access. - if (this.getAllHeadersMethod == null) { - this.getAllHeadersMethod = getAllHeadersMethodHandle; - } + private HeadersExtractorBridge(MethodHandle getFirstHeaderMethod, MethodHandle getAllHeadersMethod) { + this.getFirstHeaderMethod = getFirstHeaderMethod; + this.getAllHeadersMethod = getAllHeadersMethod; } @Nullable @Override - public String getFirstHeader(String headerName, Object carrier) { - String value = null; - try { - if (getAllHeadersMethod != null) { - //noinspection unchecked - final Iterable headersIterable = (Iterable) getAllHeadersMethod.invoke(carrier, headerName); - if (headersIterable != null) { - final Iterator headersIterator = headersIterable.iterator(); - if (headersIterator.hasNext()) { - value = headersIterator.next(); - } + public String getFirstHeader(String headerName, Extractor carrier) { + if (carrier.headerExtractor != null) { + try { + return (String) getFirstHeaderMethod.invoke(carrier.headerExtractor, headerName); + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); + } + } else { + Iterable headers = getHeaders(headerName, carrier); + if (headers != null) { + Iterator iterator = headers.iterator(); + if (iterator.hasNext()) { + return iterator.next(); } } - } catch (Throwable throwable) { - logger.error("Failed to extract trace context headers", throwable); } - return value; + return null; } - @Nullable @Override - public Iterable getHeaders(String headerName, Object carrier) { + public Iterable getHeaders(String headerName, Extractor carrier) { Iterable values = null; - if (getAllHeadersMethod != null) { - try { - //noinspection unchecked - values = (Iterable) getAllHeadersMethod.invoke(carrier, headerName); - } catch (Throwable throwable) { - logger.error("Failed to extract trace context headers", throwable); - } + try { + //noinspection unchecked + values = (Iterable) getAllHeadersMethod.invoke(carrier.headersExtractor, headerName); + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); } return values; } From 0433c7b167ec8b17bc3e377c49550d695c0fdce8 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 7 Feb 2020 13:43:18 +0100 Subject: [PATCH 06/16] Remove HeaderGetter#getHeaders in favor of HeaderGetter#forEach Allows for internal iteration which can be more efficient - No forced Iterator allocations - No conversion from Header to String/byte[] necessary - Consumer is stateless and can be re-used, due to state method argument --- .../transaction/AbstractHeaderGetter.java | 35 ++++++++++++ .../agent/impl/transaction/HeaderGetter.java | 21 +++++++- .../agent/impl/BinaryHeaderMapAccessor.java | 9 ++-- .../apm/agent/impl/TextHeaderMapAccessor.java | 14 +---- .../ApacheHttpClientInstrumentation.java | 3 +- .../helper/RequestHeaderAccessor.java | 18 +++---- .../plugin/api/HeaderExtractorBridge.java | 11 +--- .../plugin/api/HeadersExtractorBridge.java | 14 ++++- ...bstractAsyncHttpClientInstrumentation.java | 2 +- ...Accessor.java => RequestHeaderSetter.java} | 16 +----- ...AbstractHttpClientInstrumentationTest.java | 13 ++--- .../agent/jms/JmsMessagePropertyAccessor.java | 10 +--- .../BaseKafkaHeadersInstrumentation.java | 3 +- .../helper/KafkaRecordHeaderAccessor.java | 39 ++------------ .../AbstractOkHttp3ClientInstrumentation.java | 9 +--- .../AbstractOkHttpClientInstrumentation.java | 9 +--- ...r.java => OkHttp3RequestHeaderSetter.java} | 17 +----- ...or.java => OkHttpRequestHeaderSetter.java} | 17 +----- .../impl/OpenTracingTextMapBridge.java | 10 ++-- .../agent/servlet/ServletInstrumentation.java | 3 +- .../servlet/helper/RequestHeaderGetter.java | 54 +++---------------- .../UrlConnectionPropertyAccessor.java | 11 ++-- 22 files changed, 124 insertions(+), 214 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/AbstractHeaderGetter.java rename apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/{RequestHeaderAccessor.java => RequestHeaderSetter.java} (71%) rename apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/{OkHttp3RequestHeaderAccessor.java => OkHttp3RequestHeaderSetter.java} (70%) rename apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/{OkHttpRequestHeaderAccessor.java => OkHttpRequestHeaderSetter.java} (70%) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/AbstractHeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/AbstractHeaderGetter.java new file mode 100644 index 0000000000..6e1f2bcdb2 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/AbstractHeaderGetter.java @@ -0,0 +1,35 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +public abstract class AbstractHeaderGetter implements HeaderGetter { + @Override + public void forEach(String headerName, C carrier, S state, HeaderConsumer consumer) { + T firstHeader = getFirstHeader(headerName, carrier); + if (firstHeader != null) { + consumer.accept(firstHeader, state); + } + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java index bd809c1d0f..f1f27e19e2 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java @@ -31,6 +31,23 @@ public interface HeaderGetter { @Nullable T getFirstHeader(String headerName, C carrier); - @Nullable - Iterable getHeaders(String headerName, C carrier); + /** + * Calls the consumer for each header value with the given key + * until all entries have been processed or the action throws an exception. + *

+ * The third parameter lets callers pass in a stateful object to be modified with header values, + * so the {@link HeaderConsumer} implementation itself can be stateless and potentially reusable. + *

+ * + * @param headerName the name of the header + * @param carrier the object containing the headers + * @param state the object to be passed as the second parameter to each invocation on the specified consumer + * @param consumer the action to be performed for each header value + * @param the type of the state object + */ + void forEach(String headerName, C carrier, S state, HeaderConsumer consumer); + + interface HeaderConsumer { + void accept(T headerValue, S state); + } } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java index 9f4c83f50d..036c3ef4f8 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java @@ -30,7 +30,6 @@ import javax.annotation.Nullable; import java.util.HashMap; -import java.util.List; import java.util.Map; public class BinaryHeaderMapAccessor implements BinaryHeaderGetter>, BinaryHeaderSetter> { @@ -48,10 +47,12 @@ public byte[] getFirstHeader(String headerName, Map headerMap) { return headerMap.get(headerName); } - @Nullable @Override - public Iterable getHeaders(String headerName, Map headerMap) { - return List.of(headerMap.get(headerName)); + public void forEach(String headerName, Map carrier, S state, HeaderConsumer consumer) { + byte[] headerValue = carrier.get(headerName); + if (headerValue != null) { + consumer.accept(headerValue, state); + } } @Nullable diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java index 4c6c91b741..cdd7902568 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java @@ -24,14 +24,14 @@ */ package co.elastic.apm.agent.impl; +import co.elastic.apm.agent.impl.transaction.AbstractHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import javax.annotation.Nullable; -import java.util.List; import java.util.Map; -public class TextHeaderMapAccessor implements TextHeaderGetter>, TextHeaderSetter> { +public class TextHeaderMapAccessor extends AbstractHeaderGetter> implements TextHeaderGetter>, TextHeaderSetter> { public static final TextHeaderMapAccessor INSTANCE = new TextHeaderMapAccessor(); @@ -44,16 +44,6 @@ public String getFirstHeader(String headerName, Map headerMap) { return headerMap.get(headerName); } - @Nullable - @Override - public Iterable getHeaders(String headerName, Map headerMap) { - String value = headerMap.get(headerName); - if (value != null) { - return List.of(value); - } - return null; - } - @Override public void setHeader(String headerName, String headerValue, Map headerMap) { headerMap.put(headerName, headerValue); diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java index ccce8ba71b..2bf2079dde 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpClientInstrumentation.java @@ -25,7 +25,6 @@ package co.elastic.apm.agent.httpclient; import co.elastic.apm.agent.http.client.HttpClientHelper; -import co.elastic.apm.agent.httpclient.helper.RequestHeaderAccessor; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.transaction.Span; import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; @@ -76,7 +75,7 @@ private static void onBeforeExecute(@Advice.Argument(0) HttpRoute route, if (span != null) { span.activate(); if (headerSetter != null) { - span.getTraceContext().setOutgoingTraceContextHeaders(request, new RequestHeaderAccessor()); + span.getTraceContext().setOutgoingTraceContextHeaders(request, headerSetter); } } else if (headerGetter != null && !TraceContext.containsTraceContextTextHeaders(request, headerGetter) && headerSetter != null && parent != null) { diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java index eda60b1ad9..04ec912c9a 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java @@ -30,8 +30,6 @@ import org.apache.http.HttpRequest; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; public class RequestHeaderAccessor implements TextHeaderGetter, TextHeaderSetter { @Override @@ -49,17 +47,13 @@ public String getFirstHeader(String headerName, HttpRequest request) { return null; } - @Nullable @Override - public Iterable getHeaders(String headerName, HttpRequest request) { - Header[] headers = request.getHeaders(headerName); - if (headers == null) { - return null; - } - List ret = new ArrayList<>(); - for (Header header : headers) { - ret.add(header.getValue()); + public void forEach(String headerName, HttpRequest carrier, S state, HeaderConsumer consumer) { + Header[] headers = carrier.getHeaders(headerName); + if (headers != null) { + for (Header header : headers) { + consumer.accept(header.getValue(), state); + } } - return ret; } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java index d12e8884df..a4c8a8209c 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.java @@ -25,6 +25,7 @@ package co.elastic.apm.agent.plugin.api; import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.impl.transaction.AbstractHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,7 @@ import java.lang.invoke.MethodHandle; @VisibleForAdvice -public class HeaderExtractorBridge implements TextHeaderGetter { +public class HeaderExtractorBridge extends AbstractHeaderGetter implements TextHeaderGetter { private static final Logger logger = LoggerFactory.getLogger(HeaderExtractorBridge.class); @@ -65,12 +66,4 @@ public String getFirstHeader(String headerName, Object carrier) { return value; } - /** - * Returns null. {@link HeadersExtractorBridge} should be used instead for this functionality - */ - @Nullable - @Override - public Iterable getHeaders(String headerName, Object carrier) { - return null; - } } diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java index 866e57e7e6..07ac5ceeb2 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -76,7 +76,7 @@ public String getFirstHeader(String headerName, Extractor carrier) { logger.error("Failed to extract trace context headers", throwable); } } else { - Iterable headers = getHeaders(headerName, carrier); + Iterable headers = getValues(headerName, carrier); if (headers != null) { Iterator iterator = headers.iterator(); if (iterator.hasNext()) { @@ -88,7 +88,17 @@ public String getFirstHeader(String headerName, Extractor carrier) { } @Override - public Iterable getHeaders(String headerName, Extractor carrier) { + public void forEach(String headerName, Extractor carrier, S state, HeaderConsumer consumer) { + Iterable values = getValues(headerName, carrier); + if (values != null) { + for (String value : values) { + consumer.accept(value, state); + } + } + } + + @Nullable + public Iterable getValues(String headerName, Extractor carrier) { Iterable values = null; try { //noinspection unchecked diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java index 059bf4c6a7..640fd907ed 100644 --- a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/AbstractAsyncHttpClientInstrumentation.java @@ -76,7 +76,7 @@ public AbstractAsyncHttpClientInstrumentation() { synchronized (AbstractAsyncHandlerInstrumentation.class) { if (headerSetterManager == null) { headerSetterManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.asynchttpclient.helper.RequestHeaderAccessor" + "co.elastic.apm.agent.asynchttpclient.helper.RequestHeaderSetter" ); } } diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java similarity index 71% rename from apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java rename to apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java index 20cda7a56a..edd644f917 100644 --- a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderAccessor.java +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java @@ -25,29 +25,15 @@ package co.elastic.apm.agent.asynchttpclient.helper; import co.elastic.apm.agent.bci.VisibleForAdvice; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import org.asynchttpclient.Request; -import javax.annotation.Nullable; - @VisibleForAdvice -public class RequestHeaderAccessor implements TextHeaderGetter, TextHeaderSetter { +public class RequestHeaderSetter implements TextHeaderSetter { @Override public void setHeader(String headerName, String headerValue, Request request) { request.getHeaders().set(headerName, headerValue); } - @Nullable - @Override - public String getFirstHeader(String headerName, Request request) { - return request.getHeaders().get(headerName); - } - - @Nullable - @Override - public Iterable getHeaders(String headerName, Request request) { - return request.getHeaders().getAll(headerName); - } } diff --git a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java index 035c974b04..c51f5c94e3 100644 --- a/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-httpclient-core/src/test/java/co/elastic/apm/agent/httpclient/AbstractHttpClientInstrumentationTest.java @@ -42,7 +42,6 @@ import org.junit.Test; import javax.annotation.Nullable; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,13 +49,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.any; import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.seeOther; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractHttpClientInstrumentationTest extends AbstractInstrumentationTest { @@ -229,17 +225,18 @@ public String getFirstHeader(String headerName, LoggedRequest loggedRequest) { return loggedRequest.getHeader(headerName); } - @Nullable @Override - public Iterable getHeaders(String headerName, LoggedRequest loggedRequest) { + public void forEach(String headerName, LoggedRequest loggedRequest, S state, HeaderConsumer consumer) { HttpHeaders headers = loggedRequest.getHeaders(); if (headers != null) { HttpHeader header = headers.getHeader(headerName); if (header != null) { - return header.values(); + List values = header.values(); + for (int i = 0, size = values.size(); i < size; i++) { + consumer.accept(values.get(i), state); + } } } - return null; } } } diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java index 9a3954d1e9..6ee6a187ad 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java @@ -24,6 +24,7 @@ */ package co.elastic.apm.agent.jms; +import co.elastic.apm.agent.impl.transaction.AbstractHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import co.elastic.apm.agent.impl.transaction.TraceContext; @@ -37,7 +38,7 @@ import static co.elastic.apm.agent.jms.JmsInstrumentationHelper.JMS_TRACE_PARENT_PROPERTY; -public class JmsMessagePropertyAccessor implements TextHeaderGetter, TextHeaderSetter { +public class JmsMessagePropertyAccessor extends AbstractHeaderGetter implements TextHeaderGetter, TextHeaderSetter { private static final Logger logger = LoggerFactory.getLogger(JmsMessagePropertyAccessor.class); @@ -72,13 +73,6 @@ private String jmsifyHeaderName(String headerName) { return headerName; } - @Nullable - @Override - public Iterable getHeaders(String headerName, Message message) { - // not supported for JMS - return null; - } - @Override public void setHeader(String headerName, String headerValue, Message message) { headerName = jmsifyHeaderName(headerName); diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java index 4d51810e41..01ba0633ce 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/BaseKafkaHeadersInstrumentation.java @@ -52,8 +52,7 @@ private synchronized static void init(ElasticApmTracer tracer) { "co.elastic.apm.agent.kafka.helper.ConsumerRecordsIterableWrapper", "co.elastic.apm.agent.kafka.helper.ConsumerRecordsListWrapper", "co.elastic.apm.agent.kafka.helper.ElasticHeaderImpl", - "co.elastic.apm.agent.kafka.helper.KafkaRecordHeaderAccessor", - "co.elastic.apm.agent.kafka.helper.KafkaRecordHeaderAccessor$HeaderValuesIterator"); + "co.elastic.apm.agent.kafka.helper.KafkaRecordHeaderAccessor"); } } diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java index a47d565146..a8a4a4a7c0 100644 --- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java +++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/helper/KafkaRecordHeaderAccessor.java @@ -35,20 +35,16 @@ import javax.annotation.Nullable; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import java.util.NoSuchElementException; @SuppressWarnings("rawtypes") class KafkaRecordHeaderAccessor implements BinaryHeaderGetter, BinaryHeaderSetter, - HeaderRemover, Iterable { + HeaderRemover { public static final Logger logger = LoggerFactory.getLogger(KafkaRecordHeaderAccessor.class); private static final KafkaRecordHeaderAccessor INSTANCE = new KafkaRecordHeaderAccessor(); - private static final HeaderValuesIterator headerIteratorWrapper = new HeaderValuesIterator(); - private static final ThreadLocal> threadLocalHeaderMap = new ThreadLocal<>(); public static KafkaRecordHeaderAccessor instance() { @@ -65,16 +61,11 @@ public byte[] getFirstHeader(String headerName, ConsumerRecord record) { return null; } - @Nullable - @Override - public Iterable getHeaders(String headerName, ConsumerRecord record) { - headerIteratorWrapper.headerIterator = record.headers().headers(headerName).iterator(); - return this; - } - @Override - public Iterator iterator() { - return headerIteratorWrapper; + public void forEach(String headerName, ConsumerRecord carrier, S state, HeaderConsumer consumer) { + for (Header header : carrier.headers().headers(headerName)) { + consumer.accept(header.value(), state); + } } @Override @@ -114,24 +105,4 @@ public void remove(String headerName, ProducerRecord carrier) { carrier.headers().remove(headerName); } - private static final class HeaderValuesIterator implements Iterator { - @Nullable - private Iterator
headerIterator; - - @Override - public boolean hasNext() { - if (headerIterator != null) { - return headerIterator.hasNext(); - } - return false; - } - - @Override - public byte[] next() { - if (headerIterator != null) { - return headerIterator.next().value(); - } - throw new NoSuchElementException("Kafka record header iterator is not initialized"); - } - } } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java index 62fdc9a968..b315d1ec1c 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java @@ -28,7 +28,6 @@ import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import okhttp3.Request; @@ -41,17 +40,11 @@ public abstract class AbstractOkHttp3ClientInstrumentation extends ElasticApmIns // We can refer OkHttp types thanks to type erasure @VisibleForAdvice @Nullable - public static HelperClassManager> headerGetterHelperManager; - @VisibleForAdvice - @Nullable public static HelperClassManager> headerSetterHelperManager; public AbstractOkHttp3ClientInstrumentation(ElasticApmTracer tracer) { - headerGetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderAccessor" - ); headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderAccessor" + "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderSetter" ); } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java index fc5a1df096..1362321841 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java @@ -28,7 +28,6 @@ import co.elastic.apm.agent.bci.HelperClassManager; import co.elastic.apm.agent.bci.VisibleForAdvice; import co.elastic.apm.agent.impl.ElasticApmTracer; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import com.squareup.okhttp.Request; @@ -41,17 +40,11 @@ public abstract class AbstractOkHttpClientInstrumentation extends ElasticApmInst // We can refer OkHttp types thanks to type erasure @VisibleForAdvice @Nullable - public static HelperClassManager> headerGetterHelperManager; - @VisibleForAdvice - @Nullable public static HelperClassManager> headerSetterHelperManager; public AbstractOkHttpClientInstrumentation(ElasticApmTracer tracer) { - headerGetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderAccessor" - ); headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, - "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderAccessor" + "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderSetter" ); } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderSetter.java similarity index 70% rename from apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java rename to apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderSetter.java index be24fb7dd5..ef74770ab0 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderAccessor.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderSetter.java @@ -24,25 +24,10 @@ */ package co.elastic.apm.agent.okhttp; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import okhttp3.Request; -import javax.annotation.Nullable; - -public class OkHttp3RequestHeaderAccessor implements TextHeaderSetter, TextHeaderGetter { - - @Nullable - @Override - public String getFirstHeader(String headerName, Request request) { - return request.header(headerName); - } - - @Nullable - @Override - public Iterable getHeaders(String headerName, Request request) { - return request.headers(headerName); - } +public class OkHttp3RequestHeaderSetter implements TextHeaderSetter { @Override public void setHeader(String headerName, String headerValue, Request.Builder requestBuilder) { diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderSetter.java similarity index 70% rename from apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java rename to apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderSetter.java index d3ee86e3c7..11088bf307 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderAccessor.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderSetter.java @@ -24,25 +24,10 @@ */ package co.elastic.apm.agent.okhttp; -import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import com.squareup.okhttp.Request; -import javax.annotation.Nullable; - -public class OkHttpRequestHeaderAccessor implements TextHeaderSetter, TextHeaderGetter { - - @Nullable - @Override - public String getFirstHeader(String headerName, Request request) { - return request.header(headerName); - } - - @Nullable - @Override - public Iterable getHeaders(String headerName, Request request) { - return request.headers(headerName); - } +public class OkHttpRequestHeaderSetter implements TextHeaderSetter { @Override public void setHeader(String headerName, String headerValue, Request.Builder requestBuilder) { diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java index 4c5902a785..c085ffe94e 100644 --- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.java @@ -55,11 +55,13 @@ public String getFirstHeader(String headerName, Iterable getHeaders(String headerName, Iterable> textMap) { - // Not supported for OpenTracing TextMap - return null; + public void forEach(String headerName, Iterable> carrier, S state, HeaderConsumer consumer) { + for (Map.Entry entry : carrier) { + if (entry.getKey().equalsIgnoreCase(headerName)) { + consumer.accept(entry.getValue(), state); + } + } } @Override diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java index 4938c11036..3293349160 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java @@ -69,8 +69,7 @@ public ServletInstrumentation(ElasticApmTracer tracer) { ServletApiAdvice.init(tracer); servletTransactionCreationHelperManager = HelperClassManager.ForSingleClassLoader.of(tracer, "co.elastic.apm.agent.servlet.helper.ServletTransactionCreationHelperImpl", - "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter", - "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter$HeaderValuesIterator" + "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter" ); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java index bb7c3449ed..32a5156fc7 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java @@ -29,26 +29,13 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; -import java.util.Iterator; -import java.util.NoSuchElementException; -class RequestHeaderGetter implements TextHeaderGetter, Iterable { +class RequestHeaderGetter implements TextHeaderGetter { - // Caching in ThreadLocal in order to reuse the Enumeration-based iterator - private static final ThreadLocal threadLocalRequestHeaderGetter = new ThreadLocal<>(); + private static final RequestHeaderGetter INSTANCE = new RequestHeaderGetter(); static RequestHeaderGetter getInstance() { - RequestHeaderGetter requestHeaderGetter = threadLocalRequestHeaderGetter.get(); - if (requestHeaderGetter == null) { - requestHeaderGetter = new RequestHeaderGetter(); - threadLocalRequestHeaderGetter.set(requestHeaderGetter); - } - return requestHeaderGetter; - } - - private final HeaderValuesIterator headerValuesIterator = new HeaderValuesIterator(); - - private RequestHeaderGetter() { + return INSTANCE; } @Nullable @@ -57,37 +44,12 @@ public String getFirstHeader(String headerName, HttpServletRequest request) { return request.getHeader(headerName); } - @Nullable - @Override - public Iterable getHeaders(String headerName, HttpServletRequest request) { - headerValuesIterator.headerValues = request.getHeaders(headerName); - return this; - } - @Override - public Iterator iterator() { - return headerValuesIterator; - } - - private static final class HeaderValuesIterator implements Iterator { - - @Nullable - private Enumeration headerValues; - - @Override - public boolean hasNext() { - if (headerValues != null) { - return headerValues.hasMoreElements(); - } - return false; - } - - @Override - public String next() { - if (headerValues != null) { - return headerValues.nextElement(); - } - throw new NoSuchElementException("Header values Enumeration is not initialized"); + public void forEach(String headerName, HttpServletRequest carrier, S state, HeaderConsumer consumer) { + Enumeration headers = carrier.getHeaders(headerName); + while (headers.hasMoreElements()) { + consumer.accept(headers.nextElement(), state); } } + } diff --git a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java index 74cce038af..37cb413f18 100644 --- a/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.java @@ -29,6 +29,7 @@ import javax.annotation.Nullable; import java.net.HttpURLConnection; +import java.util.List; public class UrlConnectionPropertyAccessor implements TextHeaderSetter, TextHeaderGetter { @@ -49,9 +50,13 @@ public String getFirstHeader(String headerName, HttpURLConnection urlConnection) return urlConnection.getRequestProperty(headerName); } - @Nullable @Override - public Iterable getHeaders(String headerName, HttpURLConnection urlConnection) { - return urlConnection.getRequestProperties().get(headerName); + public void forEach(String headerName, HttpURLConnection carrier, S state, HeaderConsumer consumer) { + List values = carrier.getRequestProperties().get(headerName); + if (values != null) { + for (int i = 0, size = values.size(); i < size; i++) { + consumer.accept(values.get(i), state); + } + } } } From d90e10b8c5f993751e53b74820fc44d53bcf5fbb Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Sun, 9 Feb 2020 10:44:58 +0200 Subject: [PATCH 07/16] Apply review suggestions --- .../elastic/apm/agent/impl/ElasticApmTracer.java | 3 --- .../co/elastic/apm/agent/impl/context/Message.java | 5 ++--- .../apm/agent/impl/transaction/TraceContext.java | 14 +++----------- .../logging/AbstractLoggingInstrumentation.java | 6 +++--- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index fd044d64f2..8db6637e80 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java @@ -99,7 +99,6 @@ public class ElasticApmTracer { // Maintains a stack of all the activated spans // This way its easy to retrieve the bottom of the stack (the transaction) // Also, the caller does not have to keep a reference to the previously active span, as that is maintained by the stack - @SuppressWarnings({"Convert2Diamond", "AnonymousHasLambdaAlternative"}) private final ThreadLocal>> activeStack = new ThreadLocal>>() { @Override protected Deque> initialValue() { @@ -107,7 +106,6 @@ protected Deque> initialValue() { } }; - @SuppressWarnings({"AnonymousHasLambdaAlternative", "Convert2Diamond"}) private final ThreadLocal allowWrappingOnThread = new ThreadLocal() { @Override protected Boolean initialValue() { @@ -144,7 +142,6 @@ protected Boolean initialValue() { sampler = ProbabilitySampler.of(coreConfiguration.getSampleRate().get()); - //noinspection Convert2Lambda,Convert2Diamond coreConfiguration.getSampleRate().addChangeListener(new ConfigurationOption.ChangeListener() { @Override public void onChange(ConfigurationOption configurationOption, Double oldValue, Double newValue) { diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Message.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Message.java index 971bcf7cee..19ba4f181c 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Message.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/context/Message.java @@ -11,9 +11,9 @@ * 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 @@ -37,7 +37,6 @@ public class Message implements Recyclable { - @SuppressWarnings({"Convert2Diamond", "Convert2Lambda", "Anonymous2MethodRef"}) private static final ObjectPool stringBuilderPool = QueueBasedObjectPool.of(new MpmcAtomicArrayQueue(128), false, new Allocator() { @Override diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index ccd0299de0..85156b5a46 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -70,7 +70,7 @@ * 2, 1] * */ -@SuppressWarnings({"rawtypes", "Convert2Diamond", "Convert2Lambda"}) +@SuppressWarnings({"rawtypes"}) public class TraceContext extends TraceContextHolder { public static final String TRACE_PARENT_TEXTUAL_HEADER_NAME = "elastic-apm-traceparent"; @@ -471,11 +471,7 @@ String getIncomingTraceParentHeader() { * @param headerSetter a setter implementing the actual addition of headers to the headers carrier * @param the header carrier type, for example - an HTTP request */ - public void setOutgoingTraceContextHeaders(@Nullable C carrier, TextHeaderSetter headerSetter) { - if (carrier == null) { - logger.warn("Attempted to set a text header to a null carrier through {}. Distributed tracing cannot be supported.", headerSetter.getClass().getName()); - return; - } + public void setOutgoingTraceContextHeaders(C carrier, TextHeaderSetter headerSetter) { headerSetter.setHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); } @@ -487,11 +483,7 @@ public void setOutgoingTraceContextHeaders(@Nullable C carrier, TextHeaderSe * @param the header carrier type, for example - a Kafka record * @return true if Trace Context headers were set; false otherwise */ - public boolean setOutgoingTraceContextHeaders(@Nullable C carrier, BinaryHeaderSetter headerSetter) { - if (carrier == null) { - logger.warn("Attempted to set a binary header to a null carrier through {}. Distributed tracing cannot be supported.", headerSetter.getClass().getName()); - return false; - } + public boolean setOutgoingTraceContextHeaders(C carrier, BinaryHeaderSetter headerSetter) { byte[] buffer = headerSetter.getFixedLengthByteArray(TRACE_PARENT_BINARY_HEADER_NAME, BINARY_FORMAT_EXPECTED_LENGTH); if (buffer == null || buffer.length != BINARY_FORMAT_EXPECTED_LENGTH) { logger.warn("Header setter {} failed to provide a byte buffer with the proper length. Allocating a buffer for each header.", diff --git a/apm-agent-plugins/apm-error-logging-plugin/src/main/java/co/elastic/apm/agent/error/logging/AbstractLoggingInstrumentation.java b/apm-agent-plugins/apm-error-logging-plugin/src/main/java/co/elastic/apm/agent/error/logging/AbstractLoggingInstrumentation.java index 3c256255ed..0b6fee32fb 100644 --- a/apm-agent-plugins/apm-error-logging-plugin/src/main/java/co/elastic/apm/agent/error/logging/AbstractLoggingInstrumentation.java +++ b/apm-agent-plugins/apm-error-logging-plugin/src/main/java/co/elastic/apm/agent/error/logging/AbstractLoggingInstrumentation.java @@ -11,9 +11,9 @@ * 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 @@ -40,7 +40,7 @@ public abstract class AbstractLoggingInstrumentation extends ElasticApmInstrumentation { - @SuppressWarnings({"WeakerAccess", "AnonymousHasLambdaAlternative"}) + @SuppressWarnings({"WeakerAccess"}) @VisibleForAdvice public static final ThreadLocal nestedThreadLocal = new ThreadLocal() { @Override From 31e7b841e4f0f4d65e260eaa4a2d1fd2578454fd Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Sun, 9 Feb 2020 11:23:02 +0200 Subject: [PATCH 08/16] minor adjustments --- .../plugin/api/ElasticApmApiInstrumentation.java | 2 +- .../agent/plugin/api/HeadersExtractorBridge.java | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java index 73a720611b..d59e2a4166 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java @@ -88,7 +88,7 @@ private static void doStartTransaction(@Advice.Origin Class clazz, if (tracer != null) { if (headersExtractor != null) { HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders); - transaction = tracer.startChildTransaction(new HeadersExtractorBridge.Extractor(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader()); + transaction = tracer.startChildTransaction(HeadersExtractorBridge.Extractor.of(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader()); } else if (headerExtractor != null) { HeaderExtractorBridge headersExtractorBridge = HeaderExtractorBridge.get(getFirstHeader); transaction = tracer.startChildTransaction(headerExtractor, headersExtractorBridge, clazz.getClassLoader()); diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java index 07ac5ceeb2..24981ff02d 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -41,13 +41,18 @@ public class HeadersExtractorBridge implements TextHeaderGetter Date: Sun, 9 Feb 2020 14:27:06 +0200 Subject: [PATCH 09/16] Implement usage of the W3C traceparent header --- .../configuration/CoreConfiguration.java | 22 ++- .../agent/impl/transaction/TraceContext.java | 53 ++++-- .../apm/agent/impl/ElasticApmTracerTest.java | 2 +- .../apm/agent/impl/TextHeaderMapAccessor.java | 9 +- .../impl/transaction/TraceContextTest.java | 162 ++++++++++++++---- ...ttpAsyncClientRedirectInstrumentation.java | 2 +- .../agent/jms/JmsInstrumentationHelper.java | 2 +- .../agent/jms/JmsMessagePropertyAccessor.java | 2 +- .../opentracing/OpenTracingBridgeTest.java | 4 +- docs/configuration.asciidoc | 40 ++++- 10 files changed, 240 insertions(+), 58 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index fce45c2f85..924c6bfc6f 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -11,9 +11,9 @@ * 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 @@ -485,6 +485,20 @@ public class CoreConfiguration extends ConfigurationOptionProvider { "NOTE: this option can only be set via system properties, environment variables or the attacher options.") .buildWithDefault(DEFAULT_CONFIG_FILE); + private final ConfigurationOption useElasticTraceparentHeader = ConfigurationOption.booleanOption() + .key("use_elastic_traceparent_header") + .tags("added[1.13.0]") + .configurationCategory(CORE_CATEGORY) + .description("To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent\n" + + "adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.).\n" + + "These headers (`traceparent` and `tracestate`) are defined in the\n" + + "https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification.\n" + + "\n" + + "When this setting is `true`, the agent will also add the header `elastic-apm-traceparent`\n" + + "for backwards compatibility with older versions of Elastic APM agents.") + .dynamic(true) + .buildWithDefault(true); + public boolean isActive() { return active.get(); } @@ -605,6 +619,10 @@ public boolean isBreakdownMetricsEnabled() { return breakdownMetrics.get(); } + public boolean isElasticTraceparentHeaderEnabled() { + return useElasticTraceparentHeader.get(); + } + /* * Makes sure to not initialize ConfigurationOption, which would initialize the logger */ diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index 14269587b8..6dca1e9b68 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -24,6 +24,7 @@ */ package co.elastic.apm.agent.impl.transaction; +import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.sampling.Sampler; import co.elastic.apm.agent.util.HexUtils; @@ -73,7 +74,8 @@ @SuppressWarnings({"rawtypes"}) public class TraceContext extends TraceContextHolder { - public static final String TRACE_PARENT_TEXTUAL_HEADER_NAME = "elastic-apm-traceparent"; + public static final String ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME = "elastic-apm-traceparent"; + public static final String W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME = "traceparent"; private static final int TEXT_HEADER_EXPECTED_LENGTH = 55; private static final int TEXT_HEADER_TRACE_ID_OFFSET = 3; private static final int TEXT_HEADER_PARENT_ID_OFFSET = 36; @@ -111,15 +113,21 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeade if (carrier == null) { return false; } - // This cast is required, otherwise the compiler can't guarantee that the carrier type is allowed by the - // TextHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the - // carrier type is the one expected in the provided TextHeaderGetter, we cannot enforce it through generics - // if we want to use this single ChildContextCreatorTwoArg instance for all types. - String traceparent = traceContextHeaderGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + + boolean isValid = false; + String traceparent = traceContextHeaderGetter.getFirstHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); if (traceparent != null) { - return child.asChildOf(traceparent); + isValid = child.asChildOf(traceparent); } - return false; + + if (!isValid) { + // Look for the legacy Elastic traceparent header (in case this comes from an older agent) + traceparent = traceContextHeaderGetter.getFirstHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + if (traceparent != null) { + isValid = child.asChildOf(traceparent); + } + } + return isValid; } }; private static final ChildContextCreatorTwoArg FROM_TRACE_CONTEXT_BINARY_HEADERS = @@ -129,10 +137,6 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, BinaryHea if (carrier == null) { return false; } - // This cast is required, otherwise the compiler can't guarantee that the carrier type is allowed by the - // BinaryHeaderGetter (which have a runtime inferred type of ?). The caller must ensure anyway that the - // carrier type is the one expected in the provided BinaryHeaderGetter, we cannot enforce it through generics - // if we want to use this single ChildContextCreatorTwoArg instance for all types. byte[] traceparent = traceContextHeaderGetter.getFirstHeader(TRACE_PARENT_BINARY_HEADER_NAME, carrier); if (traceparent != null) { return child.asChildOf(traceparent); @@ -159,17 +163,22 @@ public boolean asChildOf(TraceContext child, Object ignore) { }; public static boolean containsTraceContextTextHeaders(C carrier, TextHeaderGetter headerGetter) { - return headerGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier) != null; + return headerGetter.getFirstHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier) != null; } public static void removeTraceContextHeaders(C carrier, HeaderRemover headerRemover) { - headerRemover.remove(TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + headerRemover.remove(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + headerRemover.remove(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); } - public static void copyTextHeaders(S source, TextHeaderGetter headerGetter, D destination, TextHeaderSetter headerSetter) { - String elasticApmTraceParent = headerGetter.getFirstHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, source); + public static void copyTraceContextTextHeaders(S source, TextHeaderGetter headerGetter, D destination, TextHeaderSetter headerSetter) { + String w3cApmTraceParent = headerGetter.getFirstHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, source); + if (w3cApmTraceParent != null) { + headerSetter.setHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, w3cApmTraceParent, destination); + } + String elasticApmTraceParent = headerGetter.getFirstHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, source); if (elasticApmTraceParent != null) { - headerSetter.setHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, elasticApmTraceParent, destination); + headerSetter.setHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, elasticApmTraceParent, destination); } } @@ -187,6 +196,8 @@ public static void copyTextHeaders(S source, TextHeaderGetter headerGe @Nullable private WeakReference applicationClassLoader; + private final CoreConfiguration coreConfiguration; + /** * Avoids clock drifts within a transaction. * @@ -198,6 +209,7 @@ public static void copyTextHeaders(S source, TextHeaderGetter headerGe private TraceContext(ElasticApmTracer tracer, Id id) { super(tracer); + coreConfiguration = tracer.getConfig(CoreConfiguration.class); this.id = id; } @@ -223,10 +235,12 @@ public static TraceContext with128BitId(ElasticApmTracer tracer) { return new TraceContext(tracer, Id.new128BitId()); } + @SuppressWarnings("unchecked") public static ChildContextCreatorTwoArg> getFromTraceContextTextHeaders() { return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_TEXT_HEADERS; } + @SuppressWarnings("unchecked") public static ChildContextCreatorTwoArg> getFromTraceContextBinaryHeaders() { return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_BINARY_HEADERS; } @@ -470,7 +484,10 @@ String getIncomingTraceParentHeader() { * @param the header carrier type, for example - an HTTP request */ public void setOutgoingTraceContextHeaders(C carrier, TextHeaderSetter headerSetter) { - headerSetter.setHeader(TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); + headerSetter.setHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); + if (coreConfiguration.isElasticTraceparentHeaderEnabled()) { + headerSetter.setHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); + } } /** diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index a7ba08fe06..6204fa0a76 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java @@ -401,7 +401,7 @@ public void stop() { @Test void testTransactionWithParentReference() { - final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final Map headerMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); final Transaction transaction = tracerImpl.startChildTransaction(headerMap, TextHeaderMapAccessor.INSTANCE, ConstantSampler.of(false), 0, null); // the traced flag in the header overrides the sampler assertThat(transaction.isSampled()).isTrue(); diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java index cdd7902568..5cb6d1c954 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java @@ -25,13 +25,15 @@ package co.elastic.apm.agent.impl; import co.elastic.apm.agent.impl.transaction.AbstractHeaderGetter; +import co.elastic.apm.agent.impl.transaction.HeaderRemover; import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; import javax.annotation.Nullable; import java.util.Map; -public class TextHeaderMapAccessor extends AbstractHeaderGetter> implements TextHeaderGetter>, TextHeaderSetter> { +public class TextHeaderMapAccessor extends AbstractHeaderGetter> implements + TextHeaderGetter>, TextHeaderSetter>, HeaderRemover> { public static final TextHeaderMapAccessor INSTANCE = new TextHeaderMapAccessor(); @@ -48,4 +50,9 @@ public String getFirstHeader(String headerName, Map headerMap) { public void setHeader(String headerName, String headerValue, Map headerMap) { headerMap.put(headerName, headerValue); } + + @Override + public void remove(String headerName, Map carrier) { + carrier.remove(headerName); + } } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index 64addf7780..af90872a4d 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -24,22 +24,42 @@ */ package co.elastic.apm.agent.impl.transaction; +import co.elastic.apm.agent.MockReporter; +import co.elastic.apm.agent.configuration.CoreConfiguration; +import co.elastic.apm.agent.configuration.SpyConfiguration; import co.elastic.apm.agent.impl.BinaryHeaderMapAccessor; import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.sampling.ConstantSampler; +import co.elastic.apm.agent.objectpool.TestObjectPoolFactory; import co.elastic.apm.agent.util.HexUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.stagemonitor.configuration.ConfigurationRegistry; import java.util.HashMap; import java.util.Map; import java.util.Random; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class TraceContextTest { + private ElasticApmTracer tracer; + private ConfigurationRegistry config; + + @BeforeEach + public void setup() { + config = SpyConfiguration.createSpyConfig(); + tracer = new ElasticApmTracerBuilder() + .configurationRegistry(config) + .reporter(new MockReporter()) + .withObjectPoolFactory(new TestObjectPoolFactory()) + .build(); + } + /** * Test flow: * 1. create a parent context from a fixed string @@ -53,8 +73,8 @@ class TraceContextTest { * @param isSampled whether to test context propagation of sampled trace or not */ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boolean isSampled) { - Map textHeaderMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-" + flagsValue); - final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + Map textHeaderMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-" + flagsValue); + final TraceContext child = TraceContext.with64BitId(tracer); assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); @@ -62,7 +82,7 @@ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boo assertThat(child.isSampled()).isEqualTo(isSampled); // create a grandchild to ensure proper regenerated trace context - final TraceContext grandchild1 = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext grandchild1 = TraceContext.with64BitId(tracer); final Map binaryHeaderMap = new HashMap<>(); assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(TraceContext.>getFromTraceContextBinaryHeaders().asChildOf(grandchild1, binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); @@ -73,7 +93,7 @@ private void mixTextAndBinaryParsingAndContextPropagation(String flagsValue, boo String childHeader = child.getOutgoingTraceParentTextHeader().toString(); assertThat(childHeader).endsWith("-" + flagsValue); - final TraceContext grandchild2 = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext grandchild2 = TraceContext.with64BitId(tracer); assertThat(grandchild2.asChildOf(childHeader)).isTrue(); assertThat(grandchild2.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); assertThat(grandchild2.getTraceContext().getParentId().toString()).isEqualTo(child.getTraceContext().getId().toString()); @@ -96,10 +116,92 @@ void parseFromTraceParentHeaderUnsupportedFlag() { mixTextAndBinaryParsingAndContextPropagation("03", true); } + @Test + void testChildOfElasticTraceparentHeader() { + Map textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); + assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); + assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); + assertThat(child.isSampled()).isTrue(); + } + + @Test + void testElasticTraceparentHeaderPrecedence() { + Map textHeaderMap = Map.of( + TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", + TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-dd8448eb211c80319c0af7651916cd43-f97918e1b9c7c989-00" + ); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("dd8448eb211c80319c0af7651916cd43"); + assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("f97918e1b9c7c989"); + assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); + assertThat(child.isSampled()).isFalse(); + } + + @Test + void testInvalidElasticTraceparentHeader() { + Map textHeaderMap = Map.of( + TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", + // one char too short trace ID + TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-d8448eb211c80319c0af7651916cd43-f97918e1b9c7c989-00" + ); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + // we should fallback to try the W3C header + assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); + assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); + assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); + assertThat(child.isSampled()).isTrue(); + } + + @Test + void testElasticTraceparentHeaderDisabled() { + Map textHeaderMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + Map outgoingHeaders = new HashMap<>(); + when(config.getConfig(CoreConfiguration.class).isElasticTraceparentHeaderEnabled()).thenReturn(false); + child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + } + + @Test + void testTraceContextHeadersRemoval() { + Map textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + Map outgoingHeaders = new HashMap<>(); + child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + TraceContext.removeTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + } + + @Test + void testTraceContextHeadersCopy() { + Map textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + Map outgoingHeaders = new HashMap<>(); + child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + Map copyOfOutgoingHeaders = new HashMap<>(); + TraceContext.copyTraceContextTextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE, copyOfOutgoingHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(copyOfOutgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(copyOfOutgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + } + @Test void testBinaryHeaderSizeEnforcement() { - final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final Map headerMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); final byte[] outgoingBinaryHeader = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH - 1]; assertThat(child.setOutgoingTraceContextHeaders(new HashMap<>(), new BinaryHeaderSetter>() { @@ -118,8 +220,8 @@ public void setHeader(String headerName, byte[] headerValue, Map @Test void testBinaryHeaderCaching() { - final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final Map headerMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); HashMap binaryHeaderMap = new HashMap<>(); assertThat(child.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); @@ -130,8 +232,8 @@ void testBinaryHeaderCaching() { @Test void testBinaryHeader_CachingDisabled() { - final Map headerMap = Map.of(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final Map headerMap = Map.of(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + final TraceContext child = TraceContext.with64BitId(tracer); assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, headerMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); BinaryHeaderSetter> headerSetter = new BinaryHeaderSetter<>() { @Override @@ -177,7 +279,7 @@ private void verifyTraceContextContents(byte[] traceContext, String expectedTrac @Test void outgoingHeader() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-03"; assertThat(traceContext.asChildOf(header)).isTrue(); String parentId = traceContext.getId().toString(); @@ -191,7 +293,7 @@ void outgoingHeader() { @Test void outgoingHeaderRootSpan() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asRootSpan(ConstantSampler.of(true)); assertThat(traceContext.isSampled()).isTrue(); String outgoingStringHeader = traceContext.getOutgoingTraceParentTextHeader().toString(); @@ -206,7 +308,7 @@ void outgoingHeaderRootSpan() { @Test void parseFromTraceParentHeader_notSampled() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); final String header = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"; assertThat(traceContext.asChildOf(header)).isTrue(); assertThat(traceContext.isSampled()).isFalse(); @@ -215,7 +317,7 @@ void parseFromTraceParentHeader_notSampled() { @Test void testResetState() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); traceContext.resetState(); assertThat(traceContext.getIncomingTraceParentHeader()).isEqualTo("00-00000000000000000000000000000000-0000000000000000-00"); @@ -223,7 +325,7 @@ void testResetState() { @Test void testResetOutgoingTextHeader() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); String traceParentHeader = traceContext.getOutgoingTraceParentTextHeader().toString(); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); assertThat(traceContext.getOutgoingTraceParentTextHeader().toString()).isNotEqualTo(traceParentHeader); @@ -231,7 +333,7 @@ void testResetOutgoingTextHeader() { @Test void testResetOutgoingBinaryHeader() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); Map headerMap = new HashMap<>(); assertThat(traceContext.setOutgoingTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE)).isTrue(); byte[] outgoingByteHeader = headerMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME); @@ -248,10 +350,10 @@ void testResetOutgoingBinaryHeader() { @Test void testCopyFrom() { - final TraceContext first = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext first = TraceContext.with64BitId(tracer); first.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - final TraceContext second = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext second = TraceContext.with64BitId(tracer); second.asChildOf("00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); assertThat(first.getTraceId()).isNotEqualTo(second.getTraceId()); @@ -278,7 +380,7 @@ void testCopyFrom() { @Test void testRandomValue() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asRootSpan(ConstantSampler.of(true)); assertThat(traceContext.getTraceId().isEmpty()).isFalse(); assertThat(traceContext.getParentId().isEmpty()).isTrue(); @@ -287,7 +389,7 @@ void testRandomValue() { @Test void testSetSampled() { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asRootSpan(ConstantSampler.of(false)); assertThat(traceContext.isSampled()).isFalse(); traceContext.setRecorded(true); @@ -298,10 +400,10 @@ void testSetSampled() { @Test void testPropagateTransactionIdForUnsampledSpan() { - final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext rootContext = TraceContext.with64BitId(tracer); rootContext.asRootSpan(ConstantSampler.of(false)); - final TraceContext childContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext childContext = TraceContext.with64BitId(tracer); childContext.asChildOf(rootContext); verifyTraceContextContents(childContext.getOutgoingTraceParentTextHeader().toString(), @@ -314,10 +416,10 @@ void testPropagateTransactionIdForUnsampledSpan() { @Test void testPropagateSpanIdForSampledSpan() { - final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext rootContext = TraceContext.with64BitId(tracer); rootContext.asRootSpan(ConstantSampler.of(true)); - final TraceContext childContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext childContext = TraceContext.with64BitId(tracer); childContext.asChildOf(rootContext); verifyTraceContextContents(childContext.getOutgoingTraceParentTextHeader().toString(), @@ -386,24 +488,24 @@ void testInvalidHeader_invalidTotalLength() { } private void assertInvalid(String s) { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); assertThat(traceContext.asChildOf(s)).isFalse(); } private void assertInvalid(byte[] binaryHeader) { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); assertThat(traceContext.asChildOf(binaryHeader)).isFalse(); } private void assertValid(String s) { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); assertThat(traceContext.asChildOf(s)).isTrue(); verifyTraceContextContents(traceContext.getOutgoingTraceParentTextHeader().toString(), traceContext.getTraceId().toString(), traceContext.getId().toString(), "00", s.substring(53, 55)); } private void assertValid(byte[] binaryHeader) { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); assertThat(traceContext.asChildOf(binaryHeader)).isTrue(); Map binaryHeaderMap = new HashMap<>(); traceContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); @@ -412,7 +514,7 @@ private void assertValid(byte[] binaryHeader) { } private byte[] convertToBinary(String textHeader) { - final TraceContext traceContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asChildOf(textHeader); Map binaryHeaderMap = new HashMap<>(); traceContext.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java index 55faae2e92..74ecdc3d6a 100644 --- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientRedirectInstrumentation.java @@ -65,7 +65,7 @@ private static void onAfterExecute(@Advice.Argument(value = 0) HttpRequest origi TextHeaderGetter headerGetter = headerGetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class); if (headerGetter != null && headerSetter != null) { if (TraceContext.containsTraceContextTextHeaders(original, headerGetter) && !TraceContext.containsTraceContextTextHeaders(redirect, headerGetter)) { - TraceContext.copyTextHeaders(original, headerGetter, redirect, headerSetter); + TraceContext.copyTraceContextTextHeaders(original, headerGetter, redirect, headerSetter); } } } diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java index 240ebe3526..fb62bd31dc 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsInstrumentationHelper.java @@ -38,7 +38,7 @@ public interface JmsInstrumentationHelper { /** * In some cases, dashes are not allowed in JMS Message property names */ - String JMS_TRACE_PARENT_PROPERTY = TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME.replace('-', '_'); + String JMS_TRACE_PARENT_PROPERTY = TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME.replace('-', '_'); /** * When the agent computes a destination name instead of using the default queue name- it should be passed as a diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java index 6ee6a187ad..7a755350ff 100644 --- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java @@ -66,7 +66,7 @@ public String getFirstHeader(String headerName, Message message) { @Nonnull private String jmsifyHeaderName(String headerName) { - if (headerName.equals(TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME)) { + if (headerName.equals(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)) { // replacing with the JMS equivalent headerName = JMS_TRACE_PARENT_PROPERTY; } diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java index 990d7e59c9..b09c4ee4f4 100644 --- a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java +++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java @@ -424,7 +424,7 @@ void testToIdOfExtractedContext() { // -------------------------------------------------------- TextMap textMapExtractAdapter = new TextMapAdapter(Map.of( - TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, + TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-" + traceIdString + "-" + parentIdString + "-01", "User-Agent", "curl")); //ExternalProcessSpanContext @@ -443,7 +443,7 @@ void testInjectExtract() { Span otSpan = apmTracer.buildSpan("span") .asChildOf(apmTracer.extract(Format.Builtin.TEXT_MAP, new TextMapAdapter(Map.of( - TraceContext.TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-" + traceId + "-" + parentId + "-01", + TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-" + traceId + "-" + parentId + "-01", "User-Agent", "curl")))) .start(); final Scope scope = apmTracer.activateSpan(otSpan); diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 3f4620d18b..f6adaa0292 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -83,6 +83,7 @@ Click on a key to get more information. ** <> ** <> ** <> +** <> * <> ** <> ** <> @@ -767,13 +768,36 @@ NOTE: this option can only be set via system properties, environment variables o | `_AGENT_HOME_/elasticapm.properties` | String | false |============ - [options="header"] |============ | Java System Properties | Property file | Environment | `elastic.apm.config_file` | `config_file` | `ELASTIC_APM_CONFIG_FILE` |============ +// This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter +[float] +[[config-use-elastic-traceparent-header]] +==== `use_elastic_traceparent_header` (added[1.13.0]) + +To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.). +These headers (`traceparent` and `tracestate`) are defined in the +https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification. + +When this setting is `true`, the agent will also add the header `elastic-apm-traceparent` +for backwards compatibility with older versions of Elastic APM agents. + +[options="header"] +|============ +| Default | Type | Dynamic +| `true` | Boolean | true +|============ + +[options="header"] +|============ +| Java System Properties | Property file | Environment +| `elastic.apm.use_elastic_traceparent_header` | `use_elastic_traceparent_header` | `ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER` +|============ + [[config-http]] === HTTP configuration options // This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter @@ -1938,6 +1962,20 @@ The default unit for this option is `ms` # # config_file=_AGENT_HOME_/elasticapm.properties +# To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent +# adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.). +# These headers (`traceparent` and `tracestate`) are defined in the +# https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification. +# +# When this setting is `true`, the agent will also add the header `elastic-apm-traceparent` +# for backwards compatibility with older versions of Elastic APM agents. +# +# This setting can be changed at runtime +# Type: Boolean +# Default value: true +# +# use_elastic_traceparent_header=true + ############################################ # HTTP # ############################################ From 2629e094abbcb122354f79760c02bd2dc928c9da Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Sun, 9 Feb 2020 15:12:31 +0200 Subject: [PATCH 10/16] Update docs --- docs/configuration.asciidoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index f6adaa0292..494af1a468 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -768,6 +768,7 @@ NOTE: this option can only be set via system properties, environment variables o | `_AGENT_HOME_/elasticapm.properties` | String | false |============ + [options="header"] |============ | Java System Properties | Property file | Environment @@ -779,19 +780,22 @@ NOTE: this option can only be set via system properties, environment variables o [[config-use-elastic-traceparent-header]] ==== `use_elastic_traceparent_header` (added[1.13.0]) -To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.). +To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent +adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.). These headers (`traceparent` and `tracestate`) are defined in the https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification. When this setting is `true`, the agent will also add the header `elastic-apm-traceparent` for backwards compatibility with older versions of Elastic APM agents. + [options="header"] |============ | Default | Type | Dynamic | `true` | Boolean | true |============ + [options="header"] |============ | Java System Properties | Property file | Environment From 95719a6a41b8ee2863640437b21d6e7b51066f06 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Sun, 9 Feb 2020 22:48:22 +0200 Subject: [PATCH 11/16] Implement tracestate header --- .../configuration/CoreConfiguration.java | 16 +++ .../agent/impl/transaction/HeaderGetter.java | 2 +- .../transaction/TextTracestateAppender.java | 71 ++++++++++++ .../agent/impl/transaction/TraceContext.java | 56 +++++++++- .../apm/agent/impl/MultiValueMapAccessor.java | 65 +++++++++++ .../TextTracestateAppenderTest.java | 104 ++++++++++++++++++ .../impl/transaction/TraceContextTest.java | 100 +++++++++++++---- .../agent/servlet/ServletInstrumentation.java | 2 +- ...r.java => ServletRequestHeaderGetter.java} | 6 +- .../ServletTransactionCreationHelperImpl.java | 2 +- 10 files changed, 397 insertions(+), 27 deletions(-) create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/impl/MultiValueMapAccessor.java create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppenderTest.java rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/{RequestHeaderGetter.java => ServletRequestHeaderGetter.java} (87%) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index 924c6bfc6f..a6d4ddda24 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -499,6 +499,18 @@ public class CoreConfiguration extends ConfigurationOptionProvider { .dynamic(true) .buildWithDefault(true); + private final ConfigurationOption tracestateHeaderSizeLimit = ConfigurationOption.integerOption() + .key("tracestate_header_size_limit") + .tags("added[1.13.0]") + .configurationCategory(CORE_CATEGORY) + .description("The agent delegates the `tracestate` header, if received, as defined in the\n" + + "https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification.\n" + + "\n" + + "This setting limits the size of the `tracestate` header.") + .dynamic(true) + .tags("internal") + .buildWithDefault(4096); + public boolean isActive() { return active.get(); } @@ -623,6 +635,10 @@ public boolean isElasticTraceparentHeaderEnabled() { return useElasticTraceparentHeader.get(); } + public int getTracestateSizeLimit() { + return tracestateHeaderSizeLimit.get(); + } + /* * Makes sure to not initialize ConfigurationOption, which would initialize the logger */ diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java index f1f27e19e2..6cfe4834df 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.java @@ -48,6 +48,6 @@ public interface HeaderGetter { void forEach(String headerName, C carrier, S state, HeaderConsumer consumer); interface HeaderConsumer { - void accept(T headerValue, S state); + void accept(@Nullable T headerValue, S state); } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java new file mode 100644 index 0000000000..a172ef3b77 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java @@ -0,0 +1,71 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +import javax.annotation.Nullable; + +class TextTracestateAppender implements HeaderGetter.HeaderConsumer { + + private static TextTracestateAppender INSTANCE = new TextTracestateAppender(); + + static TextTracestateAppender instance() { + return INSTANCE; + } + + TextTracestateAppender() { + } + + @Override + public void accept(@Nullable String headerValue, TraceContext traceContext) { + if (headerValue == null) { + return; + } + // This means that the tracestate buffer will be allocated from pool only if tracestate headers exist + StringBuilder tracestateBuffer = traceContext.getTracestateBuffer(); + int tracestateSizeLimit = traceContext.coreConfiguration.getTracestateSizeLimit(); + appendTracestateHeaderValue(headerValue, tracestateBuffer, tracestateSizeLimit); + } + + void appendTracestateHeaderValue(String headerValue, StringBuilder tracestateBuffer, int tracestateSizeLimit) { + int endIndex = headerValue.length(); + // Check if adding comma and the entire header value will break size limit + if (tracestateBuffer.length() + endIndex + 1 > tracestateSizeLimit) { + // When trimming due to size limit, we must include complete entries + endIndex = 0; + for (int i = headerValue.length() - 1; i >= 0; i--) { + if (headerValue.charAt(i) == ',' && tracestateBuffer.length() + i < tracestateSizeLimit) { + endIndex = i; + break; + } + } + } + if (endIndex > 0) { + if (tracestateBuffer.length() > 0) { + tracestateBuffer.append(','); + } + tracestateBuffer.append(headerValue, 0, endIndex); + } + } +} diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index 6dca1e9b68..e537f927ff 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -27,8 +27,13 @@ import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.sampling.Sampler; +import co.elastic.apm.agent.objectpool.Allocator; +import co.elastic.apm.agent.objectpool.ObjectPool; +import co.elastic.apm.agent.objectpool.Resetter; +import co.elastic.apm.agent.objectpool.impl.QueueBasedObjectPool; import co.elastic.apm.agent.util.HexUtils; import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; +import org.jctools.queues.atomic.MpmcAtomicArrayQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,6 +81,7 @@ public class TraceContext extends TraceContextHolder { public static final String ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME = "elastic-apm-traceparent"; public static final String W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME = "traceparent"; + public static final String TRACESTATE_HEADER_NAME = "tracestate"; private static final int TEXT_HEADER_EXPECTED_LENGTH = 55; private static final int TEXT_HEADER_TRACE_ID_OFFSET = 3; private static final int TEXT_HEADER_PARENT_ID_OFFSET = 36; @@ -94,6 +100,20 @@ public class TraceContext extends TraceContextHolder { private static final int BINARY_FORMAT_FLAGS_OFFSET = 27; private static final byte BINARY_FORMAT_FLAGS_FIELD_ID = (byte) 0b0000_0010; + private static final ObjectPool tracestateBufferPool = QueueBasedObjectPool.of(new MpmcAtomicArrayQueue(128), false, + new Allocator() { + @Override + public StringBuilder createInstance() { + return new StringBuilder(); + } + }, + new Resetter() { + @Override + public void recycle(StringBuilder object) { + object.setLength(0); + } + }); + private static final Logger logger = LoggerFactory.getLogger(TraceContext.class); /** * Helps to reduce allocations by caching {@link WeakReference}s to {@link ClassLoader}s @@ -127,6 +147,11 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeade isValid = child.asChildOf(traceparent); } } + + if (isValid) { + traceContextHeaderGetter.forEach(TRACESTATE_HEADER_NAME, carrier, child, TextTracestateAppender.instance()); + } + return isValid; } }; @@ -169,6 +194,7 @@ public static boolean containsTraceContextTextHeaders(C carrier, TextHeaderG public static void removeTraceContextHeaders(C carrier, HeaderRemover headerRemover) { headerRemover.remove(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); headerRemover.remove(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + headerRemover.remove(TRACESTATE_HEADER_NAME, carrier); } public static void copyTraceContextTextHeaders(S source, TextHeaderGetter headerGetter, D destination, TextHeaderSetter headerSetter) { @@ -180,6 +206,11 @@ public static void copyTraceContextTextHeaders(S source, TextHeaderGetter if (elasticApmTraceParent != null) { headerSetter.setHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, elasticApmTraceParent, destination); } + // copying only the first tracestate header + String tracestate = headerGetter.getFirstHeader(TRACESTATE_HEADER_NAME, source); + if (tracestate != null) { + headerSetter.setHeader(TRACESTATE_HEADER_NAME, tracestate, destination); + } } // ???????1 -> maybe recorded @@ -196,7 +227,10 @@ public static void copyTraceContextTextHeaders(S source, TextHeaderGetter @Nullable private WeakReference applicationClassLoader; - private final CoreConfiguration coreConfiguration; + @Nullable + StringBuilder tracestateBuffer; + + final CoreConfiguration coreConfiguration; /** * Avoids clock drifts within a transaction. @@ -400,6 +434,10 @@ public void resetState() { clock.resetState(); serviceName = null; applicationClassLoader = null; + if (tracestateBuffer != null) { + tracestateBufferPool.recycle(tracestateBuffer); + tracestateBuffer = null; + } } /** @@ -488,6 +526,12 @@ public void setOutgoingTraceContextHeaders(C carrier, TextHeaderSetter he if (coreConfiguration.isElasticTraceparentHeaderEnabled()) { headerSetter.setHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); } + if (tracestateBuffer != null) { + String tracestateValue = tracestateBuffer.toString(); + if (!tracestateValue.isEmpty()) { + headerSetter.setHeader(TRACESTATE_HEADER_NAME, tracestateValue, carrier); + } + } } /** @@ -578,6 +622,9 @@ public void copyFrom(TraceContext other) { clock.init(other.clock); serviceName = other.serviceName; applicationClassLoader = other.applicationClassLoader; + if (other.tracestateBuffer != null) { + getTracestateBuffer().append(other.tracestateBuffer); + } onMutation(); } @@ -634,6 +681,13 @@ void setApplicationClassLoader(@Nullable ClassLoader classLoader) { } } + StringBuilder getTracestateBuffer() { + if (tracestateBuffer == null) { + tracestateBuffer = tracestateBufferPool.createInstance(); + } + return tracestateBuffer; + } + @Nullable public ClassLoader getApplicationClassLoader() { if (applicationClassLoader != null) { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/MultiValueMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/MultiValueMapAccessor.java new file mode 100644 index 0000000000..9caf32dc8c --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/MultiValueMapAccessor.java @@ -0,0 +1,65 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl; + +import co.elastic.apm.agent.impl.transaction.AbstractHeaderGetter; +import co.elastic.apm.agent.impl.transaction.HeaderRemover; +import co.elastic.apm.agent.impl.transaction.TextHeaderGetter; +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; + +import javax.annotation.Nullable; + +public class MultiValueMapAccessor extends AbstractHeaderGetter implements + TextHeaderGetter, TextHeaderSetter, HeaderRemover { + + public static final MultiValueMapAccessor INSTANCE = new MultiValueMapAccessor(); + + private MultiValueMapAccessor() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, PotentiallyMultiValuedMap headerMap) { + return headerMap.getFirst(headerName); + } + + @Override + public void forEach(String headerName, PotentiallyMultiValuedMap headerMap, S state, HeaderConsumer consumer) { + for (String headerValue : headerMap.getAll(headerName)) { + consumer.accept(headerValue, state); + } + } + + @Override + public void setHeader(String headerName, String headerValue, PotentiallyMultiValuedMap headerMap) { + headerMap.add(headerName, headerValue); + } + + @Override + public void remove(String headerName, PotentiallyMultiValuedMap headerMap) { + headerMap.removeIgnoreCase(headerName); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppenderTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppenderTest.java new file mode 100644 index 0000000000..3346d6ed70 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppenderTest.java @@ -0,0 +1,104 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + * #L% + */ +package co.elastic.apm.agent.impl.transaction; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TextTracestateAppenderTest { + + private TextTracestateAppender tracestateAppender; + private StringBuilder stringBuilder; + + @BeforeEach + void setUp() { + tracestateAppender = new TextTracestateAppender(); + stringBuilder = new StringBuilder(); + } + + @Test + void testSimpleAppend() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("three=four", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testOverflow() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("three=four", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("five=six", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("seven=eight", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testOverflowWithinFirstHeader() { + tracestateAppender.appendTracestateHeaderValue("one=two,three=four,five=six,seven=eight", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testOverflowWithinHeader() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("three=four,five=six", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("seven=eight,nine=ten", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testIncludeEmptyEntry() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("three=four, ,five=six", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("seven=eight,nine=ten", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four, "); + } + + @Test + void testMultipleOverflowValues() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("three=four,five=six,seven=eight", stringBuilder, 20); + tracestateAppender.appendTracestateHeaderValue("nine=ten", stringBuilder, 20); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testExactCutoffOnSeparator() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 18); + tracestateAppender.appendTracestateHeaderValue("three=four,five=six,seven=eight", stringBuilder, 18); + tracestateAppender.appendTracestateHeaderValue("nine=ten", stringBuilder, 18); + assertThat(stringBuilder.toString()).isEqualTo("one=two,three=four"); + } + + @Test + void testPickFittingValue() { + tracestateAppender.appendTracestateHeaderValue("one=two", stringBuilder, 17); + tracestateAppender.appendTracestateHeaderValue("three=four,five=six,seven=eight", stringBuilder, 17); + tracestateAppender.appendTracestateHeaderValue("nine=ten,eleven-twelve", stringBuilder, 17); + assertThat(stringBuilder.toString()).isEqualTo("one=two,nine=ten"); + } +} diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index af90872a4d..cb52628cf4 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -30,10 +30,12 @@ import co.elastic.apm.agent.impl.BinaryHeaderMapAccessor; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.ElasticApmTracerBuilder; +import co.elastic.apm.agent.impl.MultiValueMapAccessor; import co.elastic.apm.agent.impl.TextHeaderMapAccessor; import co.elastic.apm.agent.impl.sampling.ConstantSampler; import co.elastic.apm.agent.objectpool.TestObjectPoolFactory; import co.elastic.apm.agent.util.HexUtils; +import co.elastic.apm.agent.util.PotentiallyMultiValuedMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.stagemonitor.configuration.ConfigurationRegistry; @@ -171,31 +173,78 @@ void testElasticTraceparentHeaderDisabled() { @Test void testTraceContextHeadersRemoval() { - Map textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); - final TraceContext child = TraceContext.with64BitId(tracer); - assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); - Map outgoingHeaders = new HashMap<>(); - child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); - assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); - assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); - TraceContext.removeTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); - assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); - assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + Map headerMap = new HashMap<>(); + headerMap.put(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + headerMap.put(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + headerMap.put(TraceContext.TRACESTATE_HEADER_NAME, "foo=bar,baz=qux"); + TraceContext.removeTraceContextHeaders(headerMap, TextHeaderMapAccessor.INSTANCE); + assertThat(headerMap.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + assertThat(headerMap.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNull(); + assertThat(headerMap.get(TraceContext.TRACESTATE_HEADER_NAME)).isNull(); } @Test void testTraceContextHeadersCopy() { - Map textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + Map original = new HashMap<>(); + original.put(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + original.put(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + original.put(TraceContext.TRACESTATE_HEADER_NAME, "foo=bar,baz=qux"); + Map copy = new HashMap<>(); + TraceContext.copyTraceContextTextHeaders(original, TextHeaderMapAccessor.INSTANCE, copy, TextHeaderMapAccessor.INSTANCE); + assertThat(copy.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(copy.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(copy.get(TraceContext.TRACESTATE_HEADER_NAME)).isNotNull(); + } + + @Test + void testTracestateHeader() { + PotentiallyMultiValuedMap incomingHeaders = new PotentiallyMultiValuedMap(); + incomingHeaders.add("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + incomingHeaders.add("tracestate", "foo=bar"); + incomingHeaders.add("tracestate", "baz=qux,quux=quuz"); final TraceContext child = TraceContext.with64BitId(tracer); - assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, incomingHeaders, MultiValueMapAccessor.INSTANCE)).isTrue(); + assertThat(child.getTraceContext().getTraceId().toString()).isEqualTo("0af7651916cd43dd8448eb211c80319c"); + assertThat(child.getTraceContext().getParentId().toString()).isEqualTo("b9c7c989f97918e1"); + assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); + assertThat(child.isSampled()).isTrue(); + PotentiallyMultiValuedMap outgoingHeaders = new PotentiallyMultiValuedMap(); + child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, MultiValueMapAccessor.INSTANCE); + assertThat(outgoingHeaders.size()).isEqualTo(3); + assertThat(outgoingHeaders.getFirst(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.getAll(TraceContext.TRACESTATE_HEADER_NAME)).hasSize(1); + assertThat(outgoingHeaders.getFirst(TraceContext.TRACESTATE_HEADER_NAME)).isEqualTo("foo=bar,baz=qux,quux=quuz"); + } + + @Test + void testTracestateHeaderSizeLimit() { + when(config.getConfig(CoreConfiguration.class).getTracestateSizeLimit()).thenReturn(20); + PotentiallyMultiValuedMap incomingHeaders = new PotentiallyMultiValuedMap(); + incomingHeaders.add("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + incomingHeaders.add("tracestate", "foo=bar"); + incomingHeaders.add("tracestate", "baz=qux,quux=quuz"); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.getFromTraceContextTextHeaders().asChildOf(child, incomingHeaders, MultiValueMapAccessor.INSTANCE)).isTrue(); + PotentiallyMultiValuedMap outgoingHeaders = new PotentiallyMultiValuedMap(); + child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, MultiValueMapAccessor.INSTANCE); + assertThat(outgoingHeaders.size()).isEqualTo(3); + assertThat(outgoingHeaders.getFirst(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.getAll(TraceContext.TRACESTATE_HEADER_NAME)).hasSize(1); + assertThat(outgoingHeaders.getFirst(TraceContext.TRACESTATE_HEADER_NAME)).isEqualTo("foo=bar,baz=qux"); + } + + @Test + void testNoTracestateWhenInvalidTraceparentHeader() { + Map textHeaderMap = Map.of( + // one char too short trace ID + TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-d8448eb211c80319c0af7651916cd43-f97918e1b9c7c989-00", + TraceContext.TRACESTATE_HEADER_NAME, "foo=bar,baz=qux" + ); + final TraceContext child = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(child, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isFalse(); Map outgoingHeaders = new HashMap<>(); child.getTraceContext().setOutgoingTraceContextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE); - assertThat(outgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); - assertThat(outgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); - Map copyOfOutgoingHeaders = new HashMap<>(); - TraceContext.copyTraceContextTextHeaders(outgoingHeaders, TextHeaderMapAccessor.INSTANCE, copyOfOutgoingHeaders, TextHeaderMapAccessor.INSTANCE); - assertThat(copyOfOutgoingHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); - assertThat(copyOfOutgoingHeaders.get(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)).isNotNull(); + assertThat(outgoingHeaders.get(TraceContext.TRACESTATE_HEADER_NAME)).isNull(); } @Test @@ -319,8 +368,11 @@ void parseFromTraceParentHeader_notSampled() { void testResetState() { final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); + traceContext.getTracestateBuffer(); + assertThat(traceContext.tracestateBuffer).isNotNull(); traceContext.resetState(); assertThat(traceContext.getIncomingTraceParentHeader()).isEqualTo("00-00000000000000000000000000000000-0000000000000000-00"); + assertThat(traceContext.tracestateBuffer).isNull(); } @Test @@ -350,11 +402,16 @@ void testResetOutgoingBinaryHeader() { @Test void testCopyFrom() { + Map textHeaderMap = Map.of( + TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", + TraceContext.TRACESTATE_HEADER_NAME, "foo=bar,baz=qux" + ); final TraceContext first = TraceContext.with64BitId(tracer); - first.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(first, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); final TraceContext second = TraceContext.with64BitId(tracer); - second.asChildOf("00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(second, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); assertThat(first.getTraceId()).isNotEqualTo(second.getTraceId()); assertThat(first.getParentId()).isNotEqualTo(second.getParentId()); @@ -368,6 +425,7 @@ void testCopyFrom() { System.arraycopy(outgoingHeader, 0, firstOutgoingHeader, 0, outgoingHeader.length); second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isNotEqualTo(firstOutgoingHeader); + assertThat(second.tracestateBuffer).isNull(); second.copyFrom(first); assertThat(first.getTraceId()).isEqualTo(second.getTraceId()); @@ -376,6 +434,8 @@ void testCopyFrom() { assertThat(first.getOutgoingTraceParentTextHeader().toString()).isEqualTo(second.getOutgoingTraceParentTextHeader().toString()); second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isEqualTo(firstOutgoingHeader); + assertThat(second.tracestateBuffer).isNotNull(); + assertThat(second.tracestateBuffer.toString()).isEqualTo("foo=bar,baz=qux"); } @Test diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java index 3293349160..69ebc42ca8 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletInstrumentation.java @@ -69,7 +69,7 @@ public ServletInstrumentation(ElasticApmTracer tracer) { ServletApiAdvice.init(tracer); servletTransactionCreationHelperManager = HelperClassManager.ForSingleClassLoader.of(tracer, "co.elastic.apm.agent.servlet.helper.ServletTransactionCreationHelperImpl", - "co.elastic.apm.agent.servlet.helper.RequestHeaderGetter" + "co.elastic.apm.agent.servlet.helper.ServletRequestHeaderGetter" ); } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletRequestHeaderGetter.java similarity index 87% rename from apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java rename to apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletRequestHeaderGetter.java index 32a5156fc7..0bbb191a4a 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RequestHeaderGetter.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletRequestHeaderGetter.java @@ -30,11 +30,11 @@ import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; -class RequestHeaderGetter implements TextHeaderGetter { +class ServletRequestHeaderGetter implements TextHeaderGetter { - private static final RequestHeaderGetter INSTANCE = new RequestHeaderGetter(); + private static final ServletRequestHeaderGetter INSTANCE = new ServletRequestHeaderGetter(); - static RequestHeaderGetter getInstance() { + static ServletRequestHeaderGetter getInstance() { return INSTANCE; } diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java index 230139985a..858086d0e1 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelperImpl.java @@ -58,7 +58,7 @@ public Transaction createAndActivateTransaction(HttpServletRequest request) { // only create a transaction if there is not already one tracer.currentTransaction() == null && !isExcluded(request.getServletPath(), request.getPathInfo(), request.getHeader("User-Agent"))) { - return tracer.startChildTransaction(request, RequestHeaderGetter.getInstance(), request.getServletContext().getClassLoader()).activate(); + return tracer.startChildTransaction(request, ServletRequestHeaderGetter.getInstance(), request.getServletContext().getClassLoader()).activate(); } else { return null; } From 63275083d3b0cd3973636c13082d2211cb718675 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Mon, 10 Feb 2020 09:52:20 +0200 Subject: [PATCH 12/16] Remove binary header when requested --- .../apm/agent/impl/transaction/TraceContext.java | 1 + .../apm/agent/impl/BinaryHeaderMapAccessor.java | 9 ++++++++- .../apm/agent/impl/transaction/TraceContextTest.java | 11 ++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index e537f927ff..b95e0af1d0 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -195,6 +195,7 @@ public static void removeTraceContextHeaders(C carrier, HeaderRemover hea headerRemover.remove(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); headerRemover.remove(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); headerRemover.remove(TRACESTATE_HEADER_NAME, carrier); + headerRemover.remove(TRACE_PARENT_BINARY_HEADER_NAME, carrier); } public static void copyTraceContextTextHeaders(S source, TextHeaderGetter headerGetter, D destination, TextHeaderSetter headerSetter) { diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java index 036c3ef4f8..31947f7805 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java @@ -26,13 +26,15 @@ import co.elastic.apm.agent.impl.transaction.BinaryHeaderGetter; import co.elastic.apm.agent.impl.transaction.BinaryHeaderSetter; +import co.elastic.apm.agent.impl.transaction.HeaderRemover; import co.elastic.apm.agent.impl.transaction.TraceContext; import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; -public class BinaryHeaderMapAccessor implements BinaryHeaderGetter>, BinaryHeaderSetter> { +public class BinaryHeaderMapAccessor implements BinaryHeaderGetter>, + BinaryHeaderSetter>, HeaderRemover> { public static final BinaryHeaderMapAccessor INSTANCE = new BinaryHeaderMapAccessor(); @@ -66,4 +68,9 @@ public byte[] getFixedLengthByteArray(String headerName, int length) { public void setHeader(String headerName, byte[] headerValue, Map headerMap) { headerMap.put(headerName, headerValue); } + + @Override + public void remove(String headerName, Map headerMap) { + headerMap.remove(headerName); + } } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index cb52628cf4..a99e2d2d09 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.Test; import org.stagemonitor.configuration.ConfigurationRegistry; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -172,7 +173,7 @@ void testElasticTraceparentHeaderDisabled() { } @Test - void testTraceContextHeadersRemoval() { + void testTraceContextTextHeadersRemoval() { Map headerMap = new HashMap<>(); headerMap.put(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); headerMap.put(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"); @@ -183,6 +184,14 @@ void testTraceContextHeadersRemoval() { assertThat(headerMap.get(TraceContext.TRACESTATE_HEADER_NAME)).isNull(); } + @Test + void testTraceContextBinaryHeadersRemoval() { + Map headerMap = new HashMap<>(); + headerMap.put(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01".getBytes(StandardCharsets.UTF_8)); + TraceContext.removeTraceContextHeaders(headerMap, BinaryHeaderMapAccessor.INSTANCE); + assertThat(headerMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isNull(); + } + @Test void testTraceContextHeadersCopy() { Map original = new HashMap<>(); From e11b568b9986db7823c0494afdad9b4b3be819d7 Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Mon, 10 Feb 2020 10:09:48 +0200 Subject: [PATCH 13/16] Remove debug prints --- .../co/elastic/apm/agent/servlet/ServletApiAdvice.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java index b7eb930aa8..2220d83ec1 100644 --- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java @@ -110,18 +110,8 @@ public static void onEnterServletService(@Advice.Argument(0) ServletRequest serv ServletInstrumentation.ServletTransactionCreationHelper helper = ServletInstrumentation.servletTransactionCreationHelperManager.getForClassLoaderOfClass(HttpServletRequest.class); if (helper != null) { - System.out.println("Helper is not null"); transaction = helper.createAndActivateTransaction(request); - if (transaction != null) { - System.out.println("transaction.getTraceContext().isSampled() = " + transaction.getTraceContext().isSampled()); - System.out.println("transaction.isNoop() = " + transaction.isNoop()); - } else { - System.out.println("Transaction is null"); - } - } else { - System.out.println("Helper is null"); } - new Exception().printStackTrace(); } if (transaction == null) { From d62f17c19e8fb3fd4af07783950c094c7354feb2 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 13 Feb 2020 17:37:03 +0100 Subject: [PATCH 14/16] Add tracestate to ArrayList(1) instead of appending to pooled StringBuilder --- .../transaction/TextTracestateAppender.java | 32 ++++++--- .../agent/impl/transaction/TraceContext.java | 67 +++++++------------ .../impl/transaction/TraceContextTest.java | 24 +++++-- 3 files changed, 64 insertions(+), 59 deletions(-) diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java index a172ef3b77..2e30cab3b3 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java @@ -24,11 +24,13 @@ */ package co.elastic.apm.agent.impl.transaction; -import javax.annotation.Nullable; +import java.util.List; -class TextTracestateAppender implements HeaderGetter.HeaderConsumer { +class TextTracestateAppender { + + private static final TextTracestateAppender INSTANCE = new TextTracestateAppender(); + private final ThreadLocal tracestateBuffer = new ThreadLocal(); - private static TextTracestateAppender INSTANCE = new TextTracestateAppender(); static TextTracestateAppender instance() { return INSTANCE; @@ -37,15 +39,12 @@ static TextTracestateAppender instance() { TextTracestateAppender() { } - @Override - public void accept(@Nullable String headerValue, TraceContext traceContext) { - if (headerValue == null) { - return; + public String join(List tracestate, int tracestateSizeLimit) { + StringBuilder buffer = getTracestateBuffer(); + for (int i = 0, size = tracestate.size(); i < size; i++) { + appendTracestateHeaderValue(tracestate.get(i), buffer, tracestateSizeLimit); } - // This means that the tracestate buffer will be allocated from pool only if tracestate headers exist - StringBuilder tracestateBuffer = traceContext.getTracestateBuffer(); - int tracestateSizeLimit = traceContext.coreConfiguration.getTracestateSizeLimit(); - appendTracestateHeaderValue(headerValue, tracestateBuffer, tracestateSizeLimit); + return buffer.toString(); } void appendTracestateHeaderValue(String headerValue, StringBuilder tracestateBuffer, int tracestateSizeLimit) { @@ -68,4 +67,15 @@ void appendTracestateHeaderValue(String headerValue, StringBuilder tracestateBuf tracestateBuffer.append(headerValue, 0, endIndex); } } + + private StringBuilder getTracestateBuffer() { + StringBuilder buffer = tracestateBuffer.get(); + if (buffer == null) { + buffer = new StringBuilder(); + tracestateBuffer.set(buffer); + } else { + buffer.setLength(0); + } + return buffer; + } } diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java index b95e0af1d0..e12f42f4c3 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java @@ -27,19 +27,16 @@ import co.elastic.apm.agent.configuration.CoreConfiguration; import co.elastic.apm.agent.impl.ElasticApmTracer; import co.elastic.apm.agent.impl.sampling.Sampler; -import co.elastic.apm.agent.objectpool.Allocator; -import co.elastic.apm.agent.objectpool.ObjectPool; -import co.elastic.apm.agent.objectpool.Resetter; -import co.elastic.apm.agent.objectpool.impl.QueueBasedObjectPool; import co.elastic.apm.agent.util.HexUtils; import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; -import org.jctools.queues.atomic.MpmcAtomicArrayQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; /** @@ -100,20 +97,6 @@ public class TraceContext extends TraceContextHolder { private static final int BINARY_FORMAT_FLAGS_OFFSET = 27; private static final byte BINARY_FORMAT_FLAGS_FIELD_ID = (byte) 0b0000_0010; - private static final ObjectPool tracestateBufferPool = QueueBasedObjectPool.of(new MpmcAtomicArrayQueue(128), false, - new Allocator() { - @Override - public StringBuilder createInstance() { - return new StringBuilder(); - } - }, - new Resetter() { - @Override - public void recycle(StringBuilder object) { - object.setLength(0); - } - }); - private static final Logger logger = LoggerFactory.getLogger(TraceContext.class); /** * Helps to reduce allocations by caching {@link WeakReference}s to {@link ClassLoader}s @@ -126,6 +109,14 @@ public boolean asChildOf(TraceContext child, TraceContextHolder parent) { return true; } }; + private static final HeaderGetter.HeaderConsumer TRACESTATE_HEADER_CONSUMER = new HeaderGetter.HeaderConsumer() { + @Override + public void accept(@Nullable String tracestateHeaderValue, TraceContext state) { + if (tracestateHeaderValue != null) { + state.addTracestate(tracestateHeaderValue); + } + } + }; private static final ChildContextCreatorTwoArg FROM_TRACE_CONTEXT_TEXT_HEADERS = new ChildContextCreatorTwoArg>() { @Override @@ -149,7 +140,8 @@ public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeade } if (isValid) { - traceContextHeaderGetter.forEach(TRACESTATE_HEADER_NAME, carrier, child, TextTracestateAppender.instance()); + // as per spec, the tracestate header can be multi-valued + traceContextHeaderGetter.forEach(TRACESTATE_HEADER_NAME, carrier, child, TRACESTATE_HEADER_CONSUMER); } return isValid; @@ -227,9 +219,7 @@ public static void copyTraceContextTextHeaders(S source, TextHeaderGetter // weakly referencing to avoid CL leaks in case of leaked spans @Nullable private WeakReference applicationClassLoader; - - @Nullable - StringBuilder tracestateBuffer; + private final List tracestate = new ArrayList<>(1); final CoreConfiguration coreConfiguration; @@ -419,6 +409,7 @@ public void asChildOf(TraceContext parent) { clock.init(parent.clock); serviceName = parent.serviceName; applicationClassLoader = parent.applicationClassLoader; + tracestate.addAll(parent.tracestate); onMutation(); } @@ -435,10 +426,7 @@ public void resetState() { clock.resetState(); serviceName = null; applicationClassLoader = null; - if (tracestateBuffer != null) { - tracestateBufferPool.recycle(tracestateBuffer); - tracestateBuffer = null; - } + tracestate.clear(); } /** @@ -527,11 +515,11 @@ public void setOutgoingTraceContextHeaders(C carrier, TextHeaderSetter he if (coreConfiguration.isElasticTraceparentHeaderEnabled()) { headerSetter.setHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, getOutgoingTraceParentTextHeader().toString(), carrier); } - if (tracestateBuffer != null) { - String tracestateValue = tracestateBuffer.toString(); - if (!tracestateValue.isEmpty()) { - headerSetter.setHeader(TRACESTATE_HEADER_NAME, tracestateValue, carrier); - } + if (tracestate.size() == 1) { + headerSetter.setHeader(TRACESTATE_HEADER_NAME, tracestate.get(0), carrier); + } else if (!tracestate.isEmpty()) { + String tracestateHeaderValue = TextTracestateAppender.instance().join(tracestate, coreConfiguration.getTracestateSizeLimit()); + headerSetter.setHeader(TRACESTATE_HEADER_NAME, tracestateHeaderValue, carrier); } } @@ -623,9 +611,7 @@ public void copyFrom(TraceContext other) { clock.init(other.clock); serviceName = other.serviceName; applicationClassLoader = other.applicationClassLoader; - if (other.tracestateBuffer != null) { - getTracestateBuffer().append(other.tracestateBuffer); - } + tracestate.addAll(other.tracestate); onMutation(); } @@ -682,13 +668,6 @@ void setApplicationClassLoader(@Nullable ClassLoader classLoader) { } } - StringBuilder getTracestateBuffer() { - if (tracestateBuffer == null) { - tracestateBuffer = tracestateBufferPool.createInstance(); - } - return tracestateBuffer; - } - @Nullable public ClassLoader getApplicationClassLoader() { if (applicationClassLoader != null) { @@ -698,6 +677,10 @@ public ClassLoader getApplicationClassLoader() { } } + public void addTracestate(String headerValue) { + tracestate.add(headerValue); + } + public interface ChildContextCreator { boolean asChildOf(TraceContext child, T parent); } diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java index a99e2d2d09..d3cb59b2d5 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TraceContextTest.java @@ -377,11 +377,8 @@ void parseFromTraceParentHeader_notSampled() { void testResetState() { final TraceContext traceContext = TraceContext.with64BitId(tracer); traceContext.asChildOf("00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"); - traceContext.getTracestateBuffer(); - assertThat(traceContext.tracestateBuffer).isNotNull(); traceContext.resetState(); assertThat(traceContext.getIncomingTraceParentHeader()).isEqualTo("00-00000000000000000000000000000000-0000000000000000-00"); - assertThat(traceContext.tracestateBuffer).isNull(); } @Test @@ -434,7 +431,6 @@ void testCopyFrom() { System.arraycopy(outgoingHeader, 0, firstOutgoingHeader, 0, outgoingHeader.length); second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isNotEqualTo(firstOutgoingHeader); - assertThat(second.tracestateBuffer).isNull(); second.copyFrom(first); assertThat(first.getTraceId()).isEqualTo(second.getTraceId()); @@ -443,8 +439,24 @@ void testCopyFrom() { assertThat(first.getOutgoingTraceParentTextHeader().toString()).isEqualTo(second.getOutgoingTraceParentTextHeader().toString()); second.setOutgoingTraceContextHeaders(binaryHeaderMap, BinaryHeaderMapAccessor.INSTANCE); assertThat(binaryHeaderMap.get(TraceContext.TRACE_PARENT_BINARY_HEADER_NAME)).isEqualTo(firstOutgoingHeader); - assertThat(second.tracestateBuffer).isNotNull(); - assertThat(second.tracestateBuffer.toString()).isEqualTo("foo=bar,baz=qux"); + } + + @Test + void testAsChildOfHeaders() { + Map textHeaderMap = Map.of( + TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01", + TraceContext.TRACESTATE_HEADER_NAME, "foo=bar,baz=qux" + ); + final TraceContext first = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(first, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + + final TraceContext second = TraceContext.with64BitId(tracer); + second.asChildOf(first); + + HashMap textHeaders = new HashMap<>(); + second.setOutgoingTraceContextHeaders(textHeaders, TextHeaderMapAccessor.INSTANCE); + assertThat(textHeaders.get(TraceContext.TRACESTATE_HEADER_NAME)).isEqualTo("foo=bar,baz=qux"); + assertThat(textHeaders.get(TraceContext.W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME)).startsWith("00-0af7651916cd43dd8448eb211c80319c-"); } @Test From 568c15dbedc77200389f3d0e588ba969e375d4ac Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 14 Feb 2020 10:06:19 +0100 Subject: [PATCH 15/16] Fix HeadersExtractorBridge.Extractor --- .../agent/plugin/api/HeadersExtractorBridge.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java index 24981ff02d..bba54ad8cf 100644 --- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -42,17 +42,17 @@ public class HeadersExtractorBridge implements TextHeaderGetter Date: Fri, 14 Feb 2020 15:08:40 +0100 Subject: [PATCH 16/16] Add changelog, adjust added version to 1.14.0 --- CHANGELOG.asciidoc | 15 +++++++++++++++ .../agent/configuration/CoreConfiguration.java | 4 ++-- .../apm/agent/impl/ElasticApmTracerTest.java | 2 +- docs/configuration.asciidoc | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 560ae0b452..1ae91e62ae 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -19,6 +19,21 @@ endif::[] //// === Unreleased +[[release-notes-1.14.0]] +==== 1.14.0 - YYYY/MM/DD + +[float] +===== Breaking changes + +[float] +===== Features +* Support for the official https://www.w3.org/TR/trace-context[W3C] `traceparent` and `tracestate` headers. + + The agent now accepts both the `elastic-apm-traceparent` and the official `traceparent` header. + By default, it sends both headers on outgoing requests, + unless <> is set to false. + +[float] +===== Bug fixes [[release-notes-1.x]] === Java Agent version 1.x diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index e14d97c02e..2c2262c627 100644 --- a/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java @@ -499,7 +499,7 @@ public class CoreConfiguration extends ConfigurationOptionProvider { private final ConfigurationOption useElasticTraceparentHeader = ConfigurationOption.booleanOption() .key("use_elastic_traceparent_header") - .tags("added[1.13.0]") + .tags("added[1.14.0]") .configurationCategory(CORE_CATEGORY) .description("To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent\n" + "adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.).\n" + @@ -513,7 +513,7 @@ public class CoreConfiguration extends ConfigurationOptionProvider { private final ConfigurationOption tracestateHeaderSizeLimit = ConfigurationOption.integerOption() .key("tracestate_header_size_limit") - .tags("added[1.13.0]") + .tags("added[1.14.0]") .configurationCategory(CORE_CATEGORY) .description("The agent delegates the `tracestate` header, if received, as defined in the\n" + "https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification.\n" + diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index 890977c2c4..4cd49bae06 100644 --- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java @@ -493,7 +493,7 @@ void testOverrideServiceNameExplicitServiceName() { @Test void testCaptureExceptionAndGetErrorId() { - Transaction transaction = tracerImpl.startTransaction(TraceContext.asRoot(), null, getClass().getClassLoader()); + Transaction transaction = tracerImpl.startRootTransaction(getClass().getClassLoader()); String errorId = transaction.captureExceptionAndGetErrorId(new Exception("test")); transaction.end(); diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index d8f5b84fed..98cd4bbef1 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -793,7 +793,7 @@ NOTE: this option can only be set via system properties, environment variables o // This file is auto generated. Please make your changes in *Configuration.java (for example CoreConfiguration.java) and execute ConfigurationExporter [float] [[config-use-elastic-traceparent-header]] -==== `use_elastic_traceparent_header` (added[1.13.0]) +==== `use_elastic_traceparent_header` (added[1.14.0]) To enable {apm-overview-ref-v}/distributed-tracing.html[distributed tracing], the agent adds trace context headers to outgoing requests (like HTTP requests, Kafka records, gRPC requests etc.).