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

Commit b3bdec8

Browse files
authored
Add ZipkinReporter (#5)
* Initial implementation of the ZipkinReporter. * Add ZipkinReporterTest * Fix ZipkinReporterTest * cast datetime to float before conversion to microseconds * Fix label fetching * Fix building Zipkin url
1 parent f1a7eed commit b3bdec8

3 files changed

Lines changed: 239 additions & 0 deletions

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 OpenCensus\Trace\Tracer\TracerInterface;
21+
22+
/**
23+
* This implementation of the ReporterInterface appends a json
24+
* representation of the trace to a file.
25+
*/
26+
class ZipkinReporter implements ReporterInterface
27+
{
28+
/**
29+
* @var string
30+
*/
31+
private $name;
32+
33+
/**
34+
* @var string
35+
*/
36+
private $host;
37+
38+
/**
39+
* @var int
40+
*/
41+
private $port;
42+
43+
/**
44+
* @var string
45+
*/
46+
private $url;
47+
48+
/**
49+
* Create a new ZipkinReporter
50+
*
51+
* @param string $name The name of this application
52+
* @param string $host The hostname of the Zipkin server
53+
* @param int $port The port of the Zipkin server
54+
* @param string $endpoint (optional) The path for the span reporting endpoint. **Defaults to** `/api/v1/spans`
55+
*/
56+
public function __construct($name, $host, $port, $endpoint = '/api/v1/spans')
57+
{
58+
$this->name = $name;
59+
$this->host = $host;
60+
$this->port = $port;
61+
$this->url = "http://${host}:${port}${endpoint}";
62+
}
63+
64+
/**
65+
* Report the provided Trace to a backend.
66+
*
67+
* @param TracerInterface $tracer
68+
* @return bool
69+
*/
70+
public function report(TracerInterface $tracer)
71+
{
72+
try {
73+
$json = $this->serialize($tracer);
74+
$contextOptions = [
75+
'http' => [
76+
'method' => 'POST',
77+
'header' => 'Content-Type: application/json',
78+
'content' => $json
79+
]
80+
];
81+
82+
$context = stream_context_create($contextOptions);
83+
file_get_contents($this->url, false, $context);
84+
} catch (\Exception $e) {
85+
return false;
86+
}
87+
return true;
88+
}
89+
90+
/**
91+
* Serialize into Zipkin's expected JSON output format.
92+
*
93+
* @param TracerInterface $tracer
94+
* @return string JSON representation of the collected trace spans
95+
*/
96+
public function serialize(TracerInterface $tracer)
97+
{
98+
$spans = $tracer->spans();
99+
$context = $tracer->context();
100+
$traceId = $context->traceId();
101+
102+
$endpoint = [
103+
'ipv4' => $this->host,
104+
'port' => $this->port,
105+
'serviceName' => $this->name
106+
];
107+
108+
return json_encode(
109+
array_map(function ($span) use ($traceId, $endpoint) {
110+
$startTime = (int)((float) $span->startTime()->format('U.u') * 1000 * 1000);
111+
$endTime = (int)((float) $span->endTime()->format('U.u') * 1000 * 1000);
112+
$spanId = str_pad(dechex($span->spanId()), 16, '0', STR_PAD_LEFT);
113+
$parentSpanId = $span->parentSpanId()
114+
? str_pad(dechex($span->parentSpanId()), 16, '0', STR_PAD_LEFT)
115+
: null;
116+
return [
117+
// 8-byte identifier encoded as 16 lowercase hex characters
118+
'id' => $spanId,
119+
'traceId' => $traceId,
120+
'name' => $span->name(),
121+
'timestamp' => $startTime,
122+
'duration' => $endTime - $startTime,
123+
'annotations' => [
124+
[
125+
'endpoint' => $endpoint,
126+
'timestamp' => $startTime,
127+
'value' => 'cs'
128+
],
129+
[
130+
'endpoint' => $endpoint,
131+
'timestamp' => $endTime,
132+
'value' => 'cr'
133+
]
134+
],
135+
'binaryAnnotations' => array_map(function ($key, $value) {
136+
return [
137+
'key' => $key,
138+
'value' => $value
139+
];
140+
}, array_keys($span->labels()), $span->labels()),
141+
'parentId' => $parentSpanId
142+
];
143+
}, $spans)
144+
);
145+
}
146+
}

src/Trace/TraceSpan.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,18 @@ public function name()
165165
return $this->info['name'];
166166
}
167167

168+
/**
169+
* Retrieve the list of labels for this span
170+
*
171+
* @return array
172+
*/
173+
public function labels()
174+
{
175+
return array_key_exists('labels', $this->info)
176+
? $this->info['labels']
177+
: [];
178+
}
179+
168180
/**
169181
* Returns a serializable array representing this span.
170182
*
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
/**
3+
* Copyright 2017 Google Inc.
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\Tests\Unit\Trace\Reporter;
19+
20+
use OpenCensus\Trace\Reporter\ZipkinReporter;
21+
use OpenCensus\Trace\TraceContext;
22+
use OpenCensus\Trace\TraceSpan;
23+
use OpenCensus\Trace\Tracer\TracerInterface;
24+
use Prophecy\Argument;
25+
26+
/**
27+
* @group trace
28+
*/
29+
class ZipkinReporterTest extends \PHPUnit_Framework_TestCase
30+
{
31+
private $tracer;
32+
33+
public function setUp()
34+
{
35+
$this->tracer = $this->prophesize(TracerInterface::class);
36+
}
37+
38+
/**
39+
* http://zipkin.io/zipkin-api/#/paths/%252Fspans/post
40+
*/
41+
public function testFormatsTrace()
42+
{
43+
$spans = [
44+
new TraceSpan([
45+
'name' => 'span',
46+
'startTime' => microtime(true),
47+
'endTime' => microtime(true) + 10
48+
])
49+
];
50+
$this->tracer->context()->willReturn(new TraceContext());
51+
$this->tracer->spans()->willReturn($spans);
52+
53+
$reporter = new ZipkinReporter('myapp', 'localhost', 9411);
54+
55+
$json = $reporter->serialize($this->tracer->reveal());
56+
$data = json_decode($json, true);
57+
58+
$this->assertInternalType('array', $data);
59+
foreach ($data as $span) {
60+
$this->assertRegExp('/[0-9a-z]{16}/', $span['id']);
61+
$this->assertRegExp('/[0-9a-z]{32}/', $span['traceId']);
62+
$this->assertInternalType('string', $span['name']);
63+
$this->assertInternalType('int', $span['timestamp']);
64+
$this->assertInternalType('int', $span['duration']);
65+
$this->assertInternalType('array', $span['annotations']);
66+
foreach ($span['annotations'] as $annotation) {
67+
$this->assertInternalType('array', $annotation['endpoint']);
68+
$this->assertInternalType('string', $annotation['endpoint']['ipv4']);
69+
$this->assertInternalType('int', $annotation['endpoint']['port']);
70+
$this->assertInternalType('string', $annotation['endpoint']['serviceName']);
71+
$this->assertInternalType('int', $annotation['timestamp']);
72+
$this->assertInternalType('string', $annotation['value']);
73+
}
74+
$this->assertInternalType('array', $span['binaryAnnotations']);
75+
foreach ($span['binaryAnnotations'] as $annotation) {
76+
$this->assertInternalType('string', $annotation['key']);
77+
$this->assertInternalType('string', $annotation['value']);
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)