Skip to content

Commit 0ec38de

Browse files
committed
feat: add SpanError details and update Span
1 parent d6aceba commit 0ec38de

6 files changed

Lines changed: 154 additions & 4 deletions

File tree

src/Span/Span.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DateTimeImmutable;
88
use LaravelTrace\LaravelTrace\Trace\TraceId;
9+
use Throwable;
910

1011
final readonly class Span
1112
{
@@ -18,6 +19,7 @@ public function __construct(
1819
public SpanStatus $status,
1920
public DateTimeImmutable $startedAt,
2021
public ?DateTimeImmutable $finishedAt = null,
22+
public ?SpanError $error = null,
2123
) {}
2224

2325
public static function start(
@@ -34,6 +36,37 @@ public static function start(
3436
type: $type,
3537
status: SpanStatus::Running,
3638
startedAt: new DateTimeImmutable,
39+
error: null,
40+
);
41+
}
42+
43+
public function complete(DateTimeImmutable $finishedAt): self
44+
{
45+
return new self(
46+
id: $this->id,
47+
traceId: $this->traceId,
48+
parentId: $this->parentId,
49+
name: $this->name,
50+
type: $this->type,
51+
status: SpanStatus::Completed,
52+
startedAt: $this->startedAt,
53+
finishedAt: $finishedAt,
54+
error: null,
55+
);
56+
}
57+
58+
public function fail(throwable $exception,DateTimeImmutable $finishedAt): self
59+
{
60+
return new self(
61+
id: $this->id,
62+
traceId: $this->traceId,
63+
parentId: $this->parentId,
64+
name: $this->name,
65+
type: $this->type,
66+
status: SpanStatus::Failed,
67+
startedAt: $this->startedAt,
68+
finishedAt: $finishedAt,
69+
error: SpanError::fromThrowable($exception),
3770
);
3871
}
3972
}

src/Span/SpanError.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaravelTrace\LaravelTrace\Span;
6+
7+
use Throwable;
8+
9+
final readonly class SpanError
10+
{
11+
public function __construct(
12+
public string $type,
13+
public string $message,
14+
public ?string $file,
15+
public ?int $line,
16+
) {
17+
}
18+
19+
public static function fromThrowable(Throwable $exception): self
20+
{
21+
return new self(
22+
type: $exception::class,
23+
message: $exception->getMessage(),
24+
file: $exception->getFile(),
25+
line: $exception->getLine(),
26+
);
27+
}
28+
}

src/Tracing/Tracer.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use LaravelTrace\LaravelTrace\Span\SpanType;
1313
use LaravelTrace\LaravelTrace\Trace\Trace;
1414
use LogicException;
15+
use RuntimeException;
16+
use Throwable;
1517

1618
final readonly class Tracer implements TracerContract
1719
{
@@ -84,9 +86,10 @@ public function complete(Span $span): Span
8486
);
8587
}
8688

87-
public function fail(Span $span): Span
89+
public function fail(Span $span,Throwable $exception,): Span
8890
{
8991
return $span->fail(
92+
$exception,
9093
new DateTimeImmutable,
9194
);
9295
}

tests/Unit/Span/SpanErrorTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use LaravelTrace\LaravelTrace\Span\SpanError;
6+
7+
it('creates an error from a throwable', function () {
8+
$exception = new RuntimeException(
9+
'Payment provider timed out',
10+
);
11+
12+
$error = SpanError::fromThrowable($exception);
13+
14+
expect($error->type)
15+
->toBe(RuntimeException::class)
16+
->and($error->message)
17+
->toBe('Payment provider timed out')
18+
->and($error->file)
19+
->toBe($exception->getFile())
20+
->and($error->line)
21+
->toBe($exception->getLine());
22+
});

tests/Unit/Span/SpanTest.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,20 @@
8282

8383
$finishedAt = new DateTimeImmutable;
8484

85-
$failed = $span->fail($finishedAt);
85+
$exception = new RuntimeException(
86+
'Inventory service failed',
87+
);
88+
89+
$failed = $span->fail($exception,$finishedAt);
8690

8791
expect($failed->status)
8892
->toBe(SpanStatus::Failed)
8993
->and($failed->finishedAt)
9094
->toBe($finishedAt)
91-
->and($span->status)
92-
->toBe(SpanStatus::Running);
95+
->and($failed->error)
96+
->not->toBeNull()
97+
->and($failed->error?->type)
98+
->toBe(RuntimeException::class)
99+
->and($failed->error?->message)
100+
->toBe('Inventory service failed');
93101
});

tests/Unit/Tracing/SpanScopeTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
declare(strict_types=1);
44

55
use LaravelTrace\LaravelTrace\Context\InMemoryTraceContextStore;
6+
use LaravelTrace\LaravelTrace\Span\Span;
7+
use LaravelTrace\LaravelTrace\Span\SpanStatus;
68
use LaravelTrace\LaravelTrace\Span\SpanType;
9+
use LaravelTrace\LaravelTrace\Trace\TraceId;
710
use LaravelTrace\LaravelTrace\Tracing\Tracer;
811

912
it('restores the previous context when closed', function () {
@@ -32,3 +35,56 @@
3235
expect($tracer->context())
3336
->toBe($parentContext);
3437
});
38+
39+
it('fails a span and restores the previous context', function () {
40+
$store = new InMemoryTraceContextStore();
41+
$tracer = new Tracer($store);
42+
43+
$tracer->start('CreateOrder');
44+
45+
$parent = $tracer->span(
46+
'CreateOrder',
47+
SpanType::Action,
48+
);
49+
50+
$parentContext = $tracer->context();
51+
52+
$child = $tracer->span(
53+
'ReserveInventory',
54+
SpanType::Action,
55+
);
56+
57+
$exception = new RuntimeException(
58+
'Inventory service failed',
59+
);
60+
61+
$failed = $child->fail($exception);
62+
63+
expect($failed->status)
64+
->toBe(SpanStatus::Failed)
65+
->and($failed->error?->type)
66+
->toBe(RuntimeException::class)
67+
->and($failed->error?->message)
68+
->toBe('Inventory service failed')
69+
->and($tracer->context())
70+
->toBe($parentContext);
71+
});
72+
73+
it('does not retain an error when completing a span', function () {
74+
$traceId = TraceId::generate();
75+
76+
$span = Span::start(
77+
traceId: $traceId,
78+
name: 'ReserveInventory',
79+
type: SpanType::Action,
80+
);
81+
82+
$completed = $span->complete(
83+
new DateTimeImmutable(),
84+
);
85+
86+
expect($completed->status)
87+
->toBe(SpanStatus::Completed)
88+
->and($completed->error)
89+
->toBeNull();
90+
});

0 commit comments

Comments
 (0)