From 94c40e9a9372e84513bd8d2b579774899f7962a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Ch=C3=A1vez?= Date: Wed, 7 Feb 2018 13:53:50 -0500 Subject: [PATCH] Adds span and basic tracer. --- composer.json | 6 +- src/DDTrace/Meta.php | 11 ++ src/DDTrace/Span.php | 302 +++++++++++++++++++++++++++++++++ src/DDTrace/Tracer.php | 44 +++++ src/DDTrace/Transport.php | 21 +++ src/DDTrace/Transport/Noop.php | 18 ++ tests/Unit/SpanTest.php | 73 ++++++++ 7 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 src/DDTrace/Meta.php create mode 100644 src/DDTrace/Span.php create mode 100644 src/DDTrace/Tracer.php create mode 100644 src/DDTrace/Transport.php create mode 100644 src/DDTrace/Transport/Noop.php create mode 100644 tests/Unit/SpanTest.php diff --git a/composer.json b/composer.json index 451c5718374..1ec6622b3cf 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ ], "minimum-stability": "dev", "require": { - "opentracing/opentracing": "1.0.0-beta2" + "opentracing/opentracing": "1.0.0-beta2", + "symfony/polyfill": "~1.7.0" }, "require-dev": { "phpunit/phpunit": "~5.7.19", @@ -25,9 +26,10 @@ }, "autoload": { "psr-4": { - "DDTrace\\": "./src/" + "DDTrace\\": "./src/DDTrace/" }, "files": [ + "./src/DDTrace/Meta.php", "./src/DDTrace/MicroTime.php" ] }, diff --git a/src/DDTrace/Meta.php b/src/DDTrace/Meta.php new file mode 100644 index 00000000000..b8675713d5e --- /dev/null +++ b/src/DDTrace/Meta.php @@ -0,0 +1,11 @@ +tracer = $tracer; + $this->name = (string) $name; + $this->service = $service; + $this->resource = (string) $resource; + $this->start = $start ?: MicroTime\now(); + $this->traceId = $traceId; + $this->spanId = $spanId; + $this->parentId = $parentId; + } + + /** + * @return string + */ + public function getTraceId() + { + return $this->traceId; + } + + /** + * @return string + */ + public function getSpanId() + { + return $this->spanId; + } + + /** + * @return null|string + */ + public function getParentId() + { + return $this->parentId; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * @return void + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getResource() + { + return $this->resource; + } + + /** + * @return string + */ + public function getService() + { + return $this->service; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getStart() + { + return $this->start; + } + + /** + * @return int + */ + public function getDuration() + { + return $this->duration; + } + + /** + * Adds an arbitrary meta field to the current Span. + * If the Span has been finished, it will not be modified by the method. + * + * @param string $key + * @param string $value + * @throws InvalidArgumentException + */ + public function setMeta($key, $value) + { + if ($this->isFinished()) { + return; + } + + if ($key !== (string) $key) { + throw new InvalidArgumentException( + sprintf('First argument expected to be string, got %s', gettype($key)) + ); + } + + $this->meta[$key] = (string) $value; + } + + /** + * @param string $key + * @return string|null + */ + public function getMeta($key) + { + if (array_key_exists($key, $this->meta)) { + return $this->meta[$key]; + } + + return null; + } + + /** + * Stores a Throwable object within the span meta. The error status is + * updated and the error.Error() string is included with a default meta key. + * If the Span has been finished, it will not be modified by this method. + * + * @param Throwable|Exception $e + * @throws InvalidArgumentException + */ + public function setError($e) + { + if ($this->isFinished()) { + return; + } + + if (($e instanceof Exception) || ($e instanceof Throwable)) { + $this->hasError = true; + $this->setMeta(Meta\ERROR_MSG_KEY, $e->getMessage()); + $this->setMeta(Meta\ERROR_TYPE_KEY, get_class($e)); + $this->setMeta(Meta\ERROR_STACK_KEY, $e->getTraceAsString()); + return; + } + + throw new InvalidArgumentException( + sprintf('Error should be either Exception or Throwable, got %s.', gettype($e)) + ); + } + + public function hasError() + { + return $this->hasError; + } + + /** + * Finish closes this Span (but not its children) providing the duration + * of this part of the tracing session. This method is idempotent so + * calling this method multiple times is safe and doesn't update the + * current Span. Once a Span has been finished, methods that modify the Span + * will become no-ops. + * + * @param int|null $finish + * @return void + */ + public function finish($finish = null) + { + if ($this->isFinished()) { + return; + } + + $this->duration = ($finish ?: MicroTime\now()) - $this->start; + $this->tracer->record($this); + } + + /** + * @param Throwable|Exception $e + * @return void + */ + public function finishWithError($e) + { + $this->setError($e); + $this->finish(); + } + + private function isFinished() + { + return $this->duration !== null; + } +} diff --git a/src/DDTrace/Tracer.php b/src/DDTrace/Tracer.php new file mode 100644 index 00000000000..c20ccfdcc3e --- /dev/null +++ b/src/DDTrace/Tracer.php @@ -0,0 +1,44 @@ +transport = $transport; + } + + public static function noop() + { + return new self(new Noop); + } + + /** + * @param Span $span + */ + public function record(Span $span) + { + $this->traces[$span->getTraceId()][] = $span; + } + + /** + * @return void + */ + public function flush() + { + $this->transport->send($this->traces); + } +} diff --git a/src/DDTrace/Transport.php b/src/DDTrace/Transport.php new file mode 100644 index 00000000000..e917fa0e17b --- /dev/null +++ b/src/DDTrace/Transport.php @@ -0,0 +1,21 @@ +createSpan(); + $span->setMeta(self::META_KEY, self::META_VALUE); + + $this->assertSame(self::NAME, $span->getName()); + $this->assertSame(self::SERVICE, $span->getService()); + $this->assertSame(self::RESOURCE, $span->getResource()); + $this->assertSame(self::META_VALUE, $span->getMeta(self::META_KEY)); + } + + public function testSpanMetaRemainsImmutableAfterFinishing() + { + $span = $this->createSpan(); + $span->finish(); + + $span->setMeta(self::META_KEY, self::META_VALUE); + $this->assertNull($span->getMeta(self::META_KEY)); + } + + public function testSpanErrorAddsExpectedMeta() + { + $span = $this->createSpan(); + $span->setError(new Exception(self::EXCEPTION_MESSAGE)); + + $this->assertTrue($span->hasError()); + $this->assertEquals($span->getMeta(Meta\ERROR_MSG_KEY), self::EXCEPTION_MESSAGE); + $this->assertEquals($span->getMeta(Meta\ERROR_TYPE_KEY), Exception::class); + } + + public function testSpanErrorRemainsImmutableAfterFinishing() + { + $span = $this->createSpan(); + $span->finish(); + + $span->setError(new Exception()); + $this->assertFalse($span->hasError()); + } + + private function createSpan() + { + $tracer = Tracer::noop(); + $span = new Span( + $tracer, + self::NAME, + self::SERVICE, + self::RESOURCE, + 'abc123', + 'abc123' + ); + + return $span; + } +}