Skip to content

Instantly share code, notes, and snippets.

@chemicL
Last active March 25, 2024 16:16
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chemicL/0e0d8e95e28414f0ecb769a5b8ca326e to your computer and use it in GitHub Desktop.
Save chemicL/0e0d8e95e28414f0ecb769a5b8ca326e to your computer and use it in GitHub Desktop.
Automatic context propagation with MDC using Project Reactor
// ...
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();
}
}
@chemicL
Copy link
Author

chemicL commented Aug 29, 2023

@imosapatryk so you mean you have some key of your own, say "X" and when you do MDC.getCopyOfContextMap() then the map contains traceId and spanId, but not "X"? Have you registered an accessor for "X"? Like this:

ContextRegistry.getInstance().registerThreadLocalAccessor(
	"X",
	() -> MDC.get("X"),
	value -> MDC.put("X", value),
	() -> MDC.remove("X"));

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.

@imosapatryk
Copy link

imosapatryk commented Aug 29, 2023

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)

@chemicL
Copy link
Author

chemicL commented Aug 30, 2023

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.

@imosapatryk
Copy link

I'll do! 👍 Btw. I've started wondering if Hooks.enableAutomaticContextPropagation(); may have impact on reactive transaction propagation...

@chemicL
Copy link
Author

chemicL commented Aug 30, 2023

be more specific please :)

@chemicL
Copy link
Author

chemicL commented Oct 2, 2023

@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.

@chemicL
Copy link
Author

chemicL commented Oct 12, 2023

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