Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for setting the application class loader when starting a transaction via the public api #2369

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ endif::[]
* When the `MANIFEST.MF` of the main jar contains the `Implementation-Version` attribute, it is used as the default service version (except for application servers) - {pull}1922[#1922]
* Added support for overwritting the service version per classloader - {pull}1726[#1726]
* Support for the Java LDAP client - {pull}2355[#2355]
* Added support for setting the application class loader when starting a transaction via the public api - {pull}2369[#2369]

[float]
===== Bug fixes
Expand Down
50 changes: 45 additions & 5 deletions apm-agent-api/src/main/java/co/elastic/apm/api/ElasticApm.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,23 @@ public class ElasticApm {
*/
@Nonnull
public static Transaction startTransaction() {
Object transaction = doStartTransaction();
return startTransaction(null);
}

/**
* Similar to {@link ElasticApm#startTransaction()} but with a specific application class loader.
*
* @param classLoader the application class loader
* @return the started transaction.
* @since 1.29.0
*/
@Nonnull
public static Transaction startTransaction(@Nullable ClassLoader classLoader) {
Object transaction = doStartTransaction(classLoader);
return transaction != null ? new TransactionImpl(transaction) : NoopTransaction.INSTANCE;
}

private static Object doStartTransaction() {
private static Object doStartTransaction(ClassLoader classLoader) {
// co.elastic.apm.api.ElasticApmInstrumentation.StartTransactionInstrumentation.doStartTransaction
return null;
}
Expand Down Expand Up @@ -117,7 +129,20 @@ private static Object doStartTransaction() {
*/
@Nonnull
public static Transaction startTransactionWithRemoteParent(final HeaderExtractor headerExtractor) {
return startTransactionWithRemoteParent(headerExtractor, null);
return startTransactionWithRemoteParent(headerExtractor, null, null);
}

/**
* Similar to {@link ElasticApm#startTransactionWithRemoteParent(HeaderExtractor)} but with a specific application class loader.
*
* @param headerExtractor a function which receives a header name and returns the fist header with that name
* @param classLoader the application class loader
* @return the started transaction
* @since 1.29.0
*/
@Nonnull
public static Transaction startTransactionWithRemoteParent(final HeaderExtractor headerExtractor, @Nullable ClassLoader classLoader) {
return startTransactionWithRemoteParent(headerExtractor, null, classLoader);
}

/**
Expand Down Expand Up @@ -156,13 +181,28 @@ public static Transaction startTransactionWithRemoteParent(final HeaderExtractor
*/
@Nonnull
public static Transaction startTransactionWithRemoteParent(HeaderExtractor headerExtractor, HeadersExtractor headersExtractor) {
return startTransactionWithRemoteParent(headerExtractor, headersExtractor, null);
}

/**
* Similar to {@link ElasticApm#startTransactionWithRemoteParent(HeaderExtractor, HeadersExtractor)} but with a specific application class loader.
*
* @param headerExtractor a function which receives a header name and returns the fist header with that name
* @param headersExtractor a function which receives a header name and returns all headers with that name
* @param classLoader the application class loader
* @return the started transaction
* @since 1.29.0
*/
@Nonnull
public static Transaction startTransactionWithRemoteParent(HeaderExtractor headerExtractor, HeadersExtractor headersExtractor, @Nullable ClassLoader classLoader) {
Object transaction = doStartTransactionWithRemoteParentFunction(ApiMethodHandles.GET_FIRST_HEADER, headerExtractor,
ApiMethodHandles.GET_ALL_HEADERS, headersExtractor);
ApiMethodHandles.GET_ALL_HEADERS, headersExtractor, classLoader);
return transaction != null ? new TransactionImpl(transaction) : NoopTransaction.INSTANCE;
}

private static Object doStartTransactionWithRemoteParentFunction(MethodHandle getFirstHeader, HeaderExtractor headerExtractor,
MethodHandle getAllHeaders, HeadersExtractor headersExtractor) {
MethodHandle getAllHeaders, HeadersExtractor headersExtractor,
ClassLoader classLoader) {
// co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation.StartTransactionWithRemoteParentInstrumentation
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public static class AdviceClass {
@Nullable
@Advice.AssignReturned.ToReturned
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static Object doStartTransaction(@Advice.Origin Class<?> clazz) {
Transaction transaction = tracer.startRootTransaction(clazz.getClassLoader());
public static Object doStartTransaction(@Advice.Origin Class<?> clazz, @Advice.Argument(value = 0, optional = true) @Nullable ClassLoader classLoader) {
Transaction transaction = tracer.startRootTransaction(classLoader != null ? classLoader : clazz.getClassLoader());
if (transaction != null) {
transaction.setFrameworkName(Utils.FRAMEWORK_NAME);
}
Expand All @@ -85,16 +85,19 @@ public static Object 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) {
@Advice.Argument(3) @Nullable Object headersExtractor,
@Advice.Argument(value = 4, optional = true) @Nullable ClassLoader classLoader) {
ClassLoader initiatingClassLoader = classLoader != null ? classLoader : clazz.getClassLoader();

Transaction transaction = null;
if (headersExtractor != null) {
HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders);
transaction = tracer.startChildTransaction(HeadersExtractorBridge.Extractor.of(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader());
transaction = tracer.startChildTransaction(HeadersExtractorBridge.Extractor.of(headerExtractor, headersExtractor), headersExtractorBridge, initiatingClassLoader);
} else if (headerExtractor != null) {
HeaderExtractorBridge headersExtractorBridge = HeaderExtractorBridge.get(getFirstHeader);
transaction = tracer.startChildTransaction(headerExtractor, headersExtractorBridge, clazz.getClassLoader());
transaction = tracer.startChildTransaction(headerExtractor, headersExtractorBridge, initiatingClassLoader);
} else {
transaction = tracer.startRootTransaction(clazz.getClassLoader());
transaction = tracer.startRootTransaction(initiatingClassLoader);
}
if (transaction != null) {
transaction.setFrameworkName(Utils.FRAMEWORK_NAME);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import co.elastic.apm.agent.impl.TextHeaderMapAccessor;
import co.elastic.apm.agent.impl.TracerInternalApiUtils;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.TraceContext;
import co.elastic.apm.agent.pluginapi.ElasticApmApiInstrumentation;
import org.junit.jupiter.api.Test;

import java.util.Collections;
Expand All @@ -33,6 +35,9 @@

class ElasticApmApiInstrumentationTest extends AbstractApiTest {

private static ClassLoader cl = new ClassLoader("Test", ElasticApmApiInstrumentation.class.getClassLoader()) {
};

@Test
void testCreateTransaction() {
Transaction transaction = ElasticApm.startTransaction();
Expand All @@ -41,6 +46,15 @@ void testCreateTransaction() {
transaction.end();
}

@Test
void testCreateTransactionWithApplicationClassLoader() {
Transaction transaction = ElasticApm.startTransaction(cl);
assertThat(transaction).isNotSameAs(NoopTransaction.INSTANCE);
assertApplicationClassLoader(transaction, cl);
assertThat(ElasticApm.currentTransaction()).isSameAs(NoopTransaction.INSTANCE);
transaction.end();
}

@Test
void testNoCurrentTransaction() {
assertThat(ElasticApm.currentTransaction()).isSameAs(NoopTransaction.INSTANCE);
Expand Down Expand Up @@ -270,6 +284,19 @@ void testTransactionWithRemoteParentFunction() {
parent.end();
}

@Test
void testTransactionWithRemoteParentFunctionAndApplicationClassLoader() {
AbstractSpan<?> parent = tracer.startRootTransaction(null);
assertThat(parent).isNotNull();
Map<String, String> headerMap = new HashMap<>();
parent.propagateTraceContext(headerMap, TextHeaderMapAccessor.INSTANCE);
Transaction transaction = ElasticApm.startTransactionWithRemoteParent(headerMap::get, cl);
assertApplicationClassLoader(transaction, cl);
transaction.end();
assertThat(reporter.getFirstTransaction().isChildOf(parent)).isTrue();
parent.end();
}

@Test
void testTransactionWithRemoteParentFunctions() {
AbstractSpan<?> parent = tracer.startRootTransaction(null);
Expand All @@ -292,6 +319,19 @@ void testTransactionWithRemoteParentHeaders() {
parent.end();
}

@Test
void testTransactionWithRemoteParentHeadersAndApplicationClassLoader() {
AbstractSpan<?> parent = tracer.startRootTransaction(null);
assertThat(parent).isNotNull();
Map<String, String> headerMap = new HashMap<>();
parent.propagateTraceContext(headerMap, TextHeaderMapAccessor.INSTANCE);
Transaction transaction = ElasticApm.startTransactionWithRemoteParent(null, key -> Collections.singletonList(headerMap.get(key)), cl);
assertApplicationClassLoader(transaction, cl);
transaction.end();
assertThat(reporter.getFirstTransaction().isChildOf(parent)).isTrue();
parent.end();
}

@Test
void testTransactionWithRemoteParentNullFunction() {
ElasticApm.startTransactionWithRemoteParent(null).end();
Expand All @@ -300,7 +340,7 @@ void testTransactionWithRemoteParentNullFunction() {

@Test
void testTransactionWithRemoteParentNullFunctions() {
ElasticApm.startTransactionWithRemoteParent(null, null).end();
ElasticApm.startTransactionWithRemoteParent(null, (HeadersExtractor) null).end();
assertThat(reporter.getFirstTransaction().getTraceContext().isRoot()).isTrue();
}

Expand Down Expand Up @@ -360,4 +400,10 @@ void testFrameworkNameWithStartTransactionWithRemoteParent() {
ElasticApm.startTransactionWithRemoteParent(null).end();
assertThat(reporter.getFirstTransaction().getFrameworkName()).isEqualTo("API");
}

private static void assertApplicationClassLoader(Transaction transaction, ClassLoader applicationClassLoader) {
TransactionImpl transactionImpl = (TransactionImpl) transaction;
TraceContext traceContext = ((co.elastic.apm.agent.impl.transaction.Transaction) transactionImpl.span).getTraceContext();
assertThat(traceContext.getApplicationClassLoader()).isEqualTo(applicationClassLoader);
}
}
26 changes: 26 additions & 0 deletions docs/public-api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ NOTE: Transactions created via this method can not be retrieved by calling <<api
or <<api-current-transaction, `ElasticApm.currentTransaction()`>>.
See <<api-transaction-activate, `transaction.activate()`>> on how to achieve that.

[float]
[[api-start-transaction-classloader]]
==== `Transaction startTransaction(ClassLoader)` added[1.29.0]

Similar to <<api-start-transaction>> but with a specific application class loader.

* `classLoader`: the application class loader

[float]
[[api-start-transaction-with-remote-parent-header]]
Expand Down Expand Up @@ -158,6 +165,15 @@ public Response onIncomingRequest(Request request) throws Exception {

NOTE: If the protocol supports multi-value headers, use <<api-start-transaction-with-remote-parent-headers>>

[float]
[[api-start-transaction-with-remote-parent-header-classloader]]
==== `Transaction startTransaction(HeaderExtractor, ClassLoader)` added[1.29.0]

Similar to <<api-start-transaction-with-remote-parent-header>> but with a specific application class loader.

* `headerExtractor`: a functional interface which receives a header name and returns the first header with that name
* `classLoader`: the application class loader

[float]
[[api-start-transaction-with-remote-parent-headers]]
==== `Transaction startTransactionWithRemoteParent(HeaderExtractor, HeadersExtractor)` added[1.3.0]
Expand Down Expand Up @@ -192,6 +208,16 @@ public Response onIncomingRequest(Request request) throws Exception {

NOTE: If the protocol does not support multi-value headers, use <<api-start-transaction-with-remote-parent-header>>

[float]
[[api-start-transaction-with-remote-parent-headers-classloader]]
==== `Transaction startTransaction(HeaderExtractor, HeadersExtractor, ClassLoader)` added[1.29.0]

Similar to <<api-start-transaction-with-remote-parent-headers>> but with a specific application class loader.

* `headerExtractor`: a functional interface which receives a header name and returns the first header with that name
* `headersExtractor`: a functional interface which receives a header name and returns all headers with that name
* `classLoader`: the application class loader

//----------------------------
[float]
[[api-annotation]]
Expand Down