Skip to content
This repository has been archived by the owner on May 25, 2021. It is now read-only.

Commit

Permalink
Merge pull request #29 from nenad/patch/decorate-event-subscriber-guzzle
Browse files Browse the repository at this point in the history
Decorate the Guzzle5 event subscriber with more information
  • Loading branch information
nenad authored Sep 12, 2019
2 parents aafcac5 + bc90ba5 commit 737e4d6
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 50 deletions.
83 changes: 69 additions & 14 deletions src/Trace/Integrations/Guzzle/EventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\EndEvent;
use GuzzleHttp\Event\SubscriberInterface;
use OpenCensus\Trace\Tracer\TracerInterface;

/**
* This class handles integration with GuzzleHttp 5. Attaching this EventSubscriber to
Expand All @@ -48,21 +49,35 @@ class EventSubscriber implements SubscriberInterface
* @var PropagatorInterface
*/
private $propagator;

/**
* @var TracerInterface
*/
private $tracer;
/**
* @var bool
*/
private $logBody;
/**
* @var Scope
*/
private $scope;
/**
* @var Span
*/
private $span;

/**
* Create a new Guzzle event listener that creates trace spans and propagates the current
* trace context to the downstream request.
*
* @param TracerInterface $tracer
* @param PropagatorInterface $propagator Interface responsible for serializing trace context
*/
public function __construct(PropagatorInterface $propagator = null)
public function __construct(TracerInterface $tracer, PropagatorInterface $propagator = null, bool $logBody = true)
{
$this->propagator = $propagator ?: new HttpHeaderPropagator();
$this->tracer = $tracer;
$this->logBody = $logBody;
}

