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/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/configuration/CoreConfiguration.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/configuration/CoreConfiguration.java index aa31d929cc..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 @@ -497,6 +497,32 @@ 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.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" + + "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); + + private final ConfigurationOption tracestateHeaderSizeLimit = ConfigurationOption.integerOption() + .key("tracestate_header_size_limit") + .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" + + "\n" + + "This setting limits the size of the `tracestate` header.") + .dynamic(true) + .tags("internal") + .buildWithDefault(4096); + public boolean isActive() { return active.get(); } @@ -617,6 +643,14 @@ public boolean isBreakdownMetricsEnabled() { return breakdownMetrics.get(); } + 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/ElasticApmTracer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java index e8e1919fec..15086f3850 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; @@ -150,60 +153,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 C 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 C 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 C 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 C 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()) { @@ -215,7 +277,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/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/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/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..6cfe4834df --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/HeaderGetter.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.impl.transaction; + +import javax.annotation.Nullable; + +public interface HeaderGetter { + + @Nullable + T getFirstHeader(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(@Nullable T headerValue, S state); + } +} 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/TextTracestateAppender.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java new file mode 100644 index 0000000000..2e30cab3b3 --- /dev/null +++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TextTracestateAppender.java @@ -0,0 +1,81 @@ +/*- + * #%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 java.util.List; + +class TextTracestateAppender { + + private static final TextTracestateAppender INSTANCE = new TextTracestateAppender(); + private final ThreadLocal tracestateBuffer = new ThreadLocal(); + + + static TextTracestateAppender instance() { + return INSTANCE; + } + + TextTracestateAppender() { + } + + 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); + } + return buffer.toString(); + } + + 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); + } + } + + 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 731cbe6318..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 @@ -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; @@ -34,6 +35,8 @@ 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; /** @@ -70,10 +73,12 @@ * 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"; + 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; @@ -104,25 +109,58 @@ public boolean asChildOf(TraceContext child, TraceContextHolder parent) { return true; } }; - private static final ChildContextCreator FROM_TRACEPARENT_TEXT_HEADER = new ChildContextCreator() { + private static final HeaderGetter.HeaderConsumer TRACESTATE_HEADER_CONSUMER = new HeaderGetter.HeaderConsumer() { @Override - public boolean asChildOf(TraceContext child, @Nullable String traceparent) { - if (traceparent != null) { - return child.asChildOf(traceparent); + public void accept(@Nullable String tracestateHeaderValue, TraceContext state) { + if (tracestateHeaderValue != null) { + state.addTracestate(tracestateHeaderValue); } - 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_TEXT_HEADERS = + new ChildContextCreatorTwoArg>() { + @Override + public boolean asChildOf(TraceContext child, @Nullable Object carrier, TextHeaderGetter traceContextHeaderGetter) { + if (carrier == null) { + return false; + } + + boolean isValid = false; + String traceparent = traceContextHeaderGetter.getFirstHeader(ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier); + if (traceparent != null) { + isValid = child.asChildOf(traceparent); + } + + 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); + } + } + + if (isValid) { + // as per spec, the tracestate header can be multi-valued + traceContextHeaderGetter.forEach(TRACESTATE_HEADER_NAME, carrier, child, TRACESTATE_HEADER_CONSUMER); + } + + return isValid; } - return false; - } - }; - + }; + 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; + } + byte[] traceparent = traceContextHeaderGetter.getFirstHeader(TRACE_PARENT_BINARY_HEADER_NAME, carrier); + if (traceparent != null) { + return child.asChildOf(traceparent); + } + return false; + } + }; private static final ChildContextCreator FROM_ACTIVE = new ChildContextCreator() { @Override public boolean asChildOf(TraceContext child, ElasticApmTracer tracer) { @@ -140,6 +178,34 @@ public boolean asChildOf(TraceContext child, Object ignore) { return false; } }; + + public static boolean containsTraceContextTextHeaders(C carrier, TextHeaderGetter headerGetter) { + return headerGetter.getFirstHeader(W3C_TRACE_PARENT_TEXTUAL_HEADER_NAME, carrier) != null; + } + + 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); + headerRemover.remove(TRACE_PARENT_BINARY_HEADER_NAME, carrier); + } + + 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(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 // ???????0 -> not recorded private static final byte FLAG_RECORDED = 0b0000_0001; @@ -153,6 +219,9 @@ public boolean asChildOf(TraceContext child, Object ignore) { // weakly referencing to avoid CL leaks in case of leaked spans @Nullable private WeakReference applicationClassLoader; + private final List tracestate = new ArrayList<>(1); + + final CoreConfiguration coreConfiguration; /** * Avoids clock drifts within a transaction. @@ -165,6 +234,7 @@ public boolean asChildOf(TraceContext child, Object ignore) { private TraceContext(ElasticApmTracer tracer, Id id) { super(tracer); + coreConfiguration = tracer.getConfig(CoreConfiguration.class); this.id = id; } @@ -183,18 +253,21 @@ 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; + @SuppressWarnings("unchecked") + public static ChildContextCreatorTwoArg> getFromTraceContextTextHeaders() { + return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_TEXT_HEADERS; } - public static ChildContextCreator fromTraceparentBinaryHeader() { - return FROM_TRACEPARENT_BINARY_HEADER; + @SuppressWarnings("unchecked") + public static ChildContextCreatorTwoArg> getFromTraceContextBinaryHeaders() { + return (ChildContextCreatorTwoArg>) FROM_TRACE_CONTEXT_BINARY_HEADERS; } public static ChildContextCreator fromActive() { @@ -209,7 +282,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 +333,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)); } @@ -336,6 +409,7 @@ public void asChildOf(TraceContext parent) { clock.init(parent.clock); serviceName = parent.serviceName; applicationClassLoader = parent.applicationClassLoader; + tracestate.addAll(parent.tracestate); onMutation(); } @@ -352,6 +426,7 @@ public void resetState() { clock.resetState(); serviceName = null; applicationClassLoader = null; + tracestate.clear(); } /** @@ -428,10 +503,52 @@ 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(C carrier, TextHeaderSetter headerSetter) { + 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); + } + 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); + } + } + + /** + * 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(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.", + 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 +573,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; @@ -494,6 +611,7 @@ public void copyFrom(TraceContext other) { clock.init(other.clock); serviceName = other.serviceName; applicationClassLoader = other.applicationClassLoader; + tracestate.addAll(other.tracestate); onMutation(); } @@ -559,10 +677,18 @@ public ClassLoader getApplicationClassLoader() { } } + public void addTracestate(String headerValue) { + tracestate.add(headerValue); + } + 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..31947f7805 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/BinaryHeaderMapAccessor.java @@ -0,0 +1,76 @@ +/*- + * #%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.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>, HeaderRemover> { + + 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); + } + + @Override + public void forEach(String headerName, Map carrier, S state, HeaderConsumer consumer) { + byte[] headerValue = carrier.get(headerName); + if (headerValue != null) { + consumer.accept(headerValue, state); + } + } + + @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); + } + + @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/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java index 0bd5bb16b4..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 @@ -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.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(); 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,13 +487,13 @@ 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(); } @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/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/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..5cb6d1c954 --- /dev/null +++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/TextHeaderMapAccessor.java @@ -0,0 +1,58 @@ +/*- + * #%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 javax.annotation.Nullable; +import java.util.Map; + +public class TextHeaderMapAccessor extends AbstractHeaderGetter> implements + TextHeaderGetter>, TextHeaderSetter>, HeaderRemover> { + + public static final TextHeaderMapAccessor INSTANCE = new TextHeaderMapAccessor(); + + private TextHeaderMapAccessor() { + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Map headerMap) { + return headerMap.get(headerName); + } + + @Override + 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/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 6d666b885f..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 @@ -24,25 +24,50 @@ */ 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.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; +import java.nio.charset.StandardCharsets; +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 final byte[] outgoingBinaryTraceContext = new byte[TraceContext.BINARY_FORMAT_EXPECTED_LENGTH]; + 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 * 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,18 +76,19 @@ 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; - final TraceContext child = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - assertThat(TraceContext.fromTraceparentHeader().asChildOf(child, parentHeader)).isTrue(); + 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"); assertThat(child.getTraceContext().getId()).isNotEqualTo(child.getTraceContext().getParentId()); assertThat(child.isSampled()).isEqualTo(isSampled); // 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 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(); 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()); @@ -70,7 +96,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()); @@ -93,22 +119,196 @@ 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 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"); + 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 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<>(); + 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, 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.TRACESTATE_HEADER_NAME)).isNull(); + } + @Test void testBinaryHeaderSizeEnforcement() { - final String parentHeader = "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(); + 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>() { + @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 testLongerBinaryHeader() { - final String parentHeader = "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(); + void testBinaryHeaderCaching() { + 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(); + 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 testBinaryHeader_CachingDisabled() { + 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 + 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, @@ -137,33 +337,36 @@ 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(); 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 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(); 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); } @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(); @@ -172,7 +375,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"); @@ -180,7 +383,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); @@ -188,48 +391,77 @@ 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); + 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); + 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"); + 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(); + + textHeaderMap = Map.of(TraceContext.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME, "00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); + final TraceContext second = TraceContext.with64BitId(tracer); + assertThat(TraceContext.>getFromTraceContextTextHeaders().asChildOf(second, textHeaderMap, TextHeaderMapAccessor.INSTANCE)).isTrue(); + + 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); + } - final TraceContext other = TraceContext.with64BitId(mock(ElasticApmTracer.class)); - other.asChildOf("00-8448ebb9c7c989f97918e11916cd43dd-211c80319c0af765-00"); + @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(); - 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); + final TraceContext second = TraceContext.with64BitId(tracer); + second.asChildOf(first); - 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); + 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 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(); @@ -238,7 +470,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); @@ -248,33 +480,35 @@ void testSetSampled() { } @Test - void testPropagateTransactionIdForUnsampledSpan_TextFormat() { - final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + void testPropagateTransactionIdForUnsampledSpan() { + 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(), 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() { - final TraceContext rootContext = TraceContext.with64BitId(mock(ElasticApmTracer.class)); + void testPropagateSpanIdForSampledSpan() { + 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(), 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 @@ -335,35 +569,37 @@ 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(); - 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)); + final TraceContext traceContext = TraceContext.with64BitId(tracer); 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..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 @@ -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.copyTraceContextTextHeaders(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..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 @@ -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; @@ -34,13 +36,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 +54,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 +70,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, 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); } } @@ -123,9 +133,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..04ec912c9a --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/helper/RequestHeaderAccessor.java @@ -0,0 +1,59 @@ +/*- + * #%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; + +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; + } + + @Override + 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); + } + } + } +} 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 ec6be7cc34..736ec05ecf 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; @@ -314,10 +313,10 @@ 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()); + 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/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..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 @@ -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,16 @@ 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()); - } 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()); - } + if (headersExtractor != null) { + HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders); + 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()); } 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..a4c8a8209c --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderExtractorBridge.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.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; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; + +@VisibleForAdvice +public class HeaderExtractorBridge extends AbstractHeaderGetter implements TextHeaderGetter { + + private static final Logger logger = LoggerFactory.getLogger(HeaderExtractorBridge.class); + + @Nullable + private static HeaderExtractorBridge INSTANCE; + + private final MethodHandle getFirstHeaderMethod; + + private HeaderExtractorBridge(MethodHandle getFirstHeaderMethod) { + this.getFirstHeaderMethod = getFirstHeaderMethod; + } + + public static HeaderExtractorBridge get(MethodHandle getFirstHeaderMethod) { + if (INSTANCE == null) { + INSTANCE = new HeaderExtractorBridge(getFirstHeaderMethod); + } + return INSTANCE; + } + + @Nullable + @Override + public String getFirstHeader(String headerName, Object carrier) { + String value = null; + try { + value = (String) getFirstHeaderMethod.invoke(carrier, headerName); + } catch (Throwable throwable) { + logger.error("Failed to extract trace context headers", throwable); + } + return value; + } + +} 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..e459f3aa1c --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeaderInjectorBridge.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.plugin.api; + +import co.elastic.apm.agent.bci.VisibleForAdvice; +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); + + @Nullable + private static HeaderInjectorBridge INSTANCE; + + @VisibleForAdvice + public static HeaderInjectorBridge get(MethodHandle addHeaderMethod) { + if (INSTANCE == null) { + INSTANCE = new HeaderInjectorBridge(addHeaderMethod); + } + return INSTANCE; + } + + private final MethodHandle addHeaderMethod; + + private HeaderInjectorBridge(MethodHandle addHeaderMethod) { + this.addHeaderMethod = addHeaderMethod; + } + + @Override + public void setHeader(String headerName, String headerValue, Object carrier) { + try { + 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..bba54ad8cf --- /dev/null +++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/HeadersExtractorBridge.java @@ -0,0 +1,116 @@ +/*- + * #%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); + @Nullable + private static HeadersExtractorBridge INSTANCE; + + public static class Extractor { + + @Nullable + private final Object headerExtractor; + private final Object headersExtractor; + + private Extractor(@Nullable Object headerExtractor, Object headersExtractor) { + this.headerExtractor = headerExtractor; + this.headersExtractor = headersExtractor; + } + + public static Extractor of(@Nullable Object headerExtractor, Object headersExtractor) { + return new Extractor(headerExtractor, headersExtractor); + } + } + + private final MethodHandle getFirstHeaderMethod; + private final MethodHandle getAllHeadersMethod; + + public static HeadersExtractorBridge get(MethodHandle getFirstHeaderMethod, MethodHandle getAllHeadersMethod) { + if (INSTANCE == null) { + INSTANCE = new HeadersExtractorBridge(getFirstHeaderMethod, getAllHeadersMethod); + } + return INSTANCE; + } + + private HeadersExtractorBridge(MethodHandle getFirstHeaderMethod, MethodHandle getAllHeadersMethod) { + this.getFirstHeaderMethod = getFirstHeaderMethod; + this.getAllHeadersMethod = getAllHeadersMethod; + } + + @Nullable + @Override + 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 = getValues(headerName, carrier); + if (headers != null) { + Iterator iterator = headers.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } + } + } + return null; + } + + @Override + 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 + values = (Iterable) getAllHeadersMethod.invoke(carrier.headersExtractor, 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 bb57a6bd3a..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 @@ -26,10 +26,11 @@ import co.elastic.apm.agent.bci.ElasticApmAgent; 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.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; @@ -56,6 +57,11 @@ 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<>(); @@ -65,6 +71,18 @@ public abstract class AbstractAsyncHttpClientInstrumentation extends ElasticApmI AsyncHandlerOnThrowableInstrumentation.class, AsyncHandlerOnStatusReceivedInstrumentation.class); + public AbstractAsyncHttpClientInstrumentation() { + if (headerSetterManager == null) { + synchronized (AbstractAsyncHandlerInstrumentation.class) { + if (headerSetterManager == null) { + headerSetterManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.asynchttpclient.helper.RequestHeaderSetter" + ); + } + } + } + } + @Override public Collection getInstrumentationGroupNames() { return Arrays.asList("http-client", "asynchttpclient"); @@ -93,7 +111,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); } } diff --git a/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java new file mode 100644 index 0000000000..edd644f917 --- /dev/null +++ b/apm-agent-plugins/apm-asynchttpclient-plugin/src/main/java/co/elastic/apm/agent/asynchttpclient/helper/RequestHeaderSetter.java @@ -0,0 +1,39 @@ +/*- + * #%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.TextHeaderSetter; +import org.asynchttpclient.Request; + +@VisibleForAdvice +public class RequestHeaderSetter implements TextHeaderSetter { + + @Override + public void setHeader(String headerName, String headerValue, Request request) { + request.getHeaders().set(headerName, headerValue); + } + +} 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/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 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..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 @@ -25,27 +25,34 @@ 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; -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 { @@ -65,7 +72,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 +137,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 +184,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 +200,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 +217,26 @@ 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); + } + + @Override + 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) { + List values = header.values(); + for (int i = 0, size = values.size(); i < size; i++) { + consumer.accept(values.get(i), state); + } + } + } + } + } } 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 b622a938c1..a272d837eb 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 @@ -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; @@ -77,7 +76,7 @@ public abstract class AbstractJdbcInstrumentationTest extends AbstractInstrument connection.createStatement().execute("CREATE TABLE ELASTIC_APM (FOO INT, BAR VARCHAR(255))"); connection.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (1, 'APM')"); connection.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (11, 'BEFORE')"); - 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 1d57094e82..bc8b0e44e2 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 @@ -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..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 @@ -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; @@ -37,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 @@ -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..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 @@ -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..7a755350ff --- /dev/null +++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessagePropertyAccessor.java @@ -0,0 +1,86 @@ +/*- + * #%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.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; +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 extends AbstractHeaderGetter 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.ELASTIC_TRACE_PARENT_TEXTUAL_HEADER_NAME)) { + // replacing with the JMS equivalent + headerName = JMS_TRACE_PARENT_PROPERTY; + } + return headerName; + } + + @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..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 @@ -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,8 @@ 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"); } } 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 c7fa16b229..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 @@ -24,7 +24,6 @@ */ 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; @@ -37,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 @@ -61,6 +61,7 @@ public String key() { * * @return the byte array representing the value */ + @Nullable public byte[] valueForSetting() { settingThreadId = Thread.currentThread().getId(); return value; @@ -74,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..a8a4a4a7c0 --- /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,108 @@ +/*- + * #%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.Map; + +@SuppressWarnings("rawtypes") +class KafkaRecordHeaderAccessor implements BinaryHeaderGetter, BinaryHeaderSetter, + HeaderRemover { + + public static final Logger logger = LoggerFactory.getLogger(KafkaRecordHeaderAccessor.class); + + private static final KafkaRecordHeaderAccessor INSTANCE = new KafkaRecordHeaderAccessor(); + + 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; + } + + @Override + public void forEach(String headerName, ConsumerRecord carrier, S state, HeaderConsumer consumer) { + for (Header header : carrier.headers().headers(headerName)) { + consumer.accept(header.value(), state); + } + } + + @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); + } + +} 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-log-correlation-plugin/src/test/java/co/elastic/apm/agent/mdc/MdcActivationListenerTest.java b/apm-agent-plugins/apm-log-correlation-plugin/src/test/java/co/elastic/apm/agent/mdc/MdcActivationListenerTest.java index 2c931a02a7..3d3ce144f8 100644 --- a/apm-agent-plugins/apm-log-correlation-plugin/src/test/java/co/elastic/apm/agent/mdc/MdcActivationListenerTest.java +++ b/apm-agent-plugins/apm-log-correlation-plugin/src/test/java/co/elastic/apm/agent/mdc/MdcActivationListenerTest.java @@ -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.apache.logging.log4j.ThreadContext; @@ -77,7 +76,7 @@ void tearDown() { @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); @@ -100,7 +99,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(); @@ -116,7 +115,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); @@ -135,7 +134,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(() -> { @@ -156,7 +155,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(() -> { @@ -177,7 +176,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(() -> { @@ -199,7 +198,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(() -> { @@ -222,7 +221,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(() -> { @@ -244,7 +243,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-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..b315d1ec1c --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttp3ClientInstrumentation.java @@ -0,0 +1,55 @@ +/*- + * #%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.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> headerSetterHelperManager; + + public AbstractOkHttp3ClientInstrumentation(ElasticApmTracer tracer) { + headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttp3RequestHeaderSetter" + ); + } + + @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..1362321841 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/AbstractOkHttpClientInstrumentation.java @@ -0,0 +1,55 @@ +/*- + * #%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.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> headerSetterHelperManager; + + public AbstractOkHttpClientInstrumentation(ElasticApmTracer tracer) { + headerSetterHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer, + "co.elastic.apm.agent.okhttp.OkHttpRequestHeaderSetter" + ); + } + + @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/OkHttp3RequestHeaderSetter.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderSetter.java new file mode 100644 index 0000000000..ef74770ab0 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3RequestHeaderSetter.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.okhttp; + +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import okhttp3.Request; + +public class OkHttp3RequestHeaderSetter implements TextHeaderSetter { + + @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/OkHttpRequestHeaderSetter.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderSetter.java new file mode 100644 index 0000000000..11088bf307 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpRequestHeaderSetter.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.okhttp; + +import co.elastic.apm.agent.impl.transaction.TextHeaderSetter; +import com.squareup.okhttp.Request; + +public class OkHttpRequestHeaderSetter implements TextHeaderSetter { + + @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..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; @@ -103,16 +102,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..c085ffe94e --- /dev/null +++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/OpenTracingTextMapBridge.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.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; + } + + @Override + 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 + 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..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 @@ -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,14 @@ 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) { + transaction = helper.createAndActivateTransaction(request); + } + } + 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..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 @@ -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,17 @@ 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.ServletRequestHeaderGetter" + ); } @Override @@ -87,4 +101,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/ServletRequestHeaderGetter.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletRequestHeaderGetter.java new file mode 100644 index 0000000000..0bbb191a4a --- /dev/null +++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletRequestHeaderGetter.java @@ -0,0 +1,55 @@ +/*- + * #%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; + +class ServletRequestHeaderGetter implements TextHeaderGetter { + + private static final ServletRequestHeaderGetter INSTANCE = new ServletRequestHeaderGetter(); + + static ServletRequestHeaderGetter getInstance() { + return INSTANCE; + } + + @Nullable + @Override + public String getFirstHeader(String headerName, HttpServletRequest request) { + return request.getHeader(headerName); + } + + @Override + 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-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..858086d0e1 --- /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, ServletRequestHeaderGetter.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-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..37cb413f18 --- /dev/null +++ b/apm-agent-plugins/apm-urlconnection-plugin/src/main/java/co/elastic/apm/agent/urlconnection/UrlConnectionPropertyAccessor.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.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; +import java.util.List; + +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); + } + + @Override + 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); + } + } + } +} 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..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 @@ -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; @@ -33,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; @@ -421,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 @@ -440,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); @@ -456,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(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; diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 8580367911..98cd4bbef1 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -85,6 +85,7 @@ Click on a key to get more information. ** <> ** <> ** <> +** <> * <> ** <> ** <> @@ -789,6 +790,33 @@ NOTE: this option can only be set via system properties, environment variables o | `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.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.). +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 @@ -1966,6 +1994,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 # ############################################