-
Type: New Feature
-
Resolution: Fixed
-
Priority: Major - P3
-
Affects Version/s: None
-
Component/s: Monitoring
-
Needed
A MongoDB driver can be used from various programming models (imperative where calls remain on the same thread, reactive where code is thread-agnostic). At the same time, the underlying application using the MongoDB driver can be interested in end-to-end tracing of outgoing remote calls. To properly construct traces, each participating component must be able to contribute their individual activity in the form of a span (one or more spans represent a full trace) and be able to attach its span to the overall trace.
In imperative programming arrangements, context propagation is typically implemented as out-of-band storage where the context is attached to the thread that is associated with the call. Context information is stored outside and integration components can associate context data and tracing identifiers through CommandListener.
Since CommandListener accept CommandEvent that are not associated with a context (or context provider), other types of programming models (asynchronous, event-loop, reactive) cannot leverage the ThreadLocal pattern.
The following proposal outlines a context capturing and propagation API to enable the driver to capture and propagate request contexts regardless of the underlying programming model. It consists of the following components:
- Generic context capturing interfaces to capture the current (inbound) context
- Context API
- Context consumption API
Context capturing
Calls to the driver are subject can happen within a potentially existing context. Depending on the called MongoDB API, context can be either stored ThreadLocal or can be passed as Reactor Context. Since the driver configuration infrastructure (MongoClientSettings) is API-agnostict, context providers should not depend on dependencies used in particular driver implementations. Instead, the driver internally can detect whether a context provider component was registered that can be leveraged to obtain the context:
// marker interface interface ContextProvider { } interface EnvironmentContextProvider extends ContextProvider { RequestContext get(); // typically fetches data from a ThreadLocal } interface ReactorContextProvider extends ContextProvider { RequestContext get(ContextView reactorContext); // extracts a context object from the Reactor ContextView } class MixedContextProvider implements EnvironmentContextProvider, ReactorContextProvider { private final Tracer tracer = …; public RequestContext get() { Span span = tracer.getCurrentSpan(); RequestContext ctx = …; ctx.put("span", span); return ctx; } public RequestContext get(ContextView view) { Span span = view.get(Span.class); RequestContext ctx = …; ctx.put("span", span); return ctx; } } MongoClientSettings.builder().contextProvider(new MixedContextProvider(…)).build();
Context API
The context API is a mutable, Map-like data structure that allows associating key-value tuples with the current request. In a tracing setup, beginning a request would create a new span, store it in the request context, and upon success, error, retry, the span would be retrieved from the context and completed with the command outcome.
// marker interface interface RequestContext // optional: extends Map<Object, Object> { Object get(Object key); // optional: <T> T get(Object key, Class<T> requiredType); Object put(Object key, Object o); Object remove(Object key); // optional: <K, T> T computeIfAbsent(K key, Function<K, T> factory, Class<T> requiredType); }
Context consumption
The context would be primarily consumed through the command listener API CommandListener and ideally, RequestContext is provided through CommandEvent. An example implementation would be:
class TracingCommandListener implements CommandListener { private final Tracer tracer = …; public void commandStarted(CommandStartedEvent event) { Span parent = event.getContext().get("span", Span.class); Span span = tracer.createSpan(event.getCommandName(), parent); span.spart(); event.getContext().put("span", span); } public void commandSucceeded(CommandSucceededEvent event) { Span span = event.getContext().get("span", Span.class); span.stop(); event.getContext().remove("span"); } }