Skip to content

Commit

Permalink
Merge pull request #13 from keepsuit/track-headers
Browse files Browse the repository at this point in the history
Track http headers
  • Loading branch information
cappuc authored Feb 12, 2024
2 parents 907e62c + fda3fdd commit e47be2d
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
options: --entrypoint redis-server

strategy:
fail-fast: true
fail-fast: false
matrix:
php: [ 8.1, 8.2, 8.3 ]
laravel: [ ^9.0, ^10.0 ]
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,16 @@ return [
Instrumentation\HttpServerInstrumentation::class => [
'enabled' => env('OT_INSTRUMENTATION_HTTP_SERVER', true),
'excluded_paths' => [],
'allowed_headers' => [],
'sensitive_headers' => [],
],

Instrumentation\HttpClientInstrumentation::class => [
'enabled' => env('OT_INSTRUMENTATION_HTTP_CLIENT', true),
'allowed_headers' => [],
'sensitive_headers' => [],
],

Instrumentation\HttpClientInstrumentation::class => env('OT_INSTRUMENTATION_HTTP_CLIENT', true),

Instrumentation\QueryInstrumentation::class => env('OT_INSTRUMENTATION_QUERY', true),

Instrumentation\RedisInstrumentation::class => env('OT_INSTRUMENTATION_REDIS', true),
Expand Down Expand Up @@ -129,6 +135,8 @@ You can disable it by setting `OT_INSTRUMENTATION_HTTP_SERVER` to `false` or rem
Configuration options:

- `excluded_paths`: list of paths to exclude from tracing
- `allowed_headers`: list of headers to include in the trace
- `sensitive_headers`: list of headers with sensitive data to hide in the trace

### Http client

Expand All @@ -138,6 +146,13 @@ To trace an outgoing http request call the `withTrace` method on the request bui
Http::withTrace()->get('https://example.com');
```

You can disable it by setting `OT_INSTRUMENTATION_HTTP_CLIENT` to `false` or removing the `HttpClientInstrumentation::class` from the config file.

Configuration options:

- `allowed_headers`: list of headers to include in the trace
- `sensitive_headers`: list of headers with sensitive data to hide in the trace

### Database

Database queries are automatically traced.
Expand Down
8 changes: 7 additions & 1 deletion config/opentelemetry.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@
Instrumentation\HttpServerInstrumentation::class => [
'enabled' => env('OT_INSTRUMENTATION_HTTP_SERVER', true),
'excluded_paths' => [],
'allowed_headers' => [],
'sensitive_headers' => [],
],

Instrumentation\HttpClientInstrumentation::class => env('OT_INSTRUMENTATION_HTTP_CLIENT', true),
Instrumentation\HttpClientInstrumentation::class => [
'enabled' => env('OT_INSTRUMENTATION_HTTP_CLIENT', true),
'allowed_headers' => [],
'sensitive_headers' => [],
],

Instrumentation\QueryInstrumentation::class => env('OT_INSTRUMENTATION_QUERY', true),

Expand Down
3 changes: 3 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<php>
<env name="APP_KEY" value="base64:Otue13cqTvN3tEEu1F5C25ku031q5HktoEk2AnOZEgg="/>
</php>
</phpunit>
69 changes: 69 additions & 0 deletions src/Instrumentation/HandlesHttpHeaders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Keepsuit\LaravelOpenTelemetry\Instrumentation;

use Illuminate\Support\Arr;

/**
* @internal
*/
trait HandlesHttpHeaders
{
/**
* @var array<string>
*/
protected array $defaultSensitiveHeaders = [
'authorization',
'php-auth-pw',
'cookie',
'set-cookie',
];

/**
* @var array<string>
*/
protected static array $allowedHeaders = [];

/**
* @var array<string>
*/
protected static array $sensitiveHeaders = [];

/**
* @return array<string>
*/
public static function getAllowedHeaders(): array
{
return static::$allowedHeaders;
}

public static function headerIsAllowed(string $header): bool
{
return in_array($header, static::getAllowedHeaders());
}

/**
* @return array<string>
*/
public static function getSensitiveHeaders(): array
{
return static::$sensitiveHeaders;
}

public static function headerIsSensitive(string $header): bool
{
return in_array($header, static::getSensitiveHeaders());
}

/**
* @param array<string> $headers
* @return array<string>
*/
protected function normalizeHeaders(array $headers): array
{
return Arr::map(
$headers,
fn (string $header) => strtolower(trim($header)),
);
}
}
10 changes: 10 additions & 0 deletions src/Instrumentation/HttpClientInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
namespace Keepsuit\LaravelOpenTelemetry\Instrumentation;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
use Keepsuit\LaravelOpenTelemetry\Support\HttpClient\GuzzleTraceMiddleware;

class HttpClientInstrumentation implements Instrumentation
{
use HandlesHttpHeaders;

public function register(array $options): void
{
static::$allowedHeaders = $this->normalizeHeaders(Arr::get($options, 'allowed_headers', []));

static::$sensitiveHeaders = array_merge(
$this->normalizeHeaders(Arr::get($options, 'sensitive_headers', [])),
$this->defaultSensitiveHeaders
);

$this->registerWithTraceMacro();
}

Expand Down
12 changes: 12 additions & 0 deletions src/Instrumentation/HttpServerInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@

class HttpServerInstrumentation implements Instrumentation
{
use HandlesHttpHeaders;

protected static array $excludedPaths = [];

/**
* @return array<string>
*/
public static function getExcludedPaths(): array
{
return static::$excludedPaths;
Expand All @@ -22,6 +27,13 @@ public function register(array $options): void
Arr::get($options, 'excluded_paths', [])
);

static::$allowedHeaders = $this->normalizeHeaders(Arr::get($options, 'allowed_headers', []));

static::$sensitiveHeaders = array_merge(
$this->normalizeHeaders(Arr::get($options, 'sensitive_headers', [])),
$this->defaultSensitiveHeaders
);

$this->injectMiddleware(app(Kernel::class));
}

Expand Down
39 changes: 35 additions & 4 deletions src/Support/HttpClient/GuzzleTraceMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

use Closure;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
use Keepsuit\LaravelOpenTelemetry\Facades\Tracer;
use Keepsuit\LaravelOpenTelemetry\Instrumentation\HttpClientInstrumentation;
use OpenTelemetry\API\Trace\SpanInterface;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\SemConv\TraceAttributes;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class GuzzleTraceMiddleware
{
Expand All @@ -29,6 +31,8 @@ public static function make(): Closure
->setAttribute(TraceAttributes::SERVER_PORT, $request->getUri()->getPort())
->start();

static::recordHeaders($span, $request);

$context = $span->storeInContext(Tracer::currentContext());

foreach (Tracer::propagationHeaders($context) as $key => $value) {
Expand All @@ -38,9 +42,14 @@ public static function make(): Closure
$promise = $handler($request, $options);
assert($promise instanceof PromiseInterface);

return $promise->then(function (Response $response) use ($span) {
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode())
->setAttribute(TraceAttributes::HTTP_REQUEST_BODY_SIZE, $response->getHeader('Content-Length')[0] ?? null);
return $promise->then(function (ResponseInterface $response) use ($span) {
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode());

if (($contentLength = $response->getHeader('Content-Length')[0] ?? null) !== null) {
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, $contentLength);
}

static::recordHeaders($span, $response);

if ($response->getStatusCode() >= 400) {
$span->setStatus(StatusCode::STATUS_ERROR);
Expand All @@ -53,4 +62,26 @@ public static function make(): Closure
};
};
}

protected static function recordHeaders(SpanInterface $span, RequestInterface|ResponseInterface $http): SpanInterface
{
$prefix = match (true) {
$http instanceof RequestInterface => 'http.request.header.',
$http instanceof ResponseInterface => 'http.response.header.',
};

foreach ($http->getHeaders() as $key => $value) {
$key = strtolower($key);

if (! HttpClientInstrumentation::headerIsAllowed($key)) {
continue;
}

$value = HttpClientInstrumentation::headerIsSensitive($key) ? ['*****'] : $value;

$span->setAttribute($prefix.$key, $value);
}

return $span;
}
}
30 changes: 29 additions & 1 deletion src/Support/HttpServer/TraceRequestMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected function startTracing(Request $request): SpanInterface
$route = rescue(fn () => Route::getRoutes()->match($request)->uri(), $request->path(), false);
$route = str_starts_with($route, '/') ? $route : '/'.$route;

return Tracer::newSpan($route)
$span = Tracer::newSpan($route)
->setSpanKind(SpanKind::KIND_SERVER)
->setParent($context)
->setAttribute(TraceAttributes::URL_FULL, $request->fullUrl())
Expand All @@ -69,6 +69,10 @@ protected function startTracing(Request $request): SpanInterface
->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $request->getProtocolVersion())
->setAttribute(TraceAttributes::NETWORK_PEER_ADDRESS, $request->ip())
->start();

$this->recordHeaders($span, $request);

return $span;
}

protected function recordHttpResponseToSpan(SpanInterface $span, Response $response): void
Expand All @@ -79,6 +83,8 @@ protected function recordHttpResponseToSpan(SpanInterface $span, Response $respo
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_BODY_SIZE, strlen($content));
}

$this->recordHeaders($span, $response);

if ($response->isSuccessful()) {
$span->setStatus(StatusCode::STATUS_OK);
}
Expand All @@ -87,4 +93,26 @@ protected function recordHttpResponseToSpan(SpanInterface $span, Response $respo
$span->setStatus(StatusCode::STATUS_ERROR);
}
}

protected function recordHeaders(SpanInterface $span, Request|Response $http): SpanInterface
{
$prefix = match (true) {
$http instanceof Request => 'http.request.header.',
$http instanceof Response => 'http.response.header.',
};

foreach ($http->headers->all() as $key => $value) {
$key = strtolower($key);

if (! HttpServerInstrumentation::headerIsAllowed($key)) {
continue;
}

$value = HttpServerInstrumentation::headerIsSensitive($key) ? ['*****'] : $value;

$span->setAttribute($prefix.$key, $value);
}

return $span;
}
}
Loading

0 comments on commit e47be2d

Please sign in to comment.