/**
Expand All @@ -87,22 +102,25 @@ public function getEvents()
public function onBefore(BeforeEvent $event)
{
$request = $event->getRequest();
$context = Tracer::spanContext();
if ($context->enabled()) {
$headers = new ArrayHeaders();
$this->propagator->inject($context, $headers);
$request->setHeaders($headers->toArray());
$headers = new ArrayHeaders();
$this->propagator->inject($this->tracer->spanContext(), $headers);
$request->setHeaders($headers->toArray());

$attrHeaders = [];
foreach ($request->getHeaders() as $name => $values) {
$attrHeaders['request.' . $name] = implode(', ', $values);
}

$span = Tracer::startSpan([
'name' => 'GuzzleHttp::request',
$this->span = $this->tracer->startSpan([
'name' => sprintf('Guzzle: %s', $request->getHost()),
'attributes' => [
'method' => $request->getMethod(),
'uri' => $request->getUrl()
],
'kind' => Span::KIND_CLIENT
'http.method' => $request->getMethod(),
'http.uri' => $request->getUrl(),
] + $attrHeaders,
'kind' => Span::KIND_CLIENT,
'sameProcessAsParentSpan' => !empty($this->spans),
]);
$this->scope = Tracer::withSpan($span);
$this->scope = $this->tracer->withSpan($this->span);
}

/**
Expand All @@ -113,6 +131,43 @@ public function onBefore(BeforeEvent $event)
*/
public function onEnd(EndEvent $event)
{
$response = $event->getResponse();
$exception = $event->getException();

if ($exception) {
$this->span->addAttribute('error', 'true');
$this->span->addAttribute('exception', sprintf('%s: %s', get_class($exception), $exception->getMessage()));
}

if ($response === null) {
$this->scope->close();
return;
}

$statusCode = $response->getStatusCode();
$this->span->addAttribute('http.status_code', (string)$statusCode);

// If it's an error, annotate it as such
if ($statusCode >= 400) {
$this->span->addAttribute('error', 'true');
}

if ($this->logBody) {
$bodyLength = (int)$response->getHeader('Content-Length');
if ($bodyLength > 0 && $bodyLength <= 4096) {
$body = (string)$response->getBody();
} else {
$body = 'Either Content-Length is missing, or it is bigger than 4096';
}
$this->span->addAttribute('response.body', $body);
}

$attrHeaders = [];
foreach ($response->getHeaders() as $name => $values) {
$attrHeaders['response.' . $name] = implode(', ', $values);
}
$this->span->addAttributes($attrHeaders);

$this->scope->close();
}
}
2 changes: 1 addition & 1 deletion tests/integration/guzzle5/composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"require": {
"php": "^7.2",
"php": "^7.0",
"opencensus/opencensus": "dev-master",
"guzzlehttp/guzzle": "^5.0",
"ext-opencensus": "*"
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/guzzle5/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php" colors="true">
<testsuites>
<testsuite>
<testsuite name="guzzle">
<directory>tests</directory>
</testsuite>
</testsuites>
Expand Down
68 changes: 34 additions & 34 deletions tests/integration/guzzle5/tests/Guzzle5Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
use GuzzleHttp\Client;
use HttpTest\HttpTestServer;
use OpenCensus\Trace\Exporter\NullExporter;
use OpenCensus\Trace\Propagator\HttpHeaderPropagator;
use OpenCensus\Trace\RequestHandler;
use OpenCensus\Trace\Sampler\AlwaysSampleSampler;
use OpenCensus\Trace\Tracer;
use OpenCensus\Trace\Integrations\Guzzle\EventSubscriber;
use Psr\Http\Message\RequestInterface;
Expand All @@ -37,16 +40,11 @@ public function setUp()
{
parent::setUp();
$this->client = new Client();
$subscriber = new EventSubscriber();
$this->client->getEmitter()->attach($subscriber);
if (extension_loaded('opencensus')) {
opencensus_trace_clear();
}
}

/**
* @runInSeparateProcess
*/
public function testGuzzleRequest()
{
$server = HttpTestServer::create(
Expand All @@ -57,32 +55,31 @@ function (RequestInterface $request, ResponseInterface &$response) {
}
);

$exporter = new NullExporter();
$tracer = Tracer::start($exporter, [
'skipReporting' => true
]);

$server->start();
$tracer = new RequestHandler(
new NullExporter(),
new AlwaysSampleSampler(),
new HttpHeaderPropagator(),
['skipReporting' => true]
);

$response = $this->client->get($server->getUrl());
$this->assertEquals(200, $response->getStatusCode());
$this->client->getEmitter()->attach(new EventSubscriber($tracer->tracer()));

$server->start();
$response = $this->client->get($server->getUrl());
$server->stop();

$tracer->onExit();
$this->assertEquals(200, $response->getStatusCode());

$spans = $tracer->tracer()->spans();
$this->assertCount(2, $spans);

$this->assertCount(2, $spans);
$curlSpan = $spans[1];
$this->assertEquals('GuzzleHttp::request', $curlSpan->name());
$this->assertEquals('GET', $curlSpan->attributes()['method']);
$this->assertEquals($server->getUrl(), $curlSpan->attributes()['uri']);
$this->assertEquals('Guzzle: localhost', $curlSpan->name());
$this->assertEquals('GET', $curlSpan->attributes()['http.method']);
$this->assertEquals($server->getUrl(), $curlSpan->attributes()['http.uri']);
}

/**
* @runInSeparateProcess
*/
public function testPersistsTraceContext()
{
$server = HttpTestServer::create(
Expand All @@ -97,29 +94,32 @@ function (RequestInterface $request, ResponseInterface &$response) {
);

$traceContextHeader = '1603c1cde5c74f23bcf1682eb822fcf7/1150672535;o=1';
$exporter = new NullExporter();
$tracer = Tracer::start($exporter, [
'skipReporting' => true,
'headers' => [
'X-Cloud-Trace-Context' => $traceContextHeader
$tracer = new RequestHandler(
new NullExporter(),
new AlwaysSampleSampler(),
new HttpHeaderPropagator(),
[
'skipReporting' => true,
'headers' => [
'X-Cloud-Trace-Context' => $traceContextHeader,
],
]
]);
);

$server->start();
$this->client->getEmitter()->attach(new EventSubscriber($tracer->tracer()));

$server->start();
$response = $this->client->get($server->getUrl());
$this->assertEquals(200, $response->getStatusCode());

$server->stop();

$tracer->onExit();
$this->assertEquals(200, $response->getStatusCode());

$spans = $tracer->tracer()->spans();
$this->assertCount(2, $spans);

$this->assertCount(2, $spans);
$curlSpan = $spans[1];
$this->assertEquals('GuzzleHttp::request', $curlSpan->name());
$this->assertEquals('GET', $curlSpan->attributes()['method']);
$this->assertEquals($server->getUrl(), $curlSpan->attributes()['uri']);
$this->assertEquals('Guzzle: localhost', $curlSpan->name());
$this->assertEquals('GET', $curlSpan->attributes()['http.method']);
$this->assertEquals($server->getUrl(), $curlSpan->attributes()['http.uri']);
}
}

0 comments on commit 737e4d6

Please sign in to comment.