Opentelemetry-java: Is it possible to get span context from threadlocal?

Created on 14 Sep 2020  路  8Comments  路  Source: open-telemetry/opentelemetry-java

Is your feature request related to a problem? Please describe.

In quickstart, I know how to create new span context in filter: https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md#context-propagation. But how can I get this context in controller, service and others?

Describe the solution you'd like
A theadlocal context resolver. Perhaps like that:

public class ThreadContextHolder {
    private static final ThreadLocal<Span> OPEN_TRACING_SPAN_HOLDER = new ThreadLocal<>();
    public static Span getOpenTracingSpan() {
        return OPEN_TRACING_SPAN_HOLDER.get();
    }
    public static void setOpenTracingSpan(Span span) {
        OPEN_TRACING_SPAN_HOLDER.set(span);
    }
}
public void handle(HttpExchange httpExchange) {
  // Extract the SpanContext and other elements from the request.
  Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
        .extract(Context.current(), httpExchange, getter);
  Span serverSpan = null;
  try (Scope scope = ContextUtils.withScopedContext(extractedContext)) {
    // Automatically use the extracted SpanContext as parent.
    serverSpan = tracer.spanBuilder("/resource").setSpanKind(Span.Kind.SERVER)
        .startSpan();
    // Add the attributes defined in the Semantic Conventions
    serverSpan.setAttribute("http.method", "GET");
    ThreadContextHolder.setOpenTracingSpan(serverSpan); // set to threadlocal
    // Serve the request
    ...
  } finally {
    if (serverSpan != null) {
      serverSpan.end();
    }
  }
}

Then we can access this span in the same thread. I guess the api would provide this util?

Feature Request

Most helpful comment

Ah - you want to make the Scope with the new span, not the extracted context. The scope, and TracingContextUtils is the utility opentelemetry provides for this :)

Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
            .extract(Context.current(), httpRequest, getter);
Span serverSpan = tracer.spanBuilder(httpRequest.getRequestURI()).setSpanKind(Span.Kind.SERVER)
    .setParent(extractedContext).startSpan();
...
try (Scope scope = TracingContextUtils.currentContextWith(serverSpan) {
  filterChain.doFilter(servletRequest, servletResponse);
}

All 8 comments

This is exactly what scope is for. You create you server span as in your example, then you create a new scope with that span:

public void handle(HttpExchange httpExchange) {
  // Extract the SpanContext and other elements from the request.
  Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
        .extract(Context.current(), httpExchange, getter);
  Span serverSpan =  tracer.spanBuilder("/resource").setSpanKind(Span.Kind.SERVER).setParent(extractedContext)
        .startSpan();
//This installs the context with serverSpan as the active one
  try (Scope scope = tracer.withSpan(serverSpan)) {
    // Add the attributes defined in the Semantic Conventions
    serverSpan.setAttribute("http.method", "GET");
    // Serve the request
    ...
  } finally {
    serverSpan.end();
  }
}

@iNikem Sorry I didn't get you point. In the controller how can I get the scope?

@RequestMapping(value = "/test")
    public boolean test() {
        // how to get scope?
    }

Hi @wwulfric, in your code you should be able to use Context.current() to get the current context, or alternatively to access the span directly, there is TracingContextUtils.getCurrentSpan(). Does that work for you?

https://github.com/open-telemetry/opentelemetry-java/blob/master/api/src/main/java/io/opentelemetry/trace/TracingContextUtils.java#L54

@anuraaga It seems literally right but the return span context trace id is 000...0, span id is 00...0.
```java
@RequestMapping(value = "/test")
public boolean test() {
Span span = TracingContextUtils.getCurrentSpan();
// span context trace id is 000...0, span id is 00...0.
}

Can you share your full code?

@iNikem

ThreadContextHolder holds threadlocal span.

public class ThreadContextHolder {
    private static final ThreadLocal<Span> OPEN_TRACING_SPAN_HOLDER = new ThreadLocal<>();
    public static Span getOpenTracingSpan() {
        return OPEN_TRACING_SPAN_HOLDER.get();
    }
    public static void setOpenTracingSpan(Span span) {
        OPEN_TRACING_SPAN_HOLDER.set(span);
    }
}

OpenTracingFilter is a web filter. Request scope span is generated here.

public class OpenTracingFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(OpenTracingFilter.class);

    private Tracer tracer;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        tracer = OpenTelemetry.getTracer(OpenTracingFilter.class.getName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;


        TextMapPropagator.Getter<HttpServletRequest> getter =
            HttpServletRequest::getHeader;


        Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
            .extract(Context.current(), httpRequest, getter);
        Span serverSpan = null;
        try (Scope scope = ContextUtils.withScopedContext(extractedContext)) {
            // Automatically use the extracted SpanContext as parent.
            serverSpan = tracer.spanBuilder(httpRequest.getRequestURI()).setSpanKind(Span.Kind.SERVER)
                .startSpan();
            // Add the attributes defined in the Semantic Conventions
            serverSpan.setAttribute("http.method", httpRequest.getMethod());
            serverSpan.setAttribute("http.scheme", httpRequest.getProtocol());
            serverSpan.setAttribute("http.host", httpRequest.getRemoteHost());
            serverSpan.setAttribute("http.target", httpRequest.getRequestURI());
            ThreadContextHolder.setOpenTracingSpan(serverSpan);
            // Serve the request 
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            if (serverSpan != null) {
                ThreadContextHolder.removeOpenTracingSpan();
                serverSpan.end();
            }
        }
    }

    @Override
    public void destroy() {
    }
}

I wanna access this span in controller method(and other same-thread logic block). I can access it by ThreadContextHolder. But I guess opentelemetry would provide this kind of util.

@RestController
public class ProjectController {
@RequestMapping(value = "/test")
    public boolean test() {
        Span span = ThreadContextHolder.getOpenTracingSpan();
    }
}

Ah - you want to make the Scope with the new span, not the extracted context. The scope, and TracingContextUtils is the utility opentelemetry provides for this :)

Context extractedContext = OpenTelemetry.getPropagators().getTextMapPropagator()
            .extract(Context.current(), httpRequest, getter);
Span serverSpan = tracer.spanBuilder(httpRequest.getRequestURI()).setSpanKind(Span.Kind.SERVER)
    .setParent(extractedContext).startSpan();
...
try (Scope scope = TracingContextUtils.currentContextWith(serverSpan) {
  filterChain.doFilter(servletRequest, servletResponse);
}

Hi @wwulfric - judging by your emoji it sounds like your issue was solved :) Let me close this issue but if you still have any please feel free to reopen it.

Was this page helpful?
0 / 5 - 0 ratings