Skip to content

Commit 2dead93

Browse files
committed
feat(sentry): release, server_name, sdk and runtime
1 parent 654805e commit 2dead93

9 files changed

Lines changed: 86 additions & 37 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
- name: Check formatting
4949
run: composer format
5050

51-
analyse:
51+
analyze:
5252
runs-on: ubuntu-latest
5353

5454
steps:
@@ -64,4 +64,22 @@ jobs:
6464
run: composer install --prefer-dist --no-progress
6565

6666
- name: Run PHPStan
67-
run: composer analyse
67+
run: composer analyze
68+
69+
refactor:
70+
runs-on: ubuntu-latest
71+
72+
steps:
73+
- uses: actions/checkout@v4
74+
75+
- name: Setup PHP
76+
uses: shivammathur/setup-php@v2
77+
with:
78+
php-version: '8.3'
79+
coverage: none
80+
81+
- name: Install dependencies
82+
run: composer install --prefer-dist --no-progress
83+
84+
- name: Check refactoring
85+
run: composer refactor:check

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020
"laravel/pint": "^1.0",
2121
"phpstan/phpstan": "^2.0",
2222
"phpunit/phpunit": "^10.0",
23-
"swoole/ide-helper": "^5.0"
23+
"swoole/ide-helper": "^5.0",
24+
"rector/rector": "^2.3"
2425
},
2526
"scripts": {
2627
"format": "pint --test",
2728
"format:fix": "pint",
2829
"test": "phpunit",
29-
"analyse": "phpstan analyse"
30+
"analyze": "phpstan analyze",
31+
"refactor": "rector",
32+
"refactor:check": "rector --dry-run"
3033
},
3134
"suggest": {
3235
"ext-swoole": "Required for coroutine-based storage"

rector.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
7+
return RectorConfig::configure()
8+
->withPaths([
9+
__DIR__ . '/src',
10+
__DIR__ . '/tests',
11+
])
12+
->withPhpSets(php84: true)
13+
->withPreparedSets(
14+
deadCode: true,
15+
codeQuality: true,
16+
typeDeclarations: true,
17+
earlyReturn: true,
18+
);

src/Span/Exporter/Sentry.php

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,26 @@
1010
* Only spans with errors are sent. Non-error spans are silently skipped.
1111
* Use the Stdout exporter for non-error spans.
1212
*/
13-
class Sentry implements Exporter
13+
readonly class Sentry implements Exporter
1414
{
15-
private string $dsn;
1615
private string $endpoint;
1716
private string $publicKey;
1817
private string $projectId;
19-
private ?string $environment;
2018

2119
/**
2220
* Create a new Sentry exporter.
2321
*
2422
* @param string $dsn Sentry DSN (e.g., https://key@sentry.io/123)
2523
* @param string|null $environment Optional environment name (e.g., 'production')
24+
* @param string|null $release Optional release/version identifier (e.g., commit hash)
25+
* @param string|null $serverName Optional server name/identifier
2626
*/
27-
public function __construct(string $dsn, ?string $environment = null)
28-
{
29-
$this->dsn = $dsn;
30-
$this->environment = $environment;
31-
$this->parseDsn($dsn);
32-
}
33-
34-
private function parseDsn(string $dsn): void
35-
{
27+
public function __construct(
28+
private string $dsn,
29+
private ?string $environment = null,
30+
private ?string $release = null,
31+
private ?string $serverName = null
32+
) {
3633
$parsed = parse_url($dsn);
3734

3835
if ($parsed === false) {
@@ -94,7 +91,7 @@ private function buildEnvelope(Span $span): ?string
9491
{
9592
$error = $span->getError();
9693

97-
if ($error === null) {
94+
if (!$error instanceof \Throwable) {
9895
return null;
9996
}
10097

@@ -103,6 +100,7 @@ private function buildEnvelope(Span $span): ?string
103100
$traceId = (string) ($attributes['span.trace_id'] ?? '');
104101
$spanId = (string) ($attributes['span.id'] ?? '');
105102
$parentId = $attributes['span.parent_id'] ?? null;
103+
$startedAt = (float) ($attributes['span.started_at'] ?? microtime(true));
106104
$finishedAt = (float) ($attributes['span.finished_at'] ?? microtime(true));
107105
$action = $span->getAction();
108106

@@ -151,11 +149,18 @@ private function buildEnvelope(Span $span): ?string
151149
$payloadData = [
152150
'level' => 'error',
153151
'platform' => 'php',
152+
'sdk' => ['name' => 'utopia-php/span'],
153+
'start_timestamp' => $startedAt,
154154
'timestamp' => $finishedAt,
155155
'transaction' => $action,
156156
'message' => $error->getMessage(),
157157
'contexts' => [
158158
'trace' => $traceContext,
159+
'runtime' => [
160+
'name' => 'php',
161+
'version' => PHP_VERSION,
162+
'sapi' => PHP_SAPI,
163+
],
159164
],
160165
'exception' => [
161166
'values' => [[
@@ -171,6 +176,14 @@ private function buildEnvelope(Span $span): ?string
171176
$payloadData['environment'] = $this->environment;
172177
}
173178

179+
if ($this->release !== null) {
180+
$payloadData['release'] = $this->release;
181+
}
182+
183+
if ($this->serverName !== null) {
184+
$payloadData['server_name'] = $this->serverName;
185+
}
186+
174187
$payload = json_encode($payloadData);
175188

176189
if ($header === false || $itemHeader === false || $payload === false) {

src/Span/Exporter/Stdout.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Writes error spans to stderr and non-error spans to stdout.
1111
* Error stacktraces are truncated to keep output readable.
1212
*/
13-
class Stdout implements Exporter
13+
readonly class Stdout implements Exporter
1414
{
1515
/**
1616
* Create a new Stdout exporter.
@@ -27,7 +27,7 @@ public function export(Span $span): void
2727
$data = ['action' => $span->getAction()] + $span->getAttributes();
2828
$error = $span->getError();
2929

30-
if ($error !== null) {
30+
if ($error instanceof \Throwable) {
3131
$data['error.type'] = $error::class;
3232
$data['error.message'] = $error->getMessage();
3333
$data['error.code'] = $error->getCode();
@@ -36,7 +36,7 @@ public function export(Span $span): void
3636

3737
$trace = $error->getTrace();
3838
$limited = array_slice($trace, 0, $this->maxTraceFrames);
39-
$data['error.trace'] = array_map(fn ($frame) => [
39+
$data['error.trace'] = array_map(fn (array $frame): array => [
4040
'file' => $frame['file'] ?? null,
4141
'line' => $frame['line'] ?? null,
4242
'function' => $frame['function'],
@@ -53,7 +53,7 @@ public function export(Span $span): void
5353
return;
5454
}
5555

56-
$stream = $error !== null ? STDERR : STDOUT;
56+
$stream = $error instanceof \Throwable ? STDERR : STDOUT;
5757

5858
fwrite($stream, $output . PHP_EOL);
5959
}

src/Span/Span.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ class Span
2121
*/
2222
private array $attributes = [];
2323

24-
private string $action;
25-
2624
private ?Throwable $error = null;
2725

28-
public function __construct(string $action = 'unknown')
26+
public function __construct(private readonly string $action = 'unknown')
2927
{
30-
$this->action = $action;
3128
$this->attributes['span.trace_id'] = bin2hex(random_bytes(16));
3229
$this->attributes['span.id'] = bin2hex(random_bytes(8));
3330
$this->attributes['span.started_at'] = microtime(true);
@@ -115,7 +112,7 @@ public static function init(string $action, ?string $traceparent = null): self
115112
}
116113
}
117114

118-
if (self::$storage !== null) {
115+
if (self::$storage instanceof \Utopia\Span\Storage\Storage) {
119116
self::$storage->set($span);
120117
}
121118

@@ -282,7 +279,7 @@ public function finish(): void
282279
}
283280
}
284281

285-
if (self::$storage !== null) {
282+
if (self::$storage instanceof \Utopia\Span\Storage\Storage) {
286283
self::$storage->set(null);
287284
}
288285
}

src/Span/Storage/Auto.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212
class Auto implements Storage
1313
{
14-
private Memory $memory;
14+
private readonly Memory $memory;
1515
private ?Coroutine $coroutine = null;
1616

1717
public function __construct()
@@ -25,7 +25,7 @@ public function __construct()
2525

2626
public function get(): ?Span
2727
{
28-
if ($this->coroutine !== null && $this->isInCoroutine()) {
28+
if ($this->coroutine instanceof \Utopia\Span\Storage\Coroutine && $this->isInCoroutine()) {
2929
return $this->coroutine->get();
3030
}
3131

@@ -34,7 +34,7 @@ public function get(): ?Span
3434

3535
public function set(?Span $span): void
3636
{
37-
if ($this->coroutine !== null && $this->isInCoroutine()) {
37+
if ($this->coroutine instanceof \Utopia\Span\Storage\Coroutine && $this->isInCoroutine()) {
3838
$this->coroutine->set($span);
3939
return;
4040
}

tests/Exporter/StdoutTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function testExportWritesJson(): void
1717

1818
ob_start();
1919
$exporter->export($span);
20-
$output = ob_get_clean();
20+
ob_get_clean();
2121

2222
// Output goes to STDOUT, not output buffer in CLI
2323
// Just verify no exception is thrown
@@ -65,7 +65,7 @@ public function testExportHandlesError(): void
6565

6666
public function testExportIncludesSpanMetadata(): void
6767
{
68-
$exporter = new Stdout();
68+
new Stdout();
6969
$span = new Span();
7070
$span->finish();
7171

tests/SpanTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public function testSamplerFiltersExport(): void
239239
$exporter = $this->createExporter($exported);
240240

241241
// Only export spans with errors
242-
Span::addExporter($exporter, fn (Span $s) => $s->getError() !== null);
242+
Span::addExporter($exporter, fn (Span $s): bool => $s->getError() instanceof \Throwable);
243243

244244
$span1 = Span::init('test');
245245
$span1->finish();
@@ -257,7 +257,7 @@ public function testSamplerReceivesSpan(): void
257257
$exporter = $this->createExporter($exported);
258258
$sampledSpan = null;
259259

260-
Span::addExporter($exporter, function (Span $s) use (&$sampledSpan) {
260+
Span::addExporter($exporter, function (Span $s) use (&$sampledSpan): bool {
261261
$sampledSpan = $s;
262262
return true;
263263
});
@@ -451,8 +451,8 @@ public function testMultipleSamplersAllMustPass(): void
451451
$exported = [];
452452
$exporter = $this->createExporter($exported);
453453

454-
Span::addExporter($exporter, fn (Span $s) => true);
455-
Span::addExporter($exporter, fn (Span $s) => false);
454+
Span::addExporter($exporter, fn (Span $s): bool => true);
455+
Span::addExporter($exporter, fn (Span $s): bool => false);
456456

457457
$span = Span::init('test');
458458
$span->finish();
@@ -465,7 +465,7 @@ public function testSamplerCanFilterByDuration(): void
465465
$exported = [];
466466
$exporter = $this->createExporter($exported);
467467

468-
Span::addExporter($exporter, fn (Span $s) => $s->get('span.duration') > 0.005);
468+
Span::addExporter($exporter, fn (Span $s): bool => $s->get('span.duration') > 0.005);
469469

470470
$fastSpan = Span::init('test');
471471
$fastSpan->finish();

0 commit comments

Comments
 (0)