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

Commit 2736840

Browse files
authored
Add Google Cloud Reporter (#4)
* Add GoogleCloudReporter. Move App Engine/Stackdriver labels from the RequestHandler to the reporter. The reporter merges the AsyncReporter and SyncReporter from Google\Cloud into a single reporter with the `async` option. * Add GoogleCloudReport test. Add development dependency on google/cloud * Label parsing delegated to reporter for now. * Tests for GoogleCloudReporter parsing labels and formatting TraceSpan objects * stub the TraceClient for tests * Add example in GoogleCloudReporter documentation * Add list of provided reporters to the README
1 parent b3bdec8 commit 2736840

8 files changed

Lines changed: 430 additions & 165 deletions

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OpenCensus - A stats collection and distributed tracing framework
22

3-
This is the open-source release of Census for Python. Census provides a
3+
This is the open-source release of Census for PHP. Census provides a
44
framework to measure a server's resource usage and collect performance stats.
55
This repository contains PHP related utilities and supporting software needed by
66
Census.
@@ -47,6 +47,17 @@ bottom of the webpage.
4747

4848
If you would like to provide your own reporter, create a class that implements `ReporterInterface`.
4949

50+
Currently implemented reporters:
51+
52+
| Class | Description |
53+
| ----- | ----------- |
54+
| [EchoReporter](src/Trace/Reporter/EchoReporter.php) | Output the collected spans to stdout |
55+
| [FileReporter](src/Trace/Reporter/FileReporter.php) | Output JSON encoded spans to a file |
56+
| [GoogleCloudReporter](src/Trace/Reporter/GoogleCloudReporter.php) | Report traces to Google Cloud Stackdriver Trace |
57+
| [LoggerReporter](src/Trace/Reporter/LoggerReporter.php) | Reporter JSON encoded spans to a PSR-3 logger |
58+
| [NullReporter](scr/Trace/Reporter/NullReporter.php) | No-op |
59+
| [ZipkinReporter](src/Trace/Reporter/ZipkinReporter.php) | Report collected spans to a Zipkin server |
60+
5061
### Sampling Rate
5162

5263
By default we attempt to trace all requests. This is not ideal as a little bit of

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
},
1313
"require-dev": {
1414
"phpunit/phpunit": "4.8.*",
15-
"squizlabs/php_codesniffer": "2.*"
15+
"squizlabs/php_codesniffer": "2.*",
16+
"google/cloud-trace": "^0.3"
1617
},
1718
"suggest": {
1819
"cache/apcu-adapter": "Enable QpsSampler to use apcu cache.",
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
<?php
2+
/**
3+
* Copyright 2017 Google Inc. All Rights Reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace OpenCensus\Trace\Reporter;
19+
20+
use Google\Cloud\Core\Batch\BatchRunner;
21+
use Google\Cloud\Core\Batch\BatchTrait;
22+
use Google\Cloud\Trace\TraceClient;
23+
use Google\Cloud\Trace\TraceSpan;
24+
use OpenCensus\Trace\Tracer\TracerInterface;
25+
26+
/**
27+
* This implementation of the ReporterInterface use the BatchRunner to provide
28+
* reporting of Traces and their TraceSpans to Google Cloud Stackdriver Trace.
29+
*
30+
* Example:
31+
* ```
32+
* use OpenCensus\Trace\RequestTracer;
33+
* use OpenCensus\Trace\Reporter\GoogleCloudReporter;
34+
*
35+
* $reporter = new GoogleCloudReporter([
36+
* 'clientConfig' => [
37+
* 'projectId' => 'my-project'
38+
* ]
39+
* ]);
40+
* RequestTracer::start($reporter);
41+
* ```
42+
*
43+
* The above configuration will synchronously report the traces to Google Cloud
44+
* Stackdriver Trace. You can enable an experimental asynchronous reporting
45+
* mechanism using (BatchDaemon)[https://github.com/GoogleCloudPlatform/google-cloud-php/tree/master/src/Core/Batch].
46+
*
47+
* Example:
48+
* ```
49+
* use OpenCensus\Trace\RequestTracer;
50+
* use OpenCensus\Trace\Reporter\GoogleCloudReporter;
51+
*
52+
* $reporter = new GoogleCloudReporter([
53+
* 'async' => true,
54+
* 'clientConfig' => [
55+
* 'projectId' => 'my-project'
56+
* ]
57+
* ]);
58+
* RequestTracer::start($reporter);
59+
* ```
60+
*
61+
* Note that to use the `async` option, you will also need to set the
62+
* `IS_BATCH_DAEMON_RUNNING` environment variable to `true`.
63+
*
64+
* @experimental The experimental flag means that while we believe this method
65+
* or class is ready for use, it may change before release in backwards-
66+
* incompatible ways. Please use with caution, and test thoroughly when
67+
* upgrading.
68+
*/
69+
class GoogleCloudReporter implements ReporterInterface
70+
{
71+
const VERSION = '0.1.0';
72+
73+
// These are Stackdriver Trace's common labels
74+
const AGENT = '/agent';
75+
const COMPONENT = '/component';
76+
const ERROR_MESSAGE = '/error/message';
77+
const ERROR_NAME = '/error/name';
78+
const HTTP_CLIENT_CITY = '/http/client_city';
79+
const HTTP_CLIENT_COUNTRY = '/http/client_country';
80+
const HTTP_CLIENT_PROTOCOL = '/http/client_protocol';
81+
const HTTP_CLIENT_REGION = '/http/client_region';
82+
const HTTP_HOST = '/http/host';
83+
const HTTP_METHOD = '/http/method';
84+
const HTTP_REDIRECTED_URL = '/http/redirected_url';
85+
const HTTP_STATUS_CODE = '/http/status_code';
86+
const HTTP_URL = '/http/url';
87+
const HTTP_USER_AGENT = '/http/user_agent';
88+
const PID = '/pid';
89+
const STACKTRACE = '/stacktrace';
90+
const TID = '/tid';
91+
92+
const GAE_APPLICATION_ERROR = 'g.co/gae/application_error';
93+
const GAE_APP_MODULE = 'g.co/gae/app/module';
94+
const GAE_APP_MODULE_VERSION = 'g.co/gae/app/module_version';
95+
const GAE_APP_VERSION = 'g.co/gae/app/version';
96+
97+
use BatchTrait;
98+
99+
/**
100+
* @var TraceClient
101+
*/
102+
private static $client;
103+
104+
/**
105+
* @var bool
106+
*/
107+
private $async;
108+
109+
/**
110+
* Create a TraceReporter that utilizes background batching.
111+
*
112+
* @param array $options [optional] {
113+
* Configuration options.
114+
*
115+
* @type TraceClient $client A trace client used to instantiate traces
116+
* to be delivered to the batch queue.
117+
* @type bool $debugOutput Whether or not to output debug information.
118+
* Please note debug output currently only applies in CLI based
119+
* applications. **Defaults to** `false`.
120+
* @type array $batchOptions A set of options for a BatchJob.
121+
* {@see \Google\Cloud\Core\Batch\BatchJob::__construct()} for
122+
* more details.
123+
* **Defaults to** ['batchSize' => 1000,
124+
* 'callPeriod' => 2.0,
125+
* 'workerNum' => 2].
126+
* @type array $clientConfig Configuration options for the Trace client
127+
* used to handle processing of batch items.
128+
* For valid options please see
129+
* {@see \Google\Cloud\Trace\TraceClient::__construct()}.
130+
* @type BatchRunner $batchRunner A BatchRunner object. Mainly used for
131+
* the tests to inject a mock. **Defaults to** a newly created
132+
* BatchRunner.
133+
* @type string $identifier An identifier for the batch job.
134+
* **Defaults to** `stackdriver-trace`.
135+
* @type bool $async Whether we should try to use the batch runner.
136+
* **Defaults to** `false`.
137+
* }
138+
*/
139+
public function __construct(array $options = [])
140+
{
141+
$this->async = isset($options['async']) ? $options['async'] : false;
142+
$this->setCommonBatchProperties($options + [
143+
'identifier' => 'stackdriver-trace',
144+
'batchMethod' => 'insertBatch'
145+
]);
146+
self::$client = isset($options['client'])
147+
? $options['client']
148+
: new TraceClient($this->clientConfig);
149+
}
150+
151+
/**
152+
* Report the provided Trace to a backend.
153+
*
154+
* @param TracerInterface $tracer
155+
* @return bool
156+
*/
157+
public function report(TracerInterface $tracer)
158+
{
159+
$this->processSpans($tracer);
160+
$spans = $this->convertSpans($tracer);
161+
162+
if (empty($spans)) {
163+
return false;
164+
}
165+
166+
// build a Trace object and assign TraceSpans
167+
$trace = self::$client->trace(
168+
$tracer->context()->traceId()
169+
);
170+
$trace->setSpans($spans);
171+
172+
try {
173+
if ($this->async) {
174+
return $this->batchRunner->submitItem($this->identifier, $trace);
175+
} else {
176+
return self::$client->insert($trace);
177+
}
178+
} catch (\Exception $e) {
179+
return false;
180+
}
181+
}
182+
183+
/**
184+
* Perform any pre-conversion modification to the spans
185+
*
186+
* @param TracerInterface $tracer
187+
* @param array $headers [optional] Array of headers to read from instead of $_SERVER
188+
*/
189+
public function processSpans(TracerInterface $tracer, $headers = null)
190+
{
191+
// detect common labels
192+
$this->addCommonLabels($tracer, $headers);
193+
}
194+
195+
/**
196+
* Convert spans into Zipkin's expected JSON output format.
197+
*
198+
* @param TracerInterface $tracer
199+
* @return array Representation of the collected trace spans ready for serialization
200+
*/
201+
public function convertSpans(TracerInterface $tracer)
202+
{
203+
// transform OpenCensus TraceSpans to Google\Cloud\TraceSpans
204+
return array_map(function ($span) {
205+
return new TraceSpan($span->info());
206+
}, $tracer->spans());
207+
}
208+
209+
/**
210+
* Returns an array representation of a callback which will be used to write
211+
* batch items.
212+
*
213+
* @return array
214+
*/
215+
protected function getCallback()
216+
{
217+
if (!isset(self::$client)) {
218+
self::$client = new TraceClient($this->clientConfig);
219+
}
220+
221+
return [self::$client, $this->batchMethod];
222+
}
223+
224+
private function addCommonLabels(&$tracer, $headers = null)
225+
{
226+
$headers = $headers ?: $_SERVER;
227+
228+
// If a redirect, add the HTTP_REDIRECTED_URL label to the main span
229+
$responseCode = http_response_code();
230+
if ($responseCode == 301 || $responseCode == 302) {
231+
foreach (headers_list() as $header) {
232+
if (substr($header, 0, 9) == 'Location:') {
233+
$tracer->addRootLabel(self::HTTP_REDIRECTED_URL, substr($header, 10));
234+
break;
235+
}
236+
}
237+
}
238+
$tracer->addRootLabel(self::HTTP_STATUS_CODE, $responseCode);
239+
240+
$labelMap = [
241+
self::HTTP_URL => ['REQUEST_URI'],
242+
self::HTTP_METHOD => ['REQUEST_METHOD'],
243+
self::HTTP_CLIENT_PROTOCOL => ['SERVER_PROTOCOL'],
244+
self::HTTP_USER_AGENT => ['HTTP_USER_AGENT'],
245+
self::HTTP_HOST => ['HTTP_HOST', 'SERVER_NAME'],
246+
self::GAE_APP_MODULE => ['GAE_SERVICE'],
247+
self::GAE_APP_MODULE_VERSION => ['GAE_VERSION'],
248+
self::HTTP_CLIENT_CITY => ['HTTP_X_APPENGINE_CITY'],
249+
self::HTTP_CLIENT_REGION => ['HTTP_X_APPENGINE_REGION'],
250+
self::HTTP_CLIENT_COUNTRY => ['HTTP_X_APPENGINE_COUNTRY']
251+
];
252+
foreach ($labelMap as $labelKey => $headerKeys) {
253+
if ($val = $this->detectKey($headerKeys, $headers)) {
254+
$tracer->addRootLabel($labelKey, $val);
255+
}
256+
}
257+
$tracer->addRootLabel(self::PID, '' . getmypid());
258+
$tracer->addRootLabel(self::AGENT, 'opencensus ' . self::VERSION);
259+
}
260+
261+
private function detectKey(array $keys, array $array)
262+
{
263+
foreach ($keys as $key) {
264+
if (array_key_exists($key, $array)) {
265+
return $array[$key];
266+
}
267+
}
268+
return null;
269+
}
270+
}

0 commit comments

Comments
 (0)