diff --git a/README.md b/README.md index 2a80389..ee63db4 100644 --- a/README.md +++ b/README.md @@ -26,134 +26,156 @@ composer require opentracing/opentracing ## Usage When consuming this library one really only need to worry about a couple of key -abstractions: the `Tracer::startSpan` method, the `Span` interface, and binding -a `Tracer` at bootstrap time. Here are code snippets demonstrating some important -use cases: +abstractions: the `Tracer::startActiveSpan` and `Tracer::startSpan` method, +the `Span` interface, and binding a `Tracer` at bootstrap time. Here are code snippets +demonstrating some important use cases: ### Singleton initialization The simplest starting point is to set the global tracer. As early as possible, do: ```php - use OpenTracing\GlobalTracer; - use AnOpenTracingImplementation\MyTracer; - - GlobalTracer::set(new MyTracer()); +use OpenTracing\GlobalTracer; + +GlobalTracer::set(new MyTracerImplementation()); ``` ### Creating a Span given an existing Request -To start a new `Span`, you can use the `StartSpanFromContext` method. +To start a new `Span`, you can use the `startActiveSpan` method. ```php - use OpenTracing\Formats; - use OpenTracing\GlobalTracer; +use OpenTracing\Formats; +use OpenTracing\GlobalTracer; - ... +... - $spanContext = GlobalTracer::get()->extract( - Formats\HTTP_HEADERS, - $request->getHeaders() - ); +$spanContext = GlobalTracer::get()->extract( + Formats\HTTP_HEADERS, + getallheaders() +); + +function doSomething() { + ... - function doSomething(SpanContext $spanContext, ...) { - ... - - $span = GlobalTracer::get()->startSpan('my_span', [ - 'child_of' => $spanContext, - ]); - - ... - - $span->log([ - 'event' => 'soft error', - 'type' => 'cache timeout', - 'waiter.millis' => 1500, - ]) - - $span->finish(); - } + $span = GlobalTracer::get()->startActiveSpan('my_span', ['child_of' => $spanContext]); + + ... + + $span->log([ + 'event' => 'soft error', + 'type' => 'cache timeout', + 'waiter.millis' => 1500, + ]) + + $span->finish(); +} ``` ### Starting an empty trace by creating a "root span" -It's always possible to create a "root" `Span` with no parent or other causal -reference. +It's always possible to create a "root" `Span` with no parent or other causal reference. ```php - $span = $tracer->startSpan('my_span'); - ... - $span->finish(); +$span = $tracer->startActiveSpan('my_first_span'); +... +$span->finish(); ``` -### Creating a (child) Span given an existing (parent) Span +#### Creating a child span assigning parent manually ```php - function xyz(Span $parentSpan, ...) { - ... - $span = GlobalTracer::get()->startSpan( - 'my_span', - [ - 'child_of' => $parentSpan, - ] - ); - - $span->finish(); - ... - } +$parent = GlobalTracer::get()->startSpan('parent'); + +$child = GlobalTracer::get()->startSpan('child', [ + 'child_of' => $parent +]); + +... + +$child->finish(); + +... + +$parent->finish(); +``` + +#### Creating a child span using automatic active span management + +Every new span will take the active span as parent and it will take its spot. + +```php +$parent = GlobalTracer::get()->startActiveSpan('parent'); + +... + +/* + * Since the parent span has been created by using startActiveSpan we don't need + * to pass a reference for this child span + */ +$child = GlobalTracer::get()->startActiveSpan('my_second_span'); + +... + +$child->finish(); + +... + +$parent->finish(); ``` ### Serializing to the wire ```php - use OpenTracing\GlobalTracer; - use OpenTracing\Formats; - - ... +use GuzzleHttp\Client; +use OpenTracing\Formats; + +... + +$tracer = GlobalTracer::get(); + +$spanContext = $tracer->extract( + Formats\HTTP_HEADERS, + getallheaders() +); + +try { + $span = $tracer->startSpan('my_span', ['child_of' => $spanContext]); + + $client = new Client; - $tracer = GlobalTracer::get(); + $headers = []; - $spanContext = $tracer->extract( + $tracer->inject( + $span->getContext(), Formats\HTTP_HEADERS, - $request->getHeaders() + $headers ); - try { - $span = $tracer->startSpan('my_span', ['child_of' => $spanContext]); - - $client = new GuzzleHttp\Client; - - $headers = []; - - $tracer->inject( - $span->getContext(), - Formats\HTTP_HEADERS, - $headers - ); - - $request = new \GuzzleHttp\Psr7\Request('GET', 'http://myservice', $headers); - $client->send($request); - ... - } catch (\Exception $e) { - ... - } - ... + $request = new \GuzzleHttp\Psr7\Request('GET', 'http://myservice', $headers); + $client->send($request); + ... + +} catch (\Exception $e) { + ... +} +... ``` ### Deserializing from the wire -When using http header for context propagation you can use either the `Request` or the `$_SERVER` variable: +When using http header for context propagation you can use either the `Request` or the `$_SERVER` +variable: ```php - use OpenTracing\GlobalTracer; - use OpenTracing\Formats; - - $request = Request::createFromGlobals(); - $tracer = GlobalTracer::get(); - $spanContext = $tracer->extract(Formats\HTTP_HEADERS, $request->getHeaders()); - $tracer->startSpan('my_span', [ - 'child_of' => $spanContext, - ]); +use OpenTracing\GlobalTracer; +use OpenTracing\Formats; + +$tracer = GlobalTracer::get(); +$spanContext = $tracer->extract(Formats\HTTP_HEADERS, getallheaders()); +$tracer->startSpan('my_span', [ + 'child_of' => $spanContext, +]); ``` ### Flushing Spans @@ -167,19 +189,18 @@ cause problems for Tracer implementations. This is why the PHP API contains a ```php use OpenTracing\GlobalTracer; -// Do application work, buffer spans in memory $application->run(); -fastcgi_finish_request(); - -$tracer = GlobalTracer::get(); -$tracer->flush(); // release buffer to backend +register_shutdown_function(function() use ($tracer) { + /* Flush the tracer to the backend */ + $tracer = GlobalTracer::get(); + $tracer->flush(); +}); ``` This is optional, tracers can decide to immediately send finished spans to a backend. The flush call can be implemented as a NO-OP for these tracers. - ### Using Span Options Passing options to the pass can be done using either an array or the @@ -191,7 +212,7 @@ SpanOptions wrapper object. The following keys are valid: - `tags` is an array with string keys and scalar values that represent OpenTracing tags. ```php -$span = $tracer->createSpan('my_span', [ +$span = $tracer->startActiveSpan('my_span', [ 'child_of' => $spanContext, 'tags' => ['foo' => 'bar'], 'start_time' => time(), @@ -217,5 +238,5 @@ Tracers will throw an exception if the requested format is not handled by them. ## Coding Style -Opentracing PHP follows the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) +OpenTracing PHP follows the [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) coding standard and the [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) autoloading standard. diff --git a/src/OpenTracing/GlobalTracer.php b/src/OpenTracing/GlobalTracer.php index 87e5e3d..4fce54f 100644 --- a/src/OpenTracing/GlobalTracer.php +++ b/src/OpenTracing/GlobalTracer.php @@ -7,14 +7,14 @@ final class GlobalTracer /** * @var Tracer */ - private static $instance = null; + private static $instance; /** * GlobalTracer::set sets the [singleton] Tracer returned by get(). * Those who use GlobalTracer (rather than directly manage a Tracer instance) * should call GlobalTracer::set as early as possible in bootstrap, prior to * start a new span. Prior to calling GlobalTracer::set, any Spans started - * via the `Tracer::startSpan` (etc) globals are noops. + * via the `Tracer::startActiveSpan` (etc) globals are noops. * * @param Tracer $tracer */ @@ -33,7 +33,7 @@ public static function set(Tracer $tracer) public static function get() { if (self::$instance === null) { - self::$instance = NoopTracer::create(); + self::$instance = Tracer::create(); } return self::$instance; diff --git a/src/OpenTracing/NoopScopeManager.php b/src/OpenTracing/NoopScopeManager.php new file mode 100644 index 0000000..b294754 --- /dev/null +++ b/src/OpenTracing/NoopScopeManager.php @@ -0,0 +1,26 @@ + + * Many times a {@link Span} will be extant (in that {@link Span#finish()} has not been called) despite being in a + * non-runnable state from a CPU/scheduler standpoint. For instance, a {@link Span} representing the client side of an + * RPC will be unfinished but blocked on IO while the RPC is still outstanding. A {@link Scope} defines when a given + * {@link Span} is scheduled and on the path. + */ +interface Scope +{ + /** + * Mark the end of the active period for the current thread and {@link Scope}, + * updating the {@link ScopeManager#active()} in the process. + * + *

