opentracing / opentracing-php

OpenTracing API for PHP
Apache License 2.0
505 stars 56 forks source link

Change Pythonic to PHPonic behavior with regard to scopes. #47

Closed beberlei closed 6 years ago

beberlei commented 6 years ago

The Python In Process propagation is based on the with concept in Python that can automatically handle scope closing and therefore its usage is simple. PHP doesnt have this language concept:

If Python API is ported to PHP 100% our API would be bad:

$scope = $tracer->startActiveSpan('foo');
try {
    $span = $scope->getSpan();
    $span->setTag('foo', 'bar');

    do_some_work();
} finally {
    $scope->close();
}

This is compared to Pythons:

with tracer.start_active_span('foo') as scope:
    scope.span.set_tag('foo', 'bar')

    do_some_work();

Python is a much more parallel and threaded language than PHP, making the scope a more important concept. But for PHP the default use-case should be shared-nothing single request processing. In this case scopes are of no interest to 99% of the users.

In contrast I propose that in PHP we hide scopes from the public API and only abstract them away for implementors convenience:

$span = $tracer->startActiveSpan('foo');
try {
    $span->setTag('foo', 'bar');

    do_some_work();
} finally {
    $span->finish();
}

Not as nice as Python, but much better than before.

This relies on a new span option called close_span_on_finish which is the other way around than in Python. It is true by default, again because in PHP we don't usually have threads and active spans can use a simple stack to find the current span, the 99% use case for most library and application developers that will want to use OT. most library and application developers that will want to use OT.

If you set close_span_on_finish=false then you can close the span the following way:

$span = $tracer->startActiveSpan('foo',
    ['close_span_on_finish' => false]);
try {
    $span->setTag('foo', 'bar');

    do_some_work();
} finally {
    $span->finish();
    $tracer->getScopeManager()->getScope($span)->close();
}

This API will usually not be needed by anyone that is using active spans, so I consider the complexity and Law Of Demeter violation to be acceptable compared to code example #1 where a user must use both $span and $scope in the right way.

All the examples above will also work for event based PHP scripts if the scope manager is implemented correctly for the Event looped system. By default a scope manager is a simple stack. For an event looped system the scope manager would keep stacks for all active threads and change the stack whenever the thread activity changes.

$scopeManager = new MyEventScopeManager();
$scopeManager->activateThread(1);
$scopeManager->activateSpan($rootSpanThread1);
$scopeManager->activateThread(2);
$scopeManager->getActiveScope(); // null

Here the activateThread method is a public method that is implementation specific based on the event loop. If ReactPHP would implement their scope manager, then all existing library code would keep on working correctly.

Ping @tedsuo @yurishkuro @felixfbecker

Closes #43