question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Slf4j MDC vs Scopes

See original GitHub issue

Disclaimer: I don’t have a working implementation yet

TL;DR - It seems that a reasonable implementation is possible with Scopes if we wrap Span whenever we start a new Scope - which seems similar to the “ActiveSpan” concept from the previous version.

Goals:

  • I want debug_id (span-id + trace-id) of the active span to be available inside MDC

    • put debug_id inside MDC when scope is activated
    • when the active scope closes remove any MDC changes since the scope was activated
  • When I define key-value attributes of the current (computation) context and put them into MDC then I want them to automatically propagate to other threads using OT in-process propagation. I want this because I don’t want to write additional in-process propagation for MDC.

    • propagate custom MDC entries to child scopes (e.g. user_id)
    • add MDC entries as span tags
  • When I define key-value attributes of the current (computation) context I want to set them both on MDC and active Span

    • when they are changed they should change both in MDC and the Span
    • if the value changes multiple times on a single Span it’s ok if only the last tag value is reported when Span finishes

The first point (setting debug_id) is trivial - see PR https://github.com/opentracing/opentracing-java/pull/207. The only requirement is not to share scopes across threads which I assume would be invalid anyway.

When it comes to propagating MDC to other scopes and other threads things get complicated:

Problem A:

In order to add MDC entries as tags in a child scope/thread we somehow need to pass MDC entries to that scope/thread. Right now the only entity that’s passed is the Span:

Scope scope = tracer.buildSpan("parent").startActive();
MDC.put("key", "x");

runInThread(() -> {
    try(Scope child = scopeManager.activate(scope.span())) {
        MDC.get("key") // == "x"
    }
});

That means that

  1. we have to glue our data to the span instance
  2. or pass something else - instead of Span or in addition to
Ad 1. Add data to Span instance

This seems doable. Whenever startActive is called and Scope is created we can wrap the Span with our SpanWithContext. Then we can store any extra information we need inside it.

When scopeManager.activate(spanwithcontext) is called we have all the information.

Ad 2. Pass something other than Span

This is solution similar to the one described in the java-examples - the Continuation/AutoFinishScope. Copying MDC manually also belong to this category. Because of the fact that this approach does not use the official API it’s a non-starter - it would require to instrument all concurrency libraries (rxjava, akka, hystrix, …) with extra capabilities for propagating MDC, but we want to use existing OT-contrib libs which already do the propagation.

With Continuations in the official API this would be the ideal solution - we’d just pass the data inside the Continuation.

Problem B:

How to put MDC data into the active Span?

The thing is that after setting MDC there won’t be any additional OT calls so there is no obvious place where to put the code which would copy the value into the active Span.

Scope scope = tracer.buildSpan("parent").startActive();
MDC.put("key", "x"); // after scope created, no OT calls afterwards

runInThread(() -> {
    try(Scope child = scopeManager.activate(scope.span())) {
        MDC.get("key")
    }
});

I see following options here:

  • change MDC implementation to actually both set the value inside MDC and additionally in the active Span. This is what we did at my company. We replace the MDCAdapter using reflection to our own implementation which is opentracing-aware. It’s a bit hacky but it works. I’d go for this approach but the upcoming Slf4j 1.8 is modularized (because Java 9) and we won’t be able to reach into its internals anymore. Slf4j 1.8 uses ServiceLoader to load the implementation and I’m not sure if we’d be able to hijack this mechanism. All in all approaching it form this side does not look good.

  • Another option is to stop using MDC class directly. Instead of

    MDC.put(key, value)
    

    Use

    Context.put(key, value)
    

    This allows us to put our logic inside the Context. Actually we already have such context - it’s the Span. So the idea is to use:

    Span.put(key, value)
    

    A real implementation could allow to configure which tags are also set in the MDC to avoid putting there everything.

Problem C:

There is a single parent MDC “state” but multiple “child” ones starting at different times. What I mean is a situation like this:

Scope scope = tracer.buildSpan("parent").startActive();
scope.span().setTag("key", "x")
// no capture() here

runInThread(() -> {
    Thread.sleep(1000)
    try(Scope child = scopeManager.activate(scope.span())) {
        MDC.get("key") // == "x" or "y" ?
    }
});
scope.span().setTag("key", "y")

With Continuations it would be easy because of the capture() call which would be able to record the “parent” state. I don’t see a good solution for this right now and my approach would be to just ignore this. It only happens if someone changes the tag values and then passes the span to another thread. One workaround is to create a child span and then pass it:

Scope scope = tracer.buildSpan("parent").startActive();
scope.span().setTag("key", "x")
// no capture() here

try(Scope outerChild = scopeManager.activate(scope.span())) {
    runInThread(() -> {
        Thread.sleep(1000)
        try(Scope child = scopeManager.activate(outerChild.span())) {
            MDC.get("key") // guaranteed to be "x"
        }
    });
}

scope.span().setTag("key", "y")

This is possible because despite scope.span() returning the same span from OT perspective, in reality it returns a different instance of SpanWithContext for different active scopes.

Problem D:

  • Need to react to Span start to be able to capture debug ids (or we could do that when scope activates)
  • Need to react to Scope activation to mark the point to which revert MDC changes when Scope closes
  • Need to react to Scope closing to revert MDC changes

Right now the only way is to wrap SpanBuilder. It would be great if the OT API allowed to do that without custom wrappers.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:2
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
wsargentcommented, Jan 4, 2018

Possibly off topic, but it is possible to use markers rather than MDC to pass contextual information around, i.e. https://www.playframework.com/documentation/2.6.x/Highlights26#Logging-Marker-API

0reactions
fzakariacommented, Nov 21, 2017

I would find TraceId and SpanId to the context useful. Right now I’m casting to my implementation (Jaeger) which exposes both.

Also it might be useful to make the mechanism in which request id generation created a Provider – consumers may want fixed / less random / more random solutions.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Improved Java Logging with Mapped Diagnostic Context (MDC)
Mapped Diagnostic Context provides a way to enrich log messages with information that could be unavailable in the scope where the logging ...
Read more >
not able to put the values in the MDC - Stack Overflow
Typically, MDC values are only output to logs if you include MDC keys in your logging pattern via configuration. Since slf4j is just...
Read more >
Customized Logging using SLF4J / MDC in AEM
AEM comes bundled with SLF4J, and therefore already has support for logback patterns, and more importantly, MDC. What is missing here is how...
Read more >
SLF4J MDC • Lightbend Telemetry
SLF4J MDC · You do not need to use any special APIs to modify the MDC; you can use the org. · You...
Read more >
There is more to logging than meets the eye - allegro.tech blog
The most popular ones are: SLF4J - 61% and Commons Logging - 9%. Plenty of developers - 28% - use no ... MDC...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found