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
12 changes: 12 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

There are some breaking changes you should be aware of. We've categorized them so you can prioritize. This guide covers the most common cases. Edge cases may not be covered, and PRs to improve it are welcome.

## From v3.0 to v3.1

`Tracer::currentTraceId()`, `currentSpanId()`, and `traceParent()` now return `?string`, with `null` when no trace is active. Recorders skip silently without an active trace instead of recording orphan spans.

If you use `spatie/laravel-flare` no changes are needed. For framework agnostic apps, replace `registerFlareHandlers()` with `$flare->start()`, which also opens the lifecycle:

```php
$flare = Flare::make($config)->start();
```

For subtask mode (queue workers, long-running processes), keep using `$flare->lifecycle->startSubtask()` per work unit.

## From v2 to v3

The new version of the package adds better support for logging and tracing lifetimes. We don't expect upgrading to require a lot of code changes for typical applications.
Expand Down
35 changes: 35 additions & 0 deletions src/Flare.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,41 @@ public static function make(
return $container->get(Flare::class);
}

public function start(
?int $timeUnixNano = null,
array $attributes = [],
?string $traceparent = null,
): self {
if ($this->lifecycle->usesSubtasks) {
throw new Exception(
'Flare::start() is for the main lifecycle. In subtask mode, use $flare->lifecycle->startSubtask() per work unit.'
);
}

$this->reporter->registerFlareHandlers();
$this->lifecycle->start($timeUnixNano, $attributes, $traceparent);

return $this;
}

public function end(
?int $timeUnixNano = null,
array $additionalTerminationAttributes = [],
array $additionalApplicationAttributes = [],
): void {
if ($this->lifecycle->usesSubtasks) {
throw new Exception(
'Flare::end() is for the main lifecycle. In subtask mode, use $flare->lifecycle->endSubtask() per work unit.'
);
}

$this->lifecycle->terminated(
$timeUnixNano,
$additionalTerminationAttributes,
$additionalApplicationAttributes,
);
}