+ * NOTE: Calling {@link #close} more than once on a single {@link Scope} instance leads to undefined + * behavior. + */ + public function close(); + + /** + * @return Span the {@link Span} that's been scoped by this {@link Scope} + */ + public function getSpan(); +} \ No newline at end of file diff --git a/src/OpenTracing/ScopeManager.php b/src/OpenTracing/ScopeManager.php new file mode 100644 index 0000000..b77431d --- /dev/null +++ b/src/OpenTracing/ScopeManager.php @@ -0,0 +1,35 @@ + + * If there is an {@link Scope non-null scope}, its wrapped {@link Span} becomes an implicit parent + * (as {@link References#CHILD_OF} reference) of any + * newly-created {@link Span} at {@link Tracer.SpanBuilder#startActive(boolean)} or {@link SpanBuilder#start()} + * time rather than at {@link Tracer#buildSpan(String)} time. + * + * @return Span the {@link Scope active scope}, or null if none could be found. + */ + public function getActiveSpan(); +} diff --git a/src/OpenTracing/Span.php b/src/OpenTracing/Span.php index ba4ea65..b1dbfc8 100644 --- a/src/OpenTracing/Span.php +++ b/src/OpenTracing/Span.php @@ -21,15 +21,19 @@ public function getContext(); /** * Sets the end timestamp and finalizes Span state. * - * With the exception of calls to Context() (which are always allowed), + * With the exception of calls to getContext() (which are always allowed), * finish() must be the last call made to any span instance, and to do - * otherwise leads to undefined behavior + * otherwise leads to undefined behavior but not returning an exception. + * + * As an implementor, make sure you call {@see Tracer::deactivate()} + * otherwise new spans might try to be child of this one. * * If the span is already finished, a warning should be logged. * * @param float|int|\DateTimeInterface|null $finishTime if passing float or int * it should represent the timestamp (including as many decimal places as you need) * @param array $logRecords + * @return void */ public function finish($finishTime = null, array $logRecords = []); @@ -41,25 +45,27 @@ public function finish($finishTime = null, array $logRecords = []); public function overwriteOperationName($newOperationName); /** - * Sets tags to the Span in key:value format, key must be a string and tag must be either + * Sets tags to the Span in key => value format, key must be a string and tag must be either * a string, a boolean value, or a numeric type. * - * As an implementor, consider using "standard tags" listed in {@see \OpenTracing\Ext\Tags} + * As an implementor, consider using "standard tags" listed in {@see \OpenTracing\Tags} * * If the span is already finished, a warning should be logged. * * @param array $tags + * @return void */ public function setTags(array $tags); /** - * Adds a log record to the span in key:value format, key must be a string and tag must be either + * Adds a log record to the span in key => value format, key must be a string and tag must be either * a string, a boolean value, or a numeric type. * * If the span is already finished, a warning should be logged. * * @param array $fields * @param int|float|\DateTimeInterface $timestamp + * @return void */ public function log(array $fields = [], $timestamp = null); @@ -71,12 +77,13 @@ public function log(array $fields = [], $timestamp = null); * * @param string $key * @param string $value + * @return void */ public function addBaggageItem($key, $value); /** * @param string $key - * @return string + * @return string|null returns null when there is not a item under the provided key */ public function getBaggageItem($key); } diff --git a/src/OpenTracing/SpanContext.php b/src/OpenTracing/SpanContext.php index f02069d..a5d3757 100644 --- a/src/OpenTracing/SpanContext.php +++ b/src/OpenTracing/SpanContext.php @@ -8,7 +8,7 @@ * SpanContext must be immutable in order to avoid complicated lifetime * issues around Span finish and references. * - * Baggage items are key:value string pairs that apply to the given Span, + * Baggage items are key => value string pairs that apply to the given Span, * its SpanContext, and all Spans which directly or transitively reference * the local Span. That is, baggage items propagate in-band along with the * trace itself. @@ -25,7 +25,7 @@ interface SpanContext extends IteratorAggregate public function getBaggageItem($key); /** - * Creates a new SpanContext out of the existing one and the new key:value pair. + * Creates a new SpanContext out of the existing one and the new key => value pair. * * @param string $key * @param string $value diff --git a/src/OpenTracing/SpanOptions.php b/src/OpenTracing/SpanOptions.php index 86474a0..85a33f5 100644 --- a/src/OpenTracing/SpanOptions.php +++ b/src/OpenTracing/SpanOptions.php @@ -18,7 +18,7 @@ final class SpanOptions private $tags = []; /** - * @var int|float|\DateTime + * @var int|float|\DateTimeInterface */ private $startTime; diff --git a/src/OpenTracing/Ext/Tags.php b/src/OpenTracing/Tags.php similarity index 99% rename from src/OpenTracing/Ext/Tags.php rename to src/OpenTracing/Tags.php index 468c87b..e4702d3 100644 --- a/src/OpenTracing/Ext/Tags.php +++ b/src/OpenTracing/Tags.php @@ -1,6 +1,6 @@ getActive()->getSpan(), + * and null will be returned if {@link Scope#active()} is null. + */ + public function getActiveSpan(); + + /** + * Starts and returns a new `Span` representing a unit of work. + * + * This method differs from `startSpan` because it uses in-process + * context propagation to keep track of the current active `Span` (if + * available). + * + * Starting a root `Span` with no casual references and a child `Span` + * in a different function, is possible without passing the parent + * reference around: + * + * function handleRequest(Request $request, $userId) + * { + * $rootSpan = $this->tracer->startActiveSpan('request.handler'); + * $user = $this->repository->getUser($userId); + * } + * + * function getUser($userId) + * { + * // `$childSpan` has `$rootSpan` as parent. + * $childSpan = $this->tracer->startActiveSpan('db.query'); + * } + * * @param string $operationName * @param array|SpanOptions $options A set of optional parameters: * - Zero or more references to related SpanContexts, including a shorthand for ChildOf and @@ -17,6 +50,16 @@ interface Tracer * - An optional explicit start timestamp; if omitted, the current walltime is used by default * The default value should be set by the vendor. * - Zero or more tags + * @param bool $finishSpanOnClose whether span should automatically be finished when {@link Scope#close()} is called + * @return Scope + */ + public function startActiveSpan($operationName, $finishSpanOnClose = true, $options = []); + + /** + * Starts and returns a new `Span` representing a unit of work. + * + * @param string $operationName + * @param array|SpanOptions $options * @return Span * @throws InvalidSpanOption for invalid option * @throws InvalidReferencesSet for invalid references set @@ -53,9 +96,8 @@ public function extract($format, $carrier); * * This method might not be needed depending on the tracing implementation * but one should make sure this method is called after the request is finished. - * As an implementor, a good idea would be to use an asynchronous message bus - * or use the call to fastcgi_finish_request in order to not to delay the end - * of the request to the client. + * As an implementor, a good idea would be to use register_shutdown_function + * or fastcgi_finish_request in order to not to delay the end of the request to the client. * * @see register_shutdown_function() * @see fastcgi_finish_request()