4

When using the OTel Java API, manually instrumented code typically looks like the following:

@Inject
Tracer tracer;

public String instrumentedMethod() {
  // ...
  Span span = tracer.spanBuilder("interesting operation").startSpan();
  span.setAttribute("custom.info", "some info");
  try (Scope scope = span.makeCurrent()) {
    // logic
  }
  finally {
    span.end();
  }
  // ...
}

From my point of view, the lifespan of the span is defined twice:

  • tracer.spanBuilder("...").startSpan() and span.end() demarcate the beginning and the end of the span,
  • but also the span.makeCurrent() and scope.end() does this in some way

What's the purpose of startSpan()/span.end() in contrast to span.makeCurrent()/scope.end(). Can scope be omitted? Is span.end() superfluous when scope is used?

1 Answers1

2

Starting and ending a span does not make the rest of your application aware of the current Context - i.e. what span is currently "active".

If you didn't open the Scope, any code in your //logic section would not be able to know the Context, and so it can't know what the parent span is. You would end up with a bunch of individual spans that aren't linked together by a common trace.

When you open a Scope, OpenTelemetry sets a ThreadLocal, and so any new span started within that scope (the try block) will be able to know what its parent span is.

There are cases where you may have a Context but not an entire Span, so those concepts are separated for that reason (and I assume a handful of others reasons).

James Moessis
  • 654
  • 6
  • 8