Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(http-client/injecting): adding 'withTrace' macro for Http Client #6

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
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
---
title: Continuing traces from other systems
title: Continuing tracing with other systems
weight: 2
---

## From other system

If your Laravel app is used in a bigger system, where one of the other apps started a trace, your Laravel app might get called by that other apps with [a `traceparent` header](https://uptrace.dev/opentelemetry/opentelemetry-traceparent.html). This header contains the id of trace that was started.

To have all measurements you take use that trace id from the header, you can use the `ContinueTrace` middleware. This middleware will look for incoming requests that have a `traceparent` header and use the given trace id.
Expand All @@ -24,4 +26,15 @@ class Kernel extends HttpKernel
}
```

## To other system

In another case, if your Laravel app calls other systems, you can inject `traceparent` HTTP Header onto the outgoing request, by calling macro `withTrace` in HTTP Facade.

It automatically adds current span context id to HTTP header for your outgoing request.

```php
use Http;

Http::withTrace()->post('https://example.com')
```

24 changes: 24 additions & 0 deletions src/Http/Client/Macro.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Spatie\OpenTelemetry\Http\Client;

use Illuminate\Http\Client\PendingRequest;
use Spatie\OpenTelemetry\Facades\Measure as MeasureFacade;
use Spatie\OpenTelemetry\Support\Injectors\TextInjector;

class Macro
{
public static function apply() {
PendingRequest::macro('withTrace', function () {
$headers = [];

if ($span = MeasureFacade::currentSpan()) {
$headers['traceparent'] = "";

TextInjector::Inject($headers['traceparent'], $span);
}

return PendingRequest::withHeaders($headers);
});
}
}
5 changes: 4 additions & 1 deletion src/OpenTelemetryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Spatie\OpenTelemetry\Drivers\Driver;
use Spatie\OpenTelemetry\Drivers\MultiDriver;
use Spatie\OpenTelemetry\Drivers\Multidriver;
use Spatie\OpenTelemetry\Http\Client\Macro as HttpClientMacro;
use Spatie\OpenTelemetry\Support\IdGenerator;
use Spatie\OpenTelemetry\Support\Measure;
use Spatie\OpenTelemetry\Support\Samplers\Sampler;
Expand Down Expand Up @@ -39,6 +40,8 @@ public function bootingPackage()

$action->execute();
}

HttpClientMacro::apply();
}

protected function getMultiDriver(): MultiDriver
Expand Down
19 changes: 19 additions & 0 deletions src/Support/Injectors/TextInjector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Spatie\OpenTelemetry\Support\Injectors;

use Spatie\OpenTelemetry\Support\Span;

class TextInjector
{
static public function Inject(string &$data, Span $span): void
{
$data = sprintf(
'%s-%s-%s-%02x',
'00',
$span->trace()->id(),
$span->id(),
$span->flags(),
);
}
}
5 changes: 5 additions & 0 deletions src/Support/Measure.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public function getSpan(string $name): ?Span
return $this->startedSpans[$name] ?? null;
}

public function currentSpan(): ?Span
{
return $this->parentSpan;
}

public function startedSpanNames(): array
{
return array_keys($this->startedSpans);
Expand Down
19 changes: 19 additions & 0 deletions src/Support/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Span

protected string $id;

protected int $flags;

/**
* @var array<string, mixed>
*/
Expand Down Expand Up @@ -47,6 +49,23 @@ public function parentSpan(): ?Span
return $this->parentSpan;
}

public function trace(): ?Trace
{
return $this->trace;
}

public function flags(): int
{
/**
* TODO: flags MUST be propagated from Measure Lottery or Parent span when new span created.
* By design, all spans, that have 0x00 flag (DEFAULT) - running lottery for trace
* And all 0x01 flags (SPAN_TRACED) MUST be sampled, without any lottery,
* If not, it will be an useless traces with "clear windows"
*
*/
return 0x01;
}

public function stop(): self
{
$this->stopWatch->stop();
Expand Down
52 changes: 52 additions & 0 deletions tests/HttpInjectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

use Spatie\OpenTelemetry\Facades\Measure;
use Spatie\OpenTelemetry\Support\Injectors\TextInjector;

it('injects current span context name to Laravel HTTP (outgoing) requests', function () {
Http::fake();

$parentSpan = Measure::start('parent');

$parentSpanTraceContextID = "";
TextInjector::Inject($parentSpanTraceContextID, $parentSpan);

Http::withTrace()->post('http://example.com/first');

Http::assertSent(function (\Illuminate\Http\Client\Request $request) use ($parentSpanTraceContextID) {
return $request
->hasHeader('traceparent', $parentSpanTraceContextID);
});

$childSpan = Measure::start('second');
$childSpanTraceContextID = "";
TextInjector::Inject($childSpanTraceContextID, $childSpan);

Http::withTrace()->post('http://example.com/second');

Http::assertSent(function (\Illuminate\Http\Client\Request $request) use ($childSpanTraceContextID) {
return $request
->hasHeader('traceparent', $childSpanTraceContextID);
});

Measure::stop('second');

Http::withTrace()->post('http://example.com/third');

Http::assertSent(function (\Illuminate\Http\Client\Request $request) use ($parentSpanTraceContextID) {
return $request
->hasHeader('traceparent', $parentSpanTraceContextID);
});

Measure::stop('first');
});

it('wil not fall, if no spans present', function () {
Http::fake();

Http::withTrace()->post('http://example.com/first');

Http::assertSent(function (\Illuminate\Http\Client\Request $request) {
return true;
});
});
3 changes: 3 additions & 0 deletions tests/TestSupport/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Orchestra\Testbench\TestCase as Orchestra;
use Spatie\OpenTelemetry\Drivers\MemoryDriver;
use Spatie\OpenTelemetry\Facades\Measure;
use Spatie\OpenTelemetry\Http\Client\Macro as HttpClientMacro;
use Spatie\OpenTelemetry\OpenTelemetryServiceProvider;
use Spatie\OpenTelemetry\Support\IdGenerator;
use Spatie\OpenTelemetry\Support\Samplers\AlwaysSampler;
Expand Down Expand Up @@ -32,6 +33,8 @@ protected function setUp(): void

Measure::setDriver($this->memoryDriver);

HttpClientMacro::apply();

FakeIdGenerator::reset();
}

Expand Down