Skip to content

Commit af323a7

Browse files
committed
feat: auto-instrument HTTP requests with a wrapping span and mutable attributes
TraceRequest now opens an http.request span around the whole request (completing/failing it alongside the trace), and Span/Trace/SpanScope gain withAttributes()/attributes() so callers can attach data (e.g. response status code) before a span or trace closes. Database query tracing takes its enabled flag from config at bind time instead of reading config() on every query.
1 parent 4e87e86 commit af323a7

15 files changed

Lines changed: 291 additions & 92 deletions

File tree

src/Contracts/Tracer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
interface Tracer
1414
{
15-
public function start(string $name): Trace;
15+
public function start(string $name, array $attributes): Trace;
1616

1717
public function Span(
1818
string $name,

src/Http/Middleware/TraceRequest.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Closure;
88
use Illuminate\Http\Request;
99
use LaravelTrace\LaravelTrace\Contracts\Tracer;
10+
use LaravelTrace\LaravelTrace\Span\SpanType;
1011
use Symfony\Component\HttpFoundation\Response;
1112
use Throwable;
1213

@@ -16,24 +17,50 @@ public function __construct(
1617
private Tracer $tracer,
1718
) {}
1819

20+
/**
21+
* @throws Throwable
22+
*/
1923
public function handle(
2024
Request $request,
2125
Closure $next,
2226
): Response {
2327
$trace = $this->tracer->start(
24-
sprintf(
25-
'HTTP %s %s',
26-
$request->method(),
27-
$request->path(),
28-
),
28+
name: 'http.request',
29+
attributes: [
30+
'http.method' => $request->method(),
31+
'http.path' => $request->path(),
32+
],
33+
);
34+
35+
$span = $this->tracer->span(
36+
name: 'http.request',
37+
type: SpanType::Http,
38+
attributes: [
39+
'http.method' => $request->method(),
40+
'http.path' => $request->path(),
41+
],
2942
);
3043

3144
try {
3245
$response = $next($request);
3346

34-
// Temporary: we'll add trace completion next.
47+
$span->attributes([
48+
'http.status_code' => $response->getStatusCode(),
49+
]);
50+
51+
$span->close();
52+
53+
$this->tracer->completeTrace($trace);
54+
3555
return $response;
3656
} catch (Throwable $exception) {
57+
$span->fail($exception);
58+
59+
$this->tracer->failTrace(
60+
trace: $trace,
61+
exception: $exception,
62+
);
63+
3764
throw $exception;
3865
} finally {
3966
$this->tracer->clearContext();

src/LaravelTraceServiceProvider.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,19 @@ public function register(): void
5555
),
5656
);
5757

58-
$this->app->singleton(DatabaseQueryListener::class);
58+
$this->app->singleton(
59+
DatabaseQueryListener::class,
60+
function ($app): DatabaseQueryListener {
61+
return new DatabaseQueryListener(
62+
tracer: $app->make(TracerContract::class),
63+
enabled: (bool) $app->make('config')->get(
64+
'laravel-trace.database.enabled',
65+
true,
66+
),
67+
);
68+
},
69+
);
70+
5971
$this->app->singleton(
6072
TracerContract::class,
6173
function ($app): Tracer {

src/Span/Span.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function __construct(
1717
public string $name,
1818
public SpanType $type,
1919

20-
/** @var array<string, scalar|null> */
20+
/** @var array<string, string|int|float|bool|null> */
2121
public array $attributes,
2222

2323
public SpanStatus $status,
@@ -101,4 +101,26 @@ public static function completed(
101101
error: null,
102102
);
103103
}
104+
105+
/**
106+
* @param array<string, string|int|float|bool|null> $attributes
107+
*/
108+
public function withAttributes(array $attributes): self
109+
{
110+
return new self(
111+
id: $this->id,
112+
traceId: $this->traceId,
113+
parentId: $this->parentId,
114+
name: $this->name,
115+
type: $this->type,
116+
attributes: [
117+
...$this->attributes,
118+
...$attributes,
119+
],
120+
status: $this->status,
121+
startedAt: $this->startedAt,
122+
finishedAt: $this->finishedAt,
123+
error: $this->error,
124+
);
125+
}
104126
}

src/Trace/Trace.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,22 @@ public function __construct(
1616
public DateTimeImmutable $startedAt,
1717
public ?DateTimeImmutable $finishedAt = null,
1818
public ?TraceError $error = null,
19+
/** @var array<string, string|int|float|bool|null> */
20+
public array $attributes = [],
1921
) {}
2022

21-
public static function start(string $name): self
23+
public static function start(string $name, array $attributes = []): self
2224
{
2325
return new self(
2426
id: TraceId::generate(),
2527
name: $name,
2628
status: TraceStatus::Running,
2729
startedAt: new DateTimeImmutable,
30+
attributes: $attributes,
2831
);
2932
}
3033

31-
public function complete(DateTimeImmutable $finishedAt): self
34+
public function complete(DateTimeImmutable $finishedAt, array $attributes = []): self
3235
{
3336
return new self(
3437
id: $this->id,
@@ -37,12 +40,14 @@ public function complete(DateTimeImmutable $finishedAt): self
3740
startedAt: $this->startedAt,
3841
finishedAt: $finishedAt,
3942
error: null,
43+
attributes: $attributes,
4044
);
4145
}
4246

4347
public function fail(
4448
Throwable $exception,
4549
DateTimeImmutable $finishedAt,
50+
array $attributes = [],
4651
): self {
4752
return new self(
4853
id: $this->id,
@@ -51,6 +56,23 @@ public function fail(
5156
startedAt: $this->startedAt,
5257
finishedAt: $finishedAt,
5358
error: TraceError::fromThrowable($exception),
59+
attributes: $attributes,
60+
);
61+
}
62+
63+
public function withAttributes(array $attributes): self
64+
{
65+
return new self(
66+
id: $this->id,
67+
name: $this->name,
68+
status: $this->status,
69+
startedAt: $this->startedAt,
70+
finishedAt: $this->finishedAt,
71+
error: $this->error,
72+
attributes: [
73+
...$this->attributes,
74+
...$attributes,
75+
],
5476
);
5577
}
5678
}

src/Tracing/DatabaseQueryListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
{
1313
public function __construct(
1414
private Tracer $tracer,
15+
private bool $enabled,
1516
) {}
1617

1718
public function handle(QueryExecuted $event): void
1819
{
19-
if (! config('laravel-trace.database.enabled', true)) {
20+
if (! $this->enabled) {
2021
return;
2122
}
2223

src/Tracing/SpanScope.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class SpanScope
1313
private bool $closed = false;
1414

1515
public function __construct(
16-
private readonly Span $span,
16+
private Span $span,
1717
private readonly TraceContext $previousContext,
1818
private readonly Tracer $tracer,
1919
) {}
@@ -61,4 +61,28 @@ public function fail(Throwable $exception): Span
6161

6262
return $failed;
6363
}
64+
65+
/**
66+
* @param array<string, string|int|float|bool|null> $attributes
67+
*/
68+
public function attributes(array $attributes): self
69+
{
70+
if ($this->closed) {
71+
return $this;
72+
}
73+
74+
$this->span = $this->span->withAttributes($attributes);
75+
76+
return $this;
77+
}
78+
79+
/**
80+
* @param array<string, string|int|float|bool|null> $attributes
81+
*/
82+
public function addAttributes(array $attributes): self
83+
{
84+
$this->span = $this->span->withAttributes($attributes);
85+
86+
return $this;
87+
}
6488
}

src/Tracing/Tracer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public function __construct(
2424
private TraceRecorder $traceRecorder,
2525
) {}
2626

27-
public function start(string $name): Trace
27+
public function start(string $name, array $attributes = []): Trace
2828
{
29-
$trace = Trace::start($name);
29+
$trace = Trace::start($name, $attributes);
3030

3131
$this->setContext(
3232
new TraceContext(

tests/Feature/Http/Middleware/TraceRequestTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function () use ($tracer) {
3535
expect($tracer->context())
3636
->not->toBeNull()
3737
->and($tracer->context()?->spanId)
38-
->toBeNull();
38+
->not->toBeNull();
3939

4040
return new Response('OK');
4141
},

tests/Feature/Tracing/DatabaseTracingTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
it('records database query metadata inside an active trace', function (): void {
1111
$tracer = app(Tracer::class);
1212

13-
$tracer->start('DatabaseTest');
13+
$tracer->start('DatabaseTest', []);
1414

1515
DB::select('select 1');
1616

@@ -49,7 +49,7 @@
4949

5050
$tracer = app(Tracer::class);
5151

52-
$tracer->start('DatabaseTest');
52+
$tracer->start('DatabaseTest', []);
5353

5454
DB::select('select 1');
5555

0 commit comments

Comments
 (0)