fix: resolve app's SLF4J MDC via reflection to bypass shade relocation
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:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user