From 3c18f3a4ab477a6f094e88260e40b16e4482d02d Mon Sep 17 00:00:00 2001 From: Onyemenam Ndubuisi Date: Fri, 26 Feb 2021 17:20:02 +0100 Subject: [PATCH] Feature/add record exception (#255) * Add recordException event, test and examples Fix phpstan errors * Attempt to fix test failures --- api/Trace/Span.php | 9 ++++++++ examples/AlwaysOnJaegerExample.php | 6 +++++ examples/AlwaysOnZipkinExample.php | 6 +++++ sdk/Trace/NoopSpan.php | 15 +++++++++++++ sdk/Trace/Span.php | 15 +++++++++++++ tests/Sdk/Unit/Trace/NoopSpanTest.php | 15 +++++++++++++ tests/Sdk/Unit/Trace/TracingTest.php | 32 +++++++++++++++++++++++++++ 7 files changed, 98 insertions(+) diff --git a/api/Trace/Span.php b/api/Trace/Span.php index 1fcc9d67..311ea566 100644 --- a/api/Trace/Span.php +++ b/api/Trace/Span.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace OpenTelemetry\Trace; +use Exception; + interface Span extends SpanStatus, SpanKind { public function getSpanName(): string; @@ -58,6 +60,13 @@ interface Span extends SpanStatus, SpanKind */ public function addLink(SpanContext $context, ?Attributes $attributes = null): Span; + /** + * + * @param Exception $exception + * @return Span Must return $this to allow setting multiple attributes at once in a chain. + */ + public function recordException(Exception $exception): Span; + /** * Calling this method is highly discouraged; the name should be set on creation and left alone. * @param string $name diff --git a/examples/AlwaysOnJaegerExample.php b/examples/AlwaysOnJaegerExample.php index 3ef21be2..1dc138a6 100644 --- a/examples/AlwaysOnJaegerExample.php +++ b/examples/AlwaysOnJaegerExample.php @@ -56,6 +56,12 @@ if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) { 'id' => md5((string) microtime(true)), ])); + try { + throw new Exception('Record exception test event'); + } catch (Exception $exception) { + $span->recordException($exception); + } + $tracer->endActiveSpan(); } echo PHP_EOL . 'AlwaysOnJaegerExample complete! See the results at http://localhost:16686/'; diff --git a/examples/AlwaysOnZipkinExample.php b/examples/AlwaysOnZipkinExample.php index f276a82f..4d9f3e7e 100644 --- a/examples/AlwaysOnZipkinExample.php +++ b/examples/AlwaysOnZipkinExample.php @@ -56,6 +56,12 @@ if (SamplingResult::RECORD_AND_SAMPLED === $samplingResult->getDecision()) { 'id' => md5((string) microtime(true)), ])); + try { + throw new Exception('Record exception test event'); + } catch (Exception $exception) { + $span->recordException($exception); + } + $tracer->endActiveSpan(); } echo PHP_EOL . 'AlwaysOnZipkinExample complete! See the results at http://localhost:9411/'; diff --git a/sdk/Trace/NoopSpan.php b/sdk/Trace/NoopSpan.php index d5e86e1f..ddc7ec87 100644 --- a/sdk/Trace/NoopSpan.php +++ b/sdk/Trace/NoopSpan.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OpenTelemetry\Sdk\Trace; +use Exception; use OpenTelemetry\Trace as API; class NoopSpan implements \OpenTelemetry\Trace\Span @@ -118,6 +119,20 @@ class NoopSpan implements \OpenTelemetry\Trace\Span return $this; } + public function recordException(Exception $exception): API\Span + { + $attributes = new Attributes( + [ + 'exception.type' => get_class($exception), + 'exception.message' => $exception->getMessage(), + 'exception.stacktrace' => $exception->getTraceAsString(), + ] + ); + $timestamp = time(); + + return $this->addEvent('exception', $timestamp, $attributes); + } + public function updateName(string $name): API\Span { return $this; diff --git a/sdk/Trace/Span.php b/sdk/Trace/Span.php index a3b20a69..8323832b 100644 --- a/sdk/Trace/Span.php +++ b/sdk/Trace/Span.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OpenTelemetry\Sdk\Trace; +use Exception; use OpenTelemetry\Sdk\Resource\ResourceInfo; use OpenTelemetry\Trace as API; @@ -207,6 +208,20 @@ class Span implements API\Span return $this; } + public function recordException(Exception $exception): API\Span + { + $attributes = new Attributes( + [ + 'exception.type' => get_class($exception), + 'exception.message' => $exception->getMessage(), + 'exception.stacktrace' => $exception->getTraceAsString(), + ] + ); + $timestamp = time(); + + return $this->addEvent('exception', $timestamp, $attributes); + } + public function getEvents(): API\Events { return $this->events; diff --git a/tests/Sdk/Unit/Trace/NoopSpanTest.php b/tests/Sdk/Unit/Trace/NoopSpanTest.php index bd64d222..8f330d39 100644 --- a/tests/Sdk/Unit/Trace/NoopSpanTest.php +++ b/tests/Sdk/Unit/Trace/NoopSpanTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OpenTelemetry\Tests\Sdk\Unit\Trace; +use Exception; use OpenTelemetry\Sdk\Trace\Clock; use OpenTelemetry\Sdk\Trace\NoopSpan; use OpenTelemetry\Sdk\Trace\SpanStatus; @@ -51,6 +52,20 @@ class NoopSpanTest extends TestCase $this->assertEmpty($this->span->getEvents()); } + /** @test */ + public function eventsCollectionShouldBeEmptyEvenAfterRecordExceptionEventUpdate() + { + $this->assertEmpty($this->span->getEvents()); + + try { + throw new Exception('Record exception test event'); + } catch (Exception $exception) { + $this->span->recordException($exception); + } + + $this->assertEmpty($this->span->getEvents()); + } + /** @test */ public function itsStatusShouldBeOkAndNoUpdatesShouldChangeIt() { diff --git a/tests/Sdk/Unit/Trace/TracingTest.php b/tests/Sdk/Unit/Trace/TracingTest.php index e896482c..e5e38c5c 100644 --- a/tests/Sdk/Unit/Trace/TracingTest.php +++ b/tests/Sdk/Unit/Trace/TracingTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace OpenTelemetry\Tests\Sdk\Unit\Trace; +use Exception; use function iterator_to_array; use OpenTelemetry\Sdk\Resource\ResourceConstants; use OpenTelemetry\Sdk\Resource\ResourceInfo; @@ -286,6 +287,37 @@ class TracingTest extends TestCase $this->assertCount(2, $span->getEvents()); } + public function testRecordExceptionEventRegistration() + { + $tracerProvider = new SDK\TracerProvider(); + $tracer = $tracerProvider->getTracer('OpenTelemetry.TracingTest'); + $span = $tracer->startAndActivateSpan('zerodivisiontest'); + + try { + throw new Exception('Record exception test event'); + } catch (Exception $exception) { + $span->recordException($exception); + } + + $events = $span->getEvents(); + self::assertCount(1, $events); + + [$event] = iterator_to_array($events); + + $this->assertSame($event->getName(), 'exception'); + $this->assertArrayHasKey('exception.type', iterator_to_array($event->getAttributes())); + $this->assertArrayHasKey('exception.message', iterator_to_array($event->getAttributes())); + $this->assertArrayHasKey('exception.stacktrace', iterator_to_array($event->getAttributes())); + + $timestamp = Clock::get()->timestamp(); + $span->addEvent('update', $timestamp) + ->setAttribute('space', 'guard.session') + ->setAttribute('id', 67235) + ->setAttribute('active_at', time()); + + $this->assertCount(2, $span->getEvents()); + } + public function testAddEventWhenNotRecording() { $tracerProvider = new SDK\TracerProvider();