-
-
Save chemicL/0e0d8e95e28414f0ecb769a5b8ca326e to your computer and use it in GitHub Desktop.
// ... | |
dependencies { | |
implementation 'io.micrometer:context-propagation:1.0.6' | |
// ... | |
} |
<configuration> | |
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> | |
<encoder> | |
<pattern> | |
%d{HH:mm:ss.SSS} [%thread] [key=%X{key}] %-5level %logger{36} - %msg%n | |
</pattern> | |
</encoder> | |
</appender> | |
<logger name="reactor" level="info"/> | |
<root level="info"> | |
<appender-ref ref="stdout"/> | |
</root> | |
</configuration> |
@Test | |
void testMDC() { | |
Logger log = LoggerFactory.getLogger("test"); | |
Hooks.enableAutomaticContextPropagation(); | |
// To deal with the entire MDC (if we ensured no third-party code modifies it): | |
// ContextRegistry.getInstance().registerThreadLocalAccessor(new MdcAccessor()); | |
// To deal with an individual key in the MDC: | |
ContextRegistry.getInstance().registerThreadLocalAccessor( | |
"key", | |
() -> MDC.get("key"), | |
value -> MDC.put("key", value), | |
() -> MDC.remove("key")); | |
MDC.put("key", "Hello"); | |
Mono.just("Delayed") | |
.delayElement(Duration.ofMillis(10)) | |
.doOnNext(log::info) | |
// It is vital to capture MDC contents into Reactor's context | |
// as it is the source of truth for propagating ThreadLocals | |
//.contextCapture() // Can be skipped since reactor-core:3.5.7 | |
.block(); | |
} | |
// This implementation modifies the entire MDC. | |
// If any other code (especially third-party libraries) interacts with the MDC | |
// it would collide and the results will be corrupted. | |
// The best strategy is to pick specific keys instead of working on the entire MDC map. | |
static class MdcAccessor implements ThreadLocalAccessor<Map<String, String>> { | |
static final String KEY = "mdc"; | |
@Override | |
public Object key() { | |
return KEY; | |
} | |
@Override | |
public Map<String, String> getValue() { | |
return MDC.getCopyOfContextMap(); | |
} | |
@Override | |
public void setValue(Map<String, String> value) { | |
MDC.setContextMap(value); | |
} | |
@Override | |
public void reset() { | |
MDC.clear(); | |
} | |
} |
This is exactly what I have done but I am bit worried how many times the "lambdas" for getting and setting are called (I debugged it and it is a lot)
Yes, it is quite a few, but it's required to guarantee proper cleanup and not leaking the state to other tasks on the event loop. You can measure the performance impact on the real scenario and decide if you want to refactor the code to directly interact with the ContextView
and extract the identifies to explicitly pass them along to the log statements or if this automation is a price worth paying.
I'll do! 👍 Btw. I've started wondering if Hooks.enableAutomaticContextPropagation();
may have impact on reactive transaction propagation...
be more specific please :)
@leakin185
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Hooks.html#enableAutomaticContextPropagation--
Please make sure you're using the latest version :) reactor-core is now 3.5.10, and this feature was made available since 3.5.3. If it's missing, probably you're using an older version of the library.
I updated the gist to reflect the best practices.
@imosapatryk so you mean you have some key of your own, say
"X"
and when you doMDC.getCopyOfContextMap()
then the map containstraceId
andspanId
, but not"X"
? Have you registered an accessor for"X"
? Like this:Micrometer actually does not deal with
MDC
, it's the tracing instrumentation library, e.g. brave, for which a Micrometer bridge can be added, e.g.micrometer-tracing-bridge-brave
. And it works with its particular keys (traceId, spanId), which are a derivative of instantiating a span into existence. So as far as I understand, yes, you do need the accessor.