Last active
March 25, 2024 16:16
-
-
Save chemicL/0e0d8e95e28414f0ecb769a5b8ca326e to your computer and use it in GitHub Desktop.
Automatic context propagation with MDC using Project Reactor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ... | |
dependencies { | |
implementation 'io.micrometer:context-propagation:1.0.6' | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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(); | |
} | |
} |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.