Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding LocalRootSpan #1310

Merged
merged 12 commits into from
Aug 6, 2024
Merged

Conversation

brettmc
Copy link
Collaborator

@brettmc brettmc commented May 14, 2024

this is based on Java's implementation, /~https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v2.3.0/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/LocalRootSpan.java and adds the ability to identify and locate the "local root span" in a trace. The local root span is the top-level active span which has either an invalid or remote parent.
It's tracked automatically as part of making a span active, either via Span::activate() or Span::storeInContext(), and can be retrieved in a couple of ways, but most easily via LocalRootSpan::current().

this is based on Java's implementation, /~https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v2.3.0/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/LocalRootSpan.java
and adds the ability to identify and locate the "local root span" in a trace. The local root span is the top-level active span which has either
an invalid or remote parent.
It's tracked automatically as part of making a span active, either via `Span::activate()` or `Span::storeInContext()`, and can be retrieved in
a couple of ways, but most easily via `LocalRootSpan::current()`.
@brettmc brettmc requested a review from a team May 14, 2024 10:40
Copy link

codecov bot commented May 14, 2024

Codecov Report

Attention: Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.

Please upload report for BASE (main@68b1b43). Learn more about missing BASE report.

Files Patch % Lines
src/API/Logs/LateBindingLogger.php 0.00% 2 Missing ⚠️
src/API/Trace/LateBindingTracer.php 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1310   +/-   ##
=======================================
  Coverage        ?   74.21%           
  Complexity      ?     2584           
=======================================
  Files           ?      373           
  Lines           ?     7430           
  Branches        ?        0           
=======================================
  Hits            ?     5514           
  Misses          ?     1916           
  Partials        ?        0           
Flag Coverage Δ
8.1 73.82% <66.66%> (?)
8.2 74.11% <77.77%> (?)
8.3 74.03% <77.77%> (?)
8.4 74.02% <77.77%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
src/API/Trace/LocalRootSpan.php 100.00% <100.00%> (ø)
src/API/Trace/Span.php 94.44% <100.00%> (ø)
src/API/Logs/LateBindingLogger.php 60.00% <0.00%> (ø)
src/API/Trace/LateBindingTracer.php 60.00% <0.00%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 68b1b43...e6fb45d. Read the comment docs.

@brettmc
Copy link
Collaborator Author

brettmc commented May 14, 2024

The primary objective here is to make it easier to go back and modify the local root span of a transaction. We do this now (by stashing the root span in context and accessing it later) in some auto-instrumentation packages to do things like update the name once routing has taken place, and in a similar fashion we could also add further attributes that were not known at the time the root span was created.

src/API/Trace/Span.php Outdated Show resolved Hide resolved
}

/** @inheritDoc */
final public function storeInContext(ContextInterface $context): ContextInterface
{
if (LocalRootSpan::isLocalRoot($context)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local root span is the top-level active span which has either an invalid or remote parent.

The context given to ::storeInContext() is not necessarily the parent context of the span -> local root cannot be set by ::storeInContext().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's unfortunate, because it's the primary use-case! When would this happen? And could that scenario be represented in a unit/integration test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible scenario would be batch receiving of messages w/ a child span for every message that is being processed; loosely based on the messaging semconv:

$tracer = (new TracerProviderBuilder())->build()->getTracer('example');

// “Receive” spans SHOULD be created for operations of passing messages to the application when those operations are initiated by the application code (pull-based scenarios).
$root = $tracer
    ->spanBuilder('receive')
    ->setSpanKind(SpanKind::KIND_CONSUMER)
    ->startSpan();
$context = Context::getCurrent();
/*
if (LocalRootSpan::isLocalRoot($context)) {
    $context = LocalRootSpan::store($context, $root);
}
*/
$rootScope = $root
    ->storeInContext($context)
    ->activate();

// ...

$remoteContext = /* extract creation context */Span::getInvalid()->storeInContext(Context::getCurrent());
if (($remoteSpan = Span::fromContext($remoteContext)) !== Span::getCurrent()) {
    // For each message it accounts for, the “Process” or “Receive” span SHOULD link to the message’s creation context.
    $root->addLink($remoteSpan->getContext());
}

// “Process” spans MAY be created in addition to “Receive” spans for pull-based scenarios for operations of processing messages.
$childScope = $tracer
    ->spanBuilder('process')
    ->startSpan()
    ->storeInContext($remoteContext) // preserve remote baggage etc.
    ->activate();
try {
    assert(LocalRootSpan::current() === $root);
    assert(LocalRootSpan::current() !== Span::getCurrent());
} finally {
    $childScope->detach();
    $rootScope->detach();
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. But in that example, by creating and activating a new span with remote parent, we've context switched and the local root is the process span. After ending the process span, then receive is the local root again.
I happened upon https://opentelemetry.io/docs/languages/java/automatic/configuration/#capturing-consumer-message-receive-telemetry-in-messaging-instrumentations which sounds like that behaviour is what happens with Java...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by creating and activating a new span with remote parent

The process span is a child span of the receive span in the example above, the remote span is only recorded as link on the receive span; exporting the example (w/ SpanContext::createFromRemoteParent('fabebb164f22d4afc51df50d9a3ff629', '87c6836d8610ac6d') instead of an invalid span context to simulate an actual remote span; doesn't change behavior):

"scopeSpans": [
    {
        "scope": {
            "name": "example"
        },
        "spans": [
            {
                "traceId": "a2ee09cae8ed2ddfb91ee679a50f18d0",
                "spanId": "274517003024b34a",
                "flags": 259,
                "name": "receive",
                "kind": 5,
                "startTimeUnixNano": "1716403479097090643",
                "endTimeUnixNano": "1716403479097304335",
                "links": [
                    {
                        "traceId": "fabebb164f22d4afc51df50d9a3ff629",
                        "spanId": "87c6836d8610ac6d",
                        "flags": 768
                    }
                ],
                "status": {}
            },
            {
                "traceId": "a2ee09cae8ed2ddfb91ee679a50f18d0",
                "spanId": "aca69b6ea434162d",
                "parentSpanId": "274517003024b34a",
                "flags": 259,
                "name": "process",
                "kind": 1,
                "startTimeUnixNano": "1716403479097294259",
                "endTimeUnixNano": "1716403479097313118",
                "attributes": [
                    {
                        "key": "local_root",
                        "value": {
                            # set to LocalRootSpan::current() === Span::getCurrent()
                            # should be false, parent span is local
                            "boolValue": true
                        }
                    }
                ],
                "status": {}
            }
        ]
    }
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to replicate this with an example (committed and pushed), however I think I created the remote span differently, because my local_root check seems ok...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had another play with this PR today. Given that all of the use-cases that I've seen for needing the root span have been for kind=server (specifically http server spans), perhaps we can avoid this issue (at least for now), by only setting the root span for kind=server spans with a remote parent?
We can document it as such, and also mark it as experimental.

src/Context/ContextKeys.php Outdated Show resolved Hide resolved
@bobstrecansky bobstrecansky merged commit 10b8d97 into open-telemetry:main Aug 6, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants