Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,40 @@ Use static methods anywhere in your codebase without passing the span around:
// Set attribute on current span
Span::add('db.query_count', 5);

// Capture an exception
Span::error($exception);
// Add warning details as attributes
Span::add('warning', 'retry scheduled');
```

### Error Handling

The `setError()` method captures the exception for exporters to process:
Pass the exception to `finish()` when the span ends because of an error:

```php
$span = Span::init('api.request');

try {
// ...
} catch (Throwable $e) {
$span->setError($e);
$span->finish(error: $e);
throw $e;
}
```

Exporters access the exception via `$span->getError()` and extract what they need (message, trace, etc.).

The `level` attribute is automatically set to `error` when an error is captured. You can override it:
Use `setError()` when you need to record the error before the span ends, such as before cleanup work that should still be included in the same span.

The `level` attribute is set when the span finishes. It defaults to `error` when an error is captured and `info` otherwise. Pass a level to `finish()` to override it:

```php
$span->finish(level: 'warning', error: $e);
```

Use attributes for warning details that do not end the span:

```php
$span->setError($e);
$span->set('level', 'warning'); // override auto-detected level
Span::add('warning', 'retry scheduled');
Span::add('warning.message', $message);
```

### Distributed Tracing
Expand Down Expand Up @@ -226,7 +236,6 @@ $this->assertEquals('http.request', $spans[0]->get('action'));
| `init(string $action, ?string $traceparent): Span` | Create and store a new span |
| `current(): ?Span` | Get the current span |
| `add(string $key, scalar $value)` | Set attribute on current span |
| `error(Throwable $e)` | Capture exception on current span |
| `traceparent(): ?string` | Get traceparent header from current span |

### Span (instance)
Expand All @@ -240,7 +249,7 @@ $this->assertEquals('http.request', $spans[0]->get('action'));
| `setError(Throwable $e): self` | Capture exception |
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
ChiragAgg5k marked this conversation as resolved.
| `getError(): ?Throwable` | Get captured exception |
| `getTraceparent(): string` | Get W3C traceparent header value |
| `finish(): void` | End span and export |
| `finish(?string $level = null, ?Throwable $error = null): void` | End span and export |

### Attribute Conventions

Expand Down
2 changes: 2 additions & 0 deletions src/Span/Exporter/Exporter.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Exporter;

use Utopia\Span\Span;
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Exporter/None.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Exporter;

use Utopia\Span\Span;
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Exporter/SentryField.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Exporter;

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Exporter/Stdout.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Exporter;

use Utopia\Span\Span;
Expand Down
28 changes: 11 additions & 17 deletions src/Span/Span.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span;

use Closure;
Expand Down Expand Up @@ -143,19 +145,6 @@ public static function add(string $key, string|int|float|bool|null $value): void
self::current()?->set($key, $value);
}

/**
* Capture an exception on the current span.
*
* Convenience method to record errors without holding a span reference.
* Does nothing if no span is active.
*
* @param Throwable $error The exception to capture
*/
public static function error(Throwable $error): void
{
self::current()?->setError($error);
}

/**
* Get the traceparent header value from the current span.
*
Expand Down Expand Up @@ -256,19 +245,24 @@ public function getAttributes(): array
*
* Sets span.finished_at and span.duration, then sends to all exporters
* that pass their sampler (if any). Clears the current span from storage.
*
* @param string|null $level Level to export for this span
* @param Throwable|null $error Exception that caused the span to fail
*/
public function finish(): void
public function finish(?string $level = null, ?Throwable $error = null): void
{
if ($error instanceof \Throwable) {
$this->setError($error);
}

$finishedAt = microtime(true);
/** @var float $startedAt */
$startedAt = $this->attributes['span.started_at'];

$this->attributes['span.finished_at'] = $finishedAt;
$this->attributes['span.duration'] = $finishedAt - $startedAt;

if (!isset($this->attributes['level'])) {
$this->attributes['level'] = $this->error instanceof \Throwable ? 'error' : 'info';
}
$this->attributes['level'] = $level ?? ($this->error instanceof \Throwable ? 'error' : 'info');

foreach (self::$exporters as $config) {
try {
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Storage/Coroutine.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Storage;

use Utopia\Span\Span;
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Storage/Memory.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Storage;

use Utopia\Span\Span;
Expand Down
2 changes: 2 additions & 0 deletions src/Span/Storage/Storage.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Storage;

use Utopia\Span\Span;
Expand Down
2 changes: 2 additions & 0 deletions tests/Exporter/PrettyTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Tests\Exporter;

use PHPUnit\Framework\TestCase;
Expand Down
2 changes: 2 additions & 0 deletions tests/Exporter/SentryTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Tests\Exporter;

use PHPUnit\Framework\TestCase;
Expand Down
2 changes: 2 additions & 0 deletions tests/Exporter/StdoutTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Tests\Exporter;

use PHPUnit\Framework\TestCase;
Expand Down
43 changes: 33 additions & 10 deletions tests/SpanTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,22 +215,38 @@ public function testAddDoesNothingWhenNoCurrentSpan(): void
$this->assertNull(Span::current());
}

public function testErrorSetsErrorOnCurrentSpan(): void
public function testFinishAcceptsError(): void
{
$span = Span::init('test');
$span = new Span();
$error = new RuntimeException('Test');

Span::error($error);
$span->finish(error: $error);

$this->assertSame($error, $span->getError());
}

public function testErrorDoesNothingWhenNoCurrentSpan(): void
public function testFinishWithErrorSetsLevelError(): void
{
// Should not throw
Span::error(new RuntimeException('Test'));
$span = new Span();

$this->assertNull(Span::current());
$span->finish(error: new RuntimeException('Test'));

$this->assertSame('error', $span->get('level'));
}

public function testFinishWithErrorExportsErrorSpan(): void
{
$exported = [];
$exporter = $this->createExporter($exported);
$error = new RuntimeException('Test');

Span::addExporter($exporter);

$span = Span::init('test');
$span->finish(error: $error);

$this->assertCount(1, $exported);
$this->assertSame($error, $exported[0]->getError());
}

public function testSamplerFiltersExport(): void
Expand Down Expand Up @@ -575,14 +591,21 @@ public function testFinishSetsLevelErrorWhenErrorSet(): void
$this->assertSame('error', $span->get('level'));
}

public function testFinishDoesNotOverrideExplicitLevel(): void
public function testFinishAcceptsLevelOverride(): void
{
$span = new Span();
$span->finish(level: 'warning', error: new RuntimeException('Test'));

$this->assertSame('warning', $span->get('level'));
}

public function testFinishOwnsLevelAttribute(): void
{
$span = new Span();
$span->set('level', 'warning');
$span->setError(new RuntimeException('Test'));
$span->finish();

$this->assertSame('warning', $span->get('level'));
$this->assertSame('info', $span->get('level'));
}

public function testLevelNotSetBeforeFinish(): void
Expand Down
2 changes: 2 additions & 0 deletions tests/Storage/AutoTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Utopia\Span\Tests\Storage;

use PHPUnit\Framework\TestCase;
Expand Down
Loading