fix: resolve app's SLF4J MDC via reflection to bypass shade relocation
All checks were successful
CI / build (push) Successful in 5m12s
CI / docker (push) Successful in 42s
CI / deploy (push) Successful in 25s

The shade plugin rewrites the string literal "org.slf4j.MDC" to
"com.cameleer.shaded.slf4j.MDC", so MdcEnricher was writing to the
agent's private MDC instance — invisible to the app's Logback/Log4j2.

Use String.join() to build the class name at runtime (not a compile-time
constant), load the real org.slf4j.MDC from the thread context
classloader via reflection, and invoke put/remove through Method handles.

Live-tested: cameleer.exchangeId, cameleer.applicationId, and
cameleer.instanceId now appear in captured log MDC maps. onCompletion
logs show cameleer.exchangeId pointing to the parent exchange.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-15 23:55:57 +02:00
parent 66ab1baec6
commit 84272268f2

View File

@@ -1,37 +1,97 @@
package com.cameleer.core.logging;
import org.slf4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
/**
* Injects Cameleer-specific MDC keys during exchange processing.
* These keys are captured by the Cameleer appender alongside Camel's
* built-in MDC keys (camel.exchangeId, camel.routeId, camel.correlationId).
*
* <p>When running inside the agent's shaded JAR, {@code org.slf4j.MDC} is relocated
* to {@code com.cameleer.shaded.slf4j.MDC} — a separate class with its own
* thread-local storage. The app's Logback/Log4j2 reads from the original
* {@code org.slf4j.MDC}, so keys set via the relocated class are invisible.
*
* <p>This class uses the thread context classloader (which is the app classloader)
* to access the real {@code org.slf4j.MDC} class and set keys there.
*/
public final class MdcEnricher {
private static final Logger LOG = LoggerFactory.getLogger(MdcEnricher.class);
static final String KEY_APPLICATION_ID = "cameleer.applicationId";
static final String KEY_INSTANCE_ID = "cameleer.instanceId";
static final String KEY_CORRELATION_ID = "cameleer.correlationId";
static final String KEY_EXCHANGE_ID = "cameleer.exchangeId";
private static volatile Method mdcPut;
private static volatile Method mdcRemove;
private static volatile boolean resolved;
private MdcEnricher() {}
public static void setExchangeContext(String applicationId, String instanceId,
String correlationId, String exchangeId) {
MDC.put(KEY_APPLICATION_ID, applicationId);
MDC.put(KEY_INSTANCE_ID, instanceId);
resolve();
put(KEY_APPLICATION_ID, applicationId);
put(KEY_INSTANCE_ID, instanceId);
if (correlationId != null) {
MDC.put(KEY_CORRELATION_ID, correlationId);
put(KEY_CORRELATION_ID, correlationId);
}
if (exchangeId != null) {
MDC.put(KEY_EXCHANGE_ID, exchangeId);
put(KEY_EXCHANGE_ID, exchangeId);
}
}
public static void clearExchangeContext() {
MDC.remove(KEY_APPLICATION_ID);
MDC.remove(KEY_INSTANCE_ID);
MDC.remove(KEY_CORRELATION_ID);
MDC.remove(KEY_EXCHANGE_ID);
resolve();
remove(KEY_APPLICATION_ID);
remove(KEY_INSTANCE_ID);
remove(KEY_CORRELATION_ID);
remove(KEY_EXCHANGE_ID);
}
private static void put(String key, String value) {
if (mdcPut != null) {
try {
mdcPut.invoke(null, key, value);
} catch (Exception e) {
LOG.trace("Cameleer: MDC put failed for key {}", key, e);
}
}
}
private static void remove(String key) {
if (mdcRemove != null) {
try {
mdcRemove.invoke(null, key);
} catch (Exception e) {
LOG.trace("Cameleer: MDC remove failed for key {}", key, e);
}
}
}
private static void resolve() {
if (resolved) {
return;
}
resolved = true;
try {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl == null) {
tccl = ClassLoader.getSystemClassLoader();
}
// Build class name at runtime to prevent shade plugin from rewriting
// "org.slf4j.MDC" → "com.cameleer.shaded.slf4j.MDC"
String mdcClassName = String.join(".", "org", "slf4j", "MDC");
Class<?> mdcClass = tccl.loadClass(mdcClassName);
mdcPut = mdcClass.getMethod("put", String.class, String.class);
mdcRemove = mdcClass.getMethod("remove", String.class);
} catch (Exception e) {
LOG.warn("Cameleer: Could not resolve app MDC class, MDC enrichment disabled: {}", e.getMessage());
}
}
}