Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 3209f8f

Browse files
authored
Automatically detect common attributes from the request (#146)
* RequestHandler now parses standard attributes from request. StackdriverExporter now maps the standard attributes to expected Stackdriver formatted attributes. * Make common attribute names constants * Add test for Stackdriver attribute mapping * Add http.route constant and fix documentation * Mock stackdriver client for tests * Fix documentation description
1 parent 829c42f commit 3209f8f

File tree

6 files changed

+173
-139
lines changed

6 files changed

+173
-139
lines changed

src/Trace/Exporter/StackdriverExporter.php

Lines changed: 38 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Google\Cloud\Trace\Span;
2424
use Google\Cloud\Trace\Trace;
2525
use OpenCensus\Trace\Tracer\TracerInterface;
26+
use OpenCensus\Trace\Span as OCSpan;
2627

2728
/**
2829
* This implementation of the ExporterInterface use the BatchRunner to provide
@@ -70,30 +71,14 @@
7071
*/
7172
class StackdriverExporter implements ExporterInterface
7273
{
73-
const VERSION = '0.1.0';
74-
75-
// These are Stackdriver Trace's common attributes
76-
const AGENT = 'g.co/agent';
77-
const COMPONENT = '/component';
78-
const ERROR_MESSAGE = '/error/message';
79-
const ERROR_NAME = '/error/name';
80-
const HTTP_CLIENT_CITY = '/http/client_city';
81-
const HTTP_CLIENT_COUNTRY = '/http/client_country';
82-
const HTTP_CLIENT_PROTOCOL = '/http/client_protocol';
83-
const HTTP_CLIENT_REGION = '/http/client_region';
84-
const HTTP_HOST = '/http/host';
85-
const HTTP_METHOD = '/http/method';
86-
const HTTP_REDIRECTED_URL = '/http/redirected_url';
87-
const HTTP_STATUS_CODE = '/http/status_code';
88-
const HTTP_URL = '/http/url';
89-
const HTTP_USER_AGENT = '/http/user_agent';
90-
const PID = '/pid';
91-
const TID = '/tid';
92-
93-
const GAE_APPLICATION_ERROR = 'g.co/gae/application_error';
94-
const GAE_APP_MODULE = 'g.co/gae/app/module';
95-
const GAE_APP_MODULE_VERSION = 'g.co/gae/app/module_version';
96-
const GAE_APP_VERSION = 'g.co/gae/app/version';
74+
const ATTRIBUTE_MAP = [
75+
OCSpan::ATTRIBUTE_HOST => '/http/host',
76+
OCSpan::ATTRIBUTE_PORT => '/http/port',
77+
OCSpan::ATTRIBUTE_METHOD => '/http/method',
78+
OCSpan::ATTRIBUTE_PATH => '/http/url',
79+
OCSpan::ATTRIBUTE_USER_AGENT => '/http/user_agent',
80+
OCSpan::ATTRIBUTE_STATUS_CODE => '/http/status_code'
81+
];
9782

9883
use BatchTrait;
9984

@@ -155,7 +140,6 @@ public function __construct(array $options = [])
155140
*/
156141
public function report(TracerInterface $tracer)
157142
{
158-
$this->processSpans($tracer);
159143
$spans = $this->convertSpans($tracer);
160144

161145
if (empty($spans)) {
@@ -182,40 +166,43 @@ public function report(TracerInterface $tracer)
182166
}
183167

184168
/**
185-
* Perform any pre-conversion modification to the spans
169+
* Convert spans into Stackdriver's expected JSON output format.
170+
*
171+
* @access private
186172
*
187173
* @param TracerInterface $tracer
188-
* @param array $headers [optional] Array of headers to read from instead of $_SERVER
174+
* @return Span[] Representation of the collected trace spans ready for serialization
189175
*/
190-
public function processSpans(TracerInterface $tracer, $headers = null)
176+
public function convertSpans(TracerInterface $tracer)
191177
{
192-
// detect common attributes
193-
$this->addCommonAttributes($tracer, $headers);
178+
// transform OpenCensus Spans to Google\Cloud\Trace\Spans
179+
return array_map([$this, 'mapSpan'], $tracer->spans());
194180
}
195181

196-
/**
197-
* Convert spans into Zipkin's expected JSON output format.
198-
*
199-
* @param TracerInterface $tracer
200-
* @param Trace $trace
201-
* @return array Representation of the collected trace spans ready for serialization
202-
*/
203-
public function convertSpans(TracerInterface $tracer)
182+
private function mapSpan($span)
204183
{
205-
$traceId = $tracer->spanContext()->traceId();
184+
return new Span($span->traceId(), [
185+
'name' => $span->name(),
186+
'startTime' => $span->startTime(),
187+
'endTime' => $span->endTime(),
188+
'spanId' => $span->spanId(),
189+
'parentSpanId' => $span->parentSpanId(),
190+
'attributes' => $this->mapAttributes($span->attributes()),
191+
'stackTrace' => $span->stackTrace()
192+
]);
193+
}
206194

207-
// transform OpenCensus Spans to Google\Cloud\Trace\Spans
208-
return array_map(function ($span) use ($traceId) {
209-
return new Span($traceId, [
210-
'name' => $span->name(),
211-
'startTime' => $span->startTime(),
212-
'endTime' => $span->endTime(),
213-
'spanId' => $span->spanId(),
214-
'parentSpanId' => $span->parentSpanId(),
215-
'attributes' => $span->attributes(),
216-
'stackTrace' => $span->stackTrace()
217-
]);
218-
}, $tracer->spans());
195+
private function mapAttributes(array $attributes)
196+
{
197+
$newAttributes = [];
198+
foreach ($attributes as $key => $value) {
199+
if (array_key_exists($key, self::ATTRIBUTE_MAP)) {
200+
$newAttributes[self::ATTRIBUTE_MAP[$key]] = $value;
201+
} else {
202+
$newAttributes[$key] = $value;
203+
}
204+
}
205+
return $newAttributes;
219206
}
220207

221208
/**
@@ -232,54 +219,4 @@ protected function getCallback()
232219

233220
return [self::$client, $this->batchMethod];
234221
}
235-
236-
private function addCommonAttributes(&$tracer, $headers = null)
237-
{
238-
$headers = $headers ?: $_SERVER;
239-
$spans = $tracer->spans();
240-
if (empty($spans)) {
241-
return;
242-
}
243-
$rootSpan = $spans[0];
244-
245-
$attributeMap = [
246-
self::HTTP_URL => ['REQUEST_URI'],
247-
self::HTTP_METHOD => ['REQUEST_METHOD'],
248-
self::HTTP_CLIENT_PROTOCOL => ['SERVER_PROTOCOL'],
249-
self::HTTP_USER_AGENT => ['HTTP_USER_AGENT'],
250-
self::HTTP_HOST => ['HTTP_HOST', 'SERVER_NAME'],
251-
self::GAE_APP_MODULE => ['GAE_SERVICE'],
252-
self::GAE_APP_MODULE_VERSION => ['GAE_VERSION'],
253-
self::HTTP_CLIENT_CITY => ['HTTP_X_APPENGINE_CITY'],
254-
self::HTTP_CLIENT_REGION => ['HTTP_X_APPENGINE_REGION'],
255-
self::HTTP_CLIENT_COUNTRY => ['HTTP_X_APPENGINE_COUNTRY']
256-
];
257-
foreach ($attributeMap as $attributeKey => $headerKeys) {
258-
if ($val = $this->detectKey($headerKeys, $headers)) {
259-
$tracer->addAttribute($attributeKey, $val, ['span' => $rootSpan]);
260-
}
261-
}
262-
263-
$responseCode = http_response_code();
264-
if ($responseCode == 301 || $responseCode == 302) {
265-
foreach (headers_list() as $header) {
266-
if (substr($header, 0, 9) == 'Location:') {
267-
$this->rootSpan->addAttribute(self::HTTP_REDIRECTED_URL, substr($header, 10));
268-
break;
269-
}
270-
}
271-
}
272-
$tracer->addAttribute(self::PID, '' . getmypid(), ['span' => $rootSpan]);
273-
$tracer->addAttribute(self::AGENT, 'opencensus-php [' . self::VERSION . ']', ['span' => $rootSpan]);
274-
}
275-
276-
private function detectKey(array $keys, array $array)
277-
{
278-
foreach ($keys as $key) {
279-
if (array_key_exists($key, $array)) {
280-
return $array[$key];
281-
}
282-
}
283-
return null;
284-
}
285222
}

src/Trace/RequestHandler.php

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
class RequestHandler
3737
{
3838
const DEFAULT_ROOT_SPAN_NAME = 'main';
39+
const ATTRIBUTE_MAP = [
40+
Span::ATTRIBUTE_HOST => ['HTTP_HOST', 'SERVER_NAME'],
41+
Span::ATTRIBUTE_PORT => ['SERVER_PORT'],
42+
Span::ATTRIBUTE_METHOD => ['REQUEST_METHOD'],
43+
Span::ATTRIBUTE_PATH => ['REQUEST_URI'],
44+
Span::ATTRIBUTE_USER_AGENT => ['HTTP_USER_AGENT']
45+
];
3946

4047
/**
4148
* @var ExporterInterface The reported to use at the end of the request
@@ -57,6 +64,11 @@ class RequestHandler
5764
*/
5865
private $scope;
5966

67+
/**
68+
* @var array Replacement $_SERVER variables
69+
*/
70+
private $headers;
71+
6072
/**
6173
* Create a new RequestHandler.
6274
*
@@ -79,11 +91,11 @@ public function __construct(
7991
array $options = []
8092
) {
8193
$this->reporter = $reporter;
82-
$headers = array_key_exists('headers', $options)
94+
$this->headers = array_key_exists('headers', $options)
8395
? $options['headers']
8496
: $_SERVER;
8597

86-
$spanContext = $propagator->extract($headers);
98+
$spanContext = $propagator->extract($this->headers);
8799

88100
// If the context force disables tracing, don't consult the $sampler.
89101
if ($spanContext->enabled() !== false) {
@@ -101,8 +113,8 @@ public function __construct(
101113
: new NullTracer();
102114

103115
$spanOptions = $options + [
104-
'startTime' => $this->startTimeFromHeaders($headers),
105-
'name' => $this->nameFromHeaders($headers),
116+
'startTime' => $this->startTimeFromHeaders($this->headers),
117+
'name' => $this->nameFromHeaders($this->headers),
106118
'attributes' => []
107119
];
108120
$this->rootSpan = $this->tracer->startSpan($spanOptions);
@@ -120,8 +132,7 @@ public function __construct(
120132
*/
121133
public function onExit()
122134
{
123-
$responseCode = http_response_code();
124-
$this->rootSpan->setStatus($responseCode, "HTTP status code: $responseCode");
135+
$this->addCommonRequestAttributes($this->headers);
125136

126137
$this->scope->close();
127138
$this->reporter->report($this->tracer);
@@ -192,6 +203,22 @@ public function addAttribute($attribute, $value, $options = [])
192203
$this->tracer->addAttribute($attribute, $value, $options);
193204
}
194205

206+
public function addCommonRequestAttributes(array $headers)
207+
{
208+
$responseCode = http_response_code();
209+
$this->rootSpan->setStatus($responseCode, "HTTP status code: $responseCode");
210+
$this->tracer->addAttribute(Span::ATTRIBUTE_STATUS_CODE, $responseCode, [
211+
'spanId' => $this->rootSpan->spanId()
212+
]);
213+
foreach (self::ATTRIBUTE_MAP as $attributeKey => $headerKeys) {
214+
if ($val = $this->detectKey($headerKeys, $headers)) {
215+
$this->tracer->addAttribute($attributeKey, $val, [
216+
'spanId' => $this->rootSpan->spanId()
217+
]);
218+
}
219+
}
220+
}
221+
195222
private function startTimeFromHeaders(array $headers)
196223
{
197224
if (array_key_exists('REQUEST_TIME_FLOAT', $headers)) {
@@ -210,4 +237,14 @@ private function nameFromHeaders(array $headers)
210237
}
211238
return self::DEFAULT_ROOT_SPAN_NAME;
212239
}
240+
241+
private function detectKey(array $keys, array $array)
242+
{
243+
foreach ($keys as $key) {
244+
if (array_key_exists($key, $array)) {
245+
return $array[$key];
246+
}
247+
}
248+
return null;
249+
}
213250
}

src/Trace/Span.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ class Span
2828
{
2929
use AttributeTrait;
3030

31+
// See https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md#attributes
32+
const ATTRIBUTE_HOST = 'http.host';
33+
const ATTRIBUTE_PORT = 'http.port';
34+
const ATTRIBUTE_METHOD = 'http.method';
35+
const ATTRIBUTE_PATH = 'http.path';
36+
const ATTRIBUTE_ROUTE = 'http.route';
37+
const ATTRIBUTE_USER_AGENT = 'http.user_agent';
38+
const ATTRIBUTE_STATUS_CODE = 'http.status_code';
39+
3140
/**
3241
* Unique identifier for a trace. All spans from the same Trace share the
3342
* same `traceId`. 16-byte value encoded as a hex string.
@@ -145,6 +154,7 @@ class Span
145154
public function __construct($options = [])
146155
{
147156
$options += [
157+
'traceId' => null,
148158
'attributes' => [],
149159
'timeEvents' => [],
150160
'links' => [],
@@ -153,6 +163,8 @@ public function __construct($options = [])
153163
'sameProcessAsParentSpan' => null
154164
];
155165

166+
$this->traceId = $options['traceId'];
167+
156168
if (array_key_exists('startTime', $options)) {
157169
$this->setStartTime($options['startTime']);
158170
}
@@ -188,6 +200,16 @@ public function __construct($options = [])
188200
$this->sameProcessAsParentSpan = $options['sameProcessAsParentSpan'];
189201
}
190202

203+
/**
204+
* Retrive the trace id for this span.
205+
*
206+
* @return string
207+
*/
208+
public function traceId()
209+
{
210+
return $this->traceId;
211+
}
212+
191213
/**
192214
* Retrieve the start time for this span.
193215
*

src/Trace/Tracer/ContextTracer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public function inSpan(array $spanOptions, callable $callable, array $arguments
8080
public function startSpan(array $spanOptions = [])
8181
{
8282
$spanOptions += [
83+
'traceId' => $this->spanContext()->traceId(),
8384
'parentSpanId' => $this->spanContext()->spanId(),
8485
'startTime' => microtime(true)
8586
];

0 commit comments

Comments
 (0)