public function registerFlareHandlers(): self
{
$this->reporter->registerFlareHandlers();
Expand Down
8 changes: 7 additions & 1 deletion src/Recorders/SpansRecorder.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ final protected function startSpan(
return null;
}

$currentTraceId = $this->tracer->currentTraceId();

if ($currentTraceId === null) {
return null;
}

if ($nameAndAttributes !== null) {
['name' => $name, 'attributes' => $attributes] = $nameAndAttributes();
} else {
Expand All @@ -98,7 +104,7 @@ final protected function startSpan(
$spanId = $this->tracer->nextSpanId();

$span = new Span(
traceId: $this->tracer->currentTraceId(),
traceId: $currentTraceId,
spanId: $spanId,
parentSpanId: $parentSpanId,
name: $name,
Expand Down
2 changes: 2 additions & 0 deletions src/Support/Lifecycle.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ public function terminated(
$this->stage = LifecycleStage::Terminated;

if ($this->tracer->sampling === false) {
$this->tracer->endTrace();

return;
}

Expand Down
90 changes: 47 additions & 43 deletions src/Tracer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ class Tracer
/** @var array @param array{max_spans: int, max_attributes_per_span: int, max_span_events_per_span: int, max_attributes_per_span_event: int}|null */
public readonly array $limits;

protected string $currentTraceId;
protected ?string $currentTraceId = null;

protected string $currentSpanId;
protected ?string $currentSpanId = null;

protected bool $currentSpanIdAvailable;
protected bool $currentSpanIdAvailable = true;

/**
* @param array{max_spans: int, max_attributes_per_span: int, max_span_events_per_span: int, max_attributes_per_span_event: int}|null $limits
Expand All @@ -64,10 +64,6 @@ public function __construct(
public readonly bool $disabled = false,
protected Closure|null $gracefulSpanEnderClosure = null,
) {
$this->currentTraceId = $this->ids->trace();
$this->currentSpanId = $this->ids->span();
$this->currentSpanIdAvailable = true;

$this->limits = [
'max_spans' => $limits['max_spans'] ?? self::DEFAULT_MAX_SPANS_LIMIT,
'max_attributes_per_span' => $limits['max_attributes_per_span'] ?? self::DEFAULT_MAX_ATTRIBUTES_PER_SPAN_LIMIT,
Expand All @@ -81,35 +77,38 @@ public function startTrace(
?string $spanId = null,
?string $traceParent = null,
): bool {
if ($this->disabled === true) {
return false;
if ($this->currentTraceId !== null) {
return $this->sampling;
}

if ($this->sampling) {
return $this->sampling;
if (($traceId === null) !== ($spanId === null)) {
throw new Exception('If one of traceId or spanId is provided, both must be provided.');
}

$parentSampled = null;
$currentSpanAvailable = true;

if ($traceParent !== null) {
$parsed = $this->ids->parseTraceparent($traceParent);
$parsedTraceParent = $traceParent !== null
? $this->ids->parseTraceparent($traceParent)
: null;

if ($parsed !== null) {
$traceId = $parsed['traceId'];
$spanId = $parsed['parentSpanId'];
$parentSampled = $parsed['sampling'];
}
if ($traceId !== null && $spanId !== null) {
$currentSpanAvailable = false;
}

if (($traceId === null) !== ($spanId === null)) {
throw new Exception('If one of traceId or spanId is provided, both must be provided.');
if ($parsedTraceParent) {
$traceId = $parsedTraceParent['traceId'];
$spanId = $parsedTraceParent['parentSpanId'];
$parentSampled = $parsedTraceParent['sampling'];
$currentSpanAvailable = false;
}

if ($traceId !== null && $spanId !== null) {
$this->currentTraceId = $traceId;
$this->currentSpanId = $spanId;
$this->currentSpanIdAvailable = false;
$this->spans = [];
$this->currentTraceId = $traceId ?? $this->ids->trace();
$this->currentSpanId = $spanId ?? $this->ids->span();
$this->currentSpanIdAvailable = $currentSpanAvailable;

if ($this->disabled === true) {
return false;
}

return $this->sampling = $this->sampler->shouldSample(
Expand All @@ -124,12 +123,8 @@ public function endTrace(): void
$this->sampler->reset();
}

if ($this->sampling === false) {
return;
}

$this->currentTraceId = $this->ids->trace();
$this->currentSpanId = $this->ids->span();
$this->currentTraceId = null;
$this->currentSpanId = null;
$this->currentSpanIdAvailable = true;
$this->sampling = false;

Expand Down Expand Up @@ -173,11 +168,8 @@ public function unsample(): void
}

$this->paused = false;

if ($this->sampling === false) {
return;
}

$this->currentTraceId = null;
$this->currentSpanId = null;
$this->currentSpanIdAvailable = true;
$this->sampling = false;
$this->spans = [];
Expand Down Expand Up @@ -213,12 +205,12 @@ public function isSamplingPaused(): bool
return $this->paused;
}

public function currentTraceId(): string
public function currentTraceId(): ?string
{
return $this->currentTraceId;
}

public function currentSpanId(): string
public function currentSpanId(): ?string
{
return $this->currentSpanId;
}
Expand All @@ -232,7 +224,7 @@ public function currentParentSpanId(): ?string

public function nextSpanId(): string
{
if ($this->sampling && $this->currentSpanIdAvailable === true) {
if ($this->sampling && $this->currentSpanIdAvailable === true && $this->currentSpanId !== null) {
$this->currentSpanIdAvailable = false;

return $this->currentSpanId;
Expand All @@ -241,11 +233,15 @@ public function nextSpanId(): string
return $this->ids->span();
}

public function traceParent(): string
public function traceParent(): ?string
{
if ($this->currentTraceId === null || $this->currentSpanId === null) {
return null;
}

return $this->ids->traceParent(
$this->currentTraceId ?? '',
$this->currentSpanId ?? '',
$this->currentTraceId,
$this->currentSpanId,
$this->isSampling(),
);
}
Expand Down Expand Up @@ -283,6 +279,10 @@ public function hasCurrentSpan(?FlareSpanType $spanType = null): bool

public function currentSpan(): ?Span
{
if ($this->currentSpanId === null) {
return null;
}

return $this->spans[$this->currentSpanId] ?? null;
}

Expand Down Expand Up @@ -312,12 +312,16 @@ public function startSpan(
?int $time = null,
array $attributes = [],
): Span|AddSpanResult {
if ($this->currentTraceId === null) {
throw new Exception('Cannot start a span without an active trace.');
}

// Order of operations is important here, do not inline!
$parentSpanId = $this->currentParentSpanId();
$spanId = $this->nextSpanId();

$span = new Span(
traceId: $this->currentTraceId ?? $this->ids->trace(),
traceId: $this->currentTraceId,
spanId: $spanId,
parentSpanId: $parentSpanId,
name: $name,
Expand Down
18 changes: 18 additions & 0 deletions tests/FlareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@
);
});

it('can start and end a flare lifecycle', function () {
$flare = setupFlare(alwaysSampleTraces: true)->start();

expect($flare->tracer->isSampling())->toBeTrue();

$flare->end();

expect($flare->tracer->isSampling())->toBeFalse();
});


it('can reset queued exceptions', function () {
$flare = setupFlare(fn (FlareConfig $config) => $config->collectContext());
Expand Down Expand Up @@ -375,6 +385,8 @@
it('can add queries', function () {
$flare = setupFlare(fn (FlareConfig $config) => $config->collectQueries());

$flare->tracer->startTrace();

$flare->query()->record(
'select * from users where id = ?',
TimeHelper::milliseconds(250),
Expand Down Expand Up @@ -423,6 +435,8 @@
it('can begin and commit transactions', function () {
$flare = setupFlare(fn (FlareConfig $config) => $config->collectTransactions());

$flare->tracer->startTrace();

$flare->transaction()->recordBegin();

FakeTime::setup('2019-01-01 12:34:57'); // One second later 1546346097000000
Expand All @@ -446,6 +460,8 @@
it('can begin and rollback transactions', function () {
$flare = setupFlare(fn (FlareConfig $config) => $config->collectTransactions());

$flare->tracer->startTrace();

$flare->transaction()->recordBegin();

FakeTime::setup('2019-01-01 12:34:57'); // One second later 1546346097000000
Expand Down Expand Up @@ -831,6 +847,8 @@
])
);

$flare->tracer->startTrace();

$flare->recorder('spans')->record('Hi', duration: TimeHelper::milliseconds(300));

$throwable = new RuntimeException('This is a test');
Expand Down
2 changes: 2 additions & 0 deletions tests/Recorders/QueryRecorder/QueryRecorderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@
]
);

$flare->tracer->startTrace();

$recorder->record('select * from users where id = ?', TimeHelper::milliseconds(299), ['id' => 1], 'users', 'mysql');

expect($recorder->getSpans())->toHaveCount(1);
Expand Down
4 changes: 4 additions & 0 deletions tests/Recorders/SpanEventsRecorderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
'with_traces' => true,
]);

$flare->tracer->startTrace();

$span = $flare->tracer->startSpan(
'root span'
);
Expand Down Expand Up @@ -236,6 +238,8 @@ public function record(string $message): ?SpanEvent
'find_origin' => false,
]);

$flare->tracer->startTrace();

$span = $flare->tracer->startSpan('Parent Span');

$recorder->record('Hello World');
Expand Down
8 changes: 8 additions & 0 deletions tests/Recorders/SpansRecorderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
'with_traces' => false,
]);

$flare->tracer->startTrace();

$recorder->record('Span', 100);

$spans = $recorder->getSpans();
Expand All @@ -65,6 +67,8 @@
'max_items_with_errors' => 35,
]);

$flare->tracer->startTrace();

foreach (range(1, 40) as $i) {
$recorder->pushSpan("Span - Hello {$i}");
$recorder->popSpan();
Expand All @@ -83,6 +87,8 @@
'max_items_with_errors' => null,
]);

$flare->tracer->startTrace();

foreach (range(1, 250) as $i) {
$recorder->pushSpan("Hello {$i}");
$recorder->popSpan();
Expand Down Expand Up @@ -164,6 +170,8 @@ public function recordMessage(string $message): ?Span

$flare = setupFlare(alwaysSampleTraces: true);

$flare->tracer->startTrace();

expect(fn () => (new TestConcreteSpanRecorderExecution($flare->tracer, $flare->backTracer, config: [
'with_traces' => true,
'with_errors' => true,
Expand Down
Loading
Loading