Skip to content

Commit a4f6c1c

Browse files
authored
Make log correlation GA (#742)
- Load the MDC with the application class loader, as opposed to the Thread's context classloader (fixes #720) - Remove span.id MDC entry This causes the most allocations as in contrast to transaction.id and trace.id, the String manifestation can't be reused. Other agents also don't add the span.id so it makes sense to align on that - Docs improvements - Mention slf4j in supported technologies page
1 parent 5526dff commit a4f6c1c

File tree

14 files changed

+154
-57
lines changed

14 files changed

+154
-57
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
* JDBC's `executeBatch` is not traced
1212
* Drops non-String labels when connected to APM Server < 6.7 to avoid validation errors (#687)
1313

14+
## Breaking changes
15+
* The log correlation feature does not add `span.id` to the MDC anymore but only `trace.id` and `transaction.id` (see #742).
16+
1417
# 1.7.0
1518

1619
## Features

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ActivationListener.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public interface ActivationListener {
5050
* That's why there is no {@link TraceContextHolder} parameter.
5151
* </p>
5252
*
53+
* @param deactivatedContext the context which has just been deactivated
5354
* @throws Throwable if there was an error while calling this method
5455
*/
55-
void afterDeactivate() throws Throwable;
56+
void afterDeactivate(TraceContextHolder<?> deactivatedContext) throws Throwable;
5657
}

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,19 @@ public boolean isWrappingAllowedOnThread() {
225225
* @param parent the parent of the transaction. May be a traceparent header.
226226
* @param sampler the {@link Sampler} instance which is responsible for determining the sampling decision if this is a root transaction
227227
* @param epochMicros the start timestamp
228-
* @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction
229-
* Used to determine the service name.
228+
* @param initiatingClassLoader the class loader corresponding to the service which initiated the creation of the transaction.
229+
* Used to determine the service name and to load application-scoped classes like the {@link org.slf4j.MDC},
230+
* for log correlation.
230231
* @param <T> the type of the parent. {@code String} in case of a traceparent header.
231232
* @return a transaction which is a child of the provided parent
232233
*/
233-
public <T> Transaction startTransaction(TraceContext.ChildContextCreator<T> childContextCreator, @Nullable T parent, Sampler sampler, long epochMicros, @Nullable ClassLoader initiatingClassLoader) {
234+
public <T> Transaction startTransaction(TraceContext.ChildContextCreator<T> childContextCreator, @Nullable T parent, Sampler sampler,
235+
long epochMicros, @Nullable ClassLoader initiatingClassLoader) {
234236
Transaction transaction;
235237
if (!coreConfiguration.isActive()) {
236238
transaction = noopTransaction();
237239
} else {
238-
transaction = createTransaction().start(childContextCreator, parent, epochMicros, sampler);
240+
transaction = createTransaction().start(childContextCreator, parent, epochMicros, sampler, initiatingClassLoader);
239241
}
240242
if (logger.isDebugEnabled()) {
241243
logger.debug("startTransaction {} {", transaction);

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContext.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import co.elastic.apm.agent.impl.ElasticApmTracer;
2828
import co.elastic.apm.agent.impl.sampling.Sampler;
2929
import co.elastic.apm.agent.util.HexUtils;
30+
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
3031
import org.slf4j.Logger;
3132
import org.slf4j.LoggerFactory;
3233

3334
import javax.annotation.Nullable;
35+
import java.lang.ref.WeakReference;
3436
import java.util.concurrent.Callable;
3537

3638
/**
@@ -56,6 +58,10 @@ public class TraceContext extends TraceContextHolder {
5658
private static final int PARENT_ID_OFFSET = 36;
5759
private static final int FLAGS_OFFSET = 53;
5860
private static final Logger logger = LoggerFactory.getLogger(TraceContext.class);
61+
/**
62+
* Helps to reduce allocations by caching {@link WeakReference}s to {@link ClassLoader}s
63+
*/
64+
private static final WeakConcurrentMap<ClassLoader, WeakReference<ClassLoader>> classLoaderWeakReferenceCache = new WeakConcurrentMap.WithInlinedExpunction<>();
5965
private static final ChildContextCreator<TraceContextHolder<?>> FROM_PARENT = new ChildContextCreator<TraceContextHolder<?>>() {
6066
@Override
6167
public boolean asChildOf(TraceContext child, TraceContextHolder<?> parent) {
@@ -101,6 +107,9 @@ public boolean asChildOf(TraceContext child, Object ignore) {
101107
private final StringBuilder outgoingHeader = new StringBuilder(TRACE_PARENT_LENGTH);
102108
private byte flags;
103109
private boolean discard;
110+
// weakly referencing to avoid CL leaks in case of leaked spans
111+
@Nullable
112+
private WeakReference<ClassLoader> applicationClassLoader;
104113

105114
/**
106115
* Avoids clock drifts within a transaction.
@@ -227,6 +236,7 @@ public void asChildOf(TraceContext parent) {
227236
id.setToRandomValue();
228237
clock.init(parent.clock);
229238
serviceName = parent.serviceName;
239+
applicationClassLoader = parent.applicationClassLoader;
230240
onMutation();
231241
}
232242

@@ -246,6 +256,7 @@ public void resetState() {
246256
discard = false;
247257
clock.resetState();
248258
serviceName = null;
259+
applicationClassLoader = null;
249260
}
250261

251262
/**
@@ -362,6 +373,7 @@ public void copyFrom(TraceContext other) {
362373
discard = other.discard;
363374
clock.init(other.clock);
364375
serviceName = other.serviceName;
376+
applicationClassLoader = other.applicationClassLoader;
365377
onMutation();
366378
}
367379

@@ -407,6 +419,26 @@ public Span createSpan(long epochMicros) {
407419
return tracer.startSpan(fromParent(), this, epochMicros);
408420
}
409421

422+
void setApplicationClassLoader(@Nullable ClassLoader classLoader) {
423+
if (classLoader != null) {
424+
WeakReference<ClassLoader> local = classLoaderWeakReferenceCache.get(classLoader);
425+
if (local == null) {
426+
local = new WeakReference<>(classLoader);
427+
classLoaderWeakReferenceCache.putIfAbsent(classLoader, local);
428+
}
429+
applicationClassLoader = local;
430+
}
431+
}
432+
433+
@Nullable
434+
public ClassLoader getApplicationClassLoader() {
435+
if (applicationClassLoader != null) {
436+
return applicationClassLoader.get();
437+
} else {
438+
return null;
439+
}
440+
}
441+
410442
public interface ChildContextCreator<T> {
411443
boolean asChildOf(TraceContext child, T parent);
412444
}

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/TraceContextHolder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ public T deactivate() {
113113
List<ActivationListener> activationListeners = tracer.getActivationListeners();
114114
for (int i = 0; i < activationListeners.size(); i++) {
115115
try {
116-
activationListeners.get(i).afterDeactivate();
116+
// `this` is guaranteed to not be recycled yet as the reference count is only decremented after this method has executed
117+
activationListeners.get(i).afterDeactivate(this);
117118
} catch (Error e) {
118119
throw e;
119120
} catch (Throwable t) {

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/transaction/Transaction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ public Transaction(ElasticApmTracer tracer) {
9494
super(tracer);
9595
}
9696

97-
public <T> Transaction start(TraceContext.ChildContextCreator<T> childContextCreator, @Nullable T parent, long epochMicros, Sampler sampler) {
97+
public <T> Transaction start(TraceContext.ChildContextCreator<T> childContextCreator, @Nullable T parent, long epochMicros,
98+
Sampler sampler, @Nullable ClassLoader initiatingClassLoader) {
99+
traceContext.setApplicationClassLoader(initiatingClassLoader);
98100
if (parent == null || !childContextCreator.asChildOf(traceContext, parent)) {
99101
traceContext.asRootSpan(sampler);
100102
}

apm-agent-core/src/main/java/co/elastic/apm/agent/logging/LoggingConfiguration.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,16 @@ public void onChange(ConfigurationOption<?> configurationOption, Level oldValue,
100100
private final ConfigurationOption<Boolean> logCorrelationEnabled = ConfigurationOption.booleanOption()
101101
.key("enable_log_correlation")
102102
.configurationCategory(LOGGING_CATEGORY)
103-
.description("A boolean specifying if the agent should integrate into SLF4J's MDC to enable trace-log correlation.\n" +
104-
"If set to `true`, the agent will set the `spanId` and `traceId` for the currently active spans and transactions to the MDC.\n" +
103+
.description("A boolean specifying if the agent should integrate into SLF4J's https://www.slf4j.org/api/org/slf4j/MDC.html[MDC] to enable trace-log correlation.\n" +
104+
"If set to `true`, the agent will set the `trace.id` and `transaction.id` for the currently active spans and transactions to the MDC.\n" +
105105
"You can then use the pattern format of your logging implementation to write the MDC values to your log file.\n" +
106+
"If you are using logback or log4j, add `%X` to the format to log all MDC values or `%X{trace.id}` to only log the trace id.\n" +
106107
"With the help of Filebeat and Logstash or an Elasticsearch ingest pipeline,\n" +
107108
"you can index your log files and correlate them with APM traces.\n" +
108109
"With this integration you can get all logs belonging to a particular trace and vice-versa:\n" +
109110
"for a specific log, see in which context it has been logged and which parameters the user provided. " +
110111
"\n" +
111-
"While it's allowed to enable this setting at runtime, you can't disable it without a restart.\n" +
112-
"\n" +
113-
"NOTE: This is an incubating feature and the MDC key names might change.")
112+
"While it's allowed to enable this setting at runtime, you can't disable it without a restart.")
114113
.dynamic(true)
115114
.addValidator(new ConfigurationOption.Validator<Boolean>() {
116115
@Override

apm-agent-core/src/test/java/co/elastic/apm/agent/TransactionUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
public class TransactionUtils {
3838

3939
public static void fillTransaction(Transaction t) {
40-
t.start(TraceContext.asRoot(), null, (long) 0, ConstantSampler.of(true));
40+
t.start(TraceContext.asRoot(), null, (long) 0, ConstantSampler.of(true), TransactionUtils.class.getClassLoader());
4141
t.setName("GET /api/types");
4242
t.withType("request");
4343
t.withResult("success");

apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/TransactionPayloadJsonSchemaTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
import co.elastic.apm.agent.MockTracer;
2828
import co.elastic.apm.agent.TransactionUtils;
29-
import co.elastic.apm.agent.impl.ElasticApmTracer;
3029
import co.elastic.apm.agent.impl.sampling.ConstantSampler;
3130
import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration;
3231
import co.elastic.apm.agent.impl.transaction.Span;
@@ -81,7 +80,7 @@ private TransactionPayload createPayloadWithRequiredValues() {
8180

8281
private Transaction createTransactionWithRequiredValues() {
8382
Transaction t = new Transaction(MockTracer.create());
84-
t.start(TraceContext.asRoot(), null, (long) 0, ConstantSampler.of(true));
83+
t.start(TraceContext.asRoot(), null, (long) 0, ConstantSampler.of(true), getClass().getClassLoader());
8584
t.withType("type");
8685
t.getContext().getRequest().withMethod("GET");
8786
t.getContext().getRequest().getUrl().appendToFull("http://localhost:8080/foo/bar");

apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,25 @@ public CreateSpanInstrumentation() {
7171
}
7272

7373
@Advice.OnMethodExit(suppress = Throwable.class)
74-
public static void createSpan(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC)
75-
@Nullable TraceContext parentContext,
74+
public static void createSpan(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext parentContext,
75+
@Advice.Origin Class<?> spanBuilderClass,
7676
@Advice.FieldValue(value = "tags") Map<String, Object> tags,
7777
@Advice.FieldValue(value = "operationName") String operationName,
7878
@Advice.FieldValue(value = "microseconds") long microseconds,
7979
@Advice.Argument(1) @Nullable Iterable<Map.Entry<String, String>> baggage,
8080
@Advice.Return(readOnly = false) Object span) {
81-
span = doCreateTransactionOrSpan(parentContext, tags, operationName, microseconds, baggage);
81+
span = doCreateTransactionOrSpan(parentContext, tags, operationName, microseconds, baggage, spanBuilderClass.getClassLoader());
8282
}
8383

8484
@Nullable
8585
@VisibleForAdvice
8686
public static AbstractSpan<?> doCreateTransactionOrSpan(@Nullable TraceContext parentContext,
8787
Map<String, Object> tags,
8888
String operationName, long microseconds,
89-
@Nullable Iterable<Map.Entry<String, String>> baggage) {
89+
@Nullable Iterable<Map.Entry<String, String>> baggage, ClassLoader applicationClassLoader) {
9090
if (tracer != null) {
9191
if (parentContext == null) {
92-
return createTransaction(tags, operationName, microseconds, baggage, tracer, TraceContext.class.getClassLoader());
92+
return createTransaction(tags, operationName, microseconds, baggage, tracer, applicationClassLoader);
9393
} else {
9494
if (microseconds >= 0) {
9595
return tracer.startSpan(TraceContext.fromParent(), parentContext, microseconds);

0 commit comments

Comments
 (0)