· 5 years ago · Jun 11, 2020, 12:20 AM
1diff --git a/src/NW/Lib/TracingRequestHeaders.php b/src/NW/Lib/TracingRequestHeaders.php
2new file mode 100644
3index 000000000..79703ad48
4--- /dev/null
5+++ b/src/NW/Lib/TracingRequestHeaders.php
6@@ -0,0 +1,20 @@
7+<?php
8+
9+namespace NW\Lib;
10+
11+use Zipkin\Propagation\Getter;
12+
13+// This class implements the Zipkin getter interface to extract the zipkin
14+// headers out of the $_SERVER variable.
15+// Due to how the api is written, $_SERVER gets passed in as $carrier rather
16+// than get accessed here directly.
17+final class TracingRequestHeaders implements Getter
18+{
19+ public function get($carrier, string $key): ?string
20+ {
21+ $key = strtoupper($key);
22+ $key = str_replace('-', '_', $key);
23+
24+ return $carrier['HTTP_' . $key] ?? null;
25+ }
26+}
27diff --git a/src/NW/Lib/YelpTracingUtils.php b/src/NW/Lib/YelpTracingUtils.php
28index a94d8bb91..5ed17fd66 100644
29--- a/src/NW/Lib/YelpTracingUtils.php
30+++ b/src/NW/Lib/YelpTracingUtils.php
31@@ -5,6 +5,7 @@ namespace NW\Lib;
32 use NW\Container;
33 use NW\Lib\Paasta\PaastaEnvironment;
34 use Zipkin\Kind;
35+use Zipkin\Propagation\TraceContext;
36 use Zipkin\Tags;
37 use Zipkin\Tracing;
38
39@@ -41,7 +42,20 @@ class YelpTracingUtils
40 public function serverSpan($endpoint)
41 {
42 $tracer = $this->tracing->getTracer();
43- $span = $tracer->newTrace();
44+ $extractor = $this->tracing->getPropagation()->getExtractor(new TracingRequestHeaders);
45+
46+ // Try to extract the zipkin context from the incoming headers. If this works, extractor
47+ // returns a TraceContext object that we can use to continue the trace.
48+ // If the headers weren't set, it returns a DefaultSamplingFlags object in which case we
49+ // start a new trace. Afaict the only way to differentiate between the 2 is by checking
50+ // the return type and we need to do so because there's no zipkin-php API that correctly
51+ // handles passing either in so we need to write the if/else ourselves.
52+ $extractedContext = $extractor($_SERVER);
53+ if ($extractedContext instanceof TraceContext) {
54+ $span = $tracer->joinSpan($extractedContext);
55+ } else {
56+ $span = $tracer->newTrace();
57+ }
58 $span->setName($_SERVER['REQUEST_METHOD'] . ' ' . $endpoint);
59 $span->setKind(Kind\SERVER);
60
61diff --git a/tests/Integration/Src/NW/Lib/TracingTest.php b/tests/Integration/Src/NW/Lib/TracingTest.php
62index 59f13e40d..2c1178c71 100644
63--- a/tests/Integration/Src/NW/Lib/TracingTest.php
64+++ b/tests/Integration/Src/NW/Lib/TracingTest.php
65@@ -4,6 +4,7 @@ namespace NWTests\Integration\Src\NW\Lib\HTTP\Client;
66
67 use GuzzleHttp\Client as GuzzleClient;
68 use NWTests\Helpers\TestCases\NWTestCase;
69+use function Zipkin\Propagation\Id\generateNextId;
70
71 class TracingTest extends NWTestCase
72 {
73@@ -21,4 +22,20 @@ class TracingTest extends NWTestCase
74 $this->assertEquals(200, $nwResponse->getStatusCode());
75 $this->assertTrue($nwResponse->hasHeader('X-Zipkin-Id'));
76 }
77+
78+ public function testWeReuseIncomingTraceId()
79+ {
80+ $client = new GuzzleClient();
81+ $traceId = generateNextId();
82+ $nwResponse = $client->get('http://web:80/ready.php', [
83+ 'headers' => [
84+ 'X-B3-TraceId' => $traceId,
85+ 'X-B3-SpanId' => generateNextId(),
86+ 'X-B3-ParentSpanId' => generateNextId(),
87+ 'X-B3-Sampled' => 0
88+ ]
89+ ]);
90+ $this->assertEquals(200, $nwResponse->getStatusCode());
91+ $this->assertEquals($traceId, $nwResponse->getHeader('X-Zipkin-Id')[0]);
92+ }
93 }
94diff --git a/tests/Unit/Src/NW/Lib/YelpTracingUtilsTest.php b/tests/Unit/Src/NW/Lib/YelpTracingUtilsTest.php
95index 75b0935b1..43700759a 100644
96--- a/tests/Unit/Src/NW/Lib/YelpTracingUtilsTest.php
97+++ b/tests/Unit/Src/NW/Lib/YelpTracingUtilsTest.php
98@@ -9,6 +9,7 @@ use NWTests\Helpers\TestCases\NWTestCase;
99 use Zipkin\Reporters\InMemory as InMemoryReporter;
100 use Zipkin\Samplers\BinarySampler;
101 use Zipkin\TracingBuilder;
102+use function Zipkin\Propagation\Id\generateNextId;
103
104 class YelpTracingUtilsTest extends NWTestCase
105 {
106@@ -45,21 +46,21 @@ class YelpTracingUtilsTest extends NWTestCase
107 $tracing->getTracer()->flush();
108 return array_map(function ($span) {return $span->toArray();}, $reporter->flush());
109 };
110- return [$tracing, $flusher];
111- }
112
113- /**
114- * @test
115- **/
116- public function server_span()
117- {
118- list($tracing, $flusher) = $this->createTestTracer();
119 $paastaEnv = Mockery::mock(PaastaEnvironment::class);
120 $paastaEnv->shouldReceive('getEcosystem')->andReturn('devc');
121 $paastaEnv->shouldReceive('getRegion')->andReturn('uswest1-devc');
122 $paastaEnv->shouldReceive('getHabitat')->andReturn('uswest1adevc');
123 $ytu = new YelpTracingUtils($paastaEnv, $tracing);
124+ return [$ytu, $flusher];
125+ }
126
127+ /**
128+ * @test
129+ **/
130+ public function server_span()
131+ {
132+ list($ytu, $flusher) = $this->createTestTracer();
133 $ytu->serverSpan('/consumerAPI/public/getCurrentWait');
134
135 $spans = $flusher();
136@@ -79,15 +80,48 @@ class YelpTracingUtilsTest extends NWTestCase
137 /**
138 * @test
139 **/
140- public function client_span()
141+ public function server_span_uses_incoming_headers()
142 {
143- list($tracing, $flusher) = $this->createTestTracer();
144- $paastaEnv = Mockery::mock(PaastaEnvironment::class);
145- $paastaEnv->shouldReceive('getEcosystem')->andReturn('devc');
146- $paastaEnv->shouldReceive('getRegion')->andReturn('uswest1-devc');
147- $paastaEnv->shouldReceive('getHabitat')->andReturn('uswest1adevc');
148- $ytu = new YelpTracingUtils($paastaEnv, $tracing);
149+ $_SERVER['HTTP_X_B3_TRACEID'] = generateNextId();
150+ $_SERVER['HTTP_X_B3_SPANID'] = generateNextId();
151+ $_SERVER['HTTP_X_B3_PARENTSPANID'] = generateNextId();
152+ $_SERVER['HTTP_X_B3_SAMPLED'] = '1';
153
154+ list($ytu, $flusher) = $this->createTestTracer();
155+ $ytu->serverSpan('/consumerAPI/public/getCurrentWait');
156+
157+ $spans = $flusher();
158+ $this->assertCount(1, $spans);
159+ $serverSpan = $spans[0];
160+
161+ $this->assertEquals($_SERVER['HTTP_X_B3_TRACEID'], $serverSpan['traceId']);
162+ $this->assertEquals($_SERVER['HTTP_X_B3_PARENTSPANID'], $serverSpan['parentId']);
163+ $this->assertEquals($_SERVER['HTTP_X_B3_SPANID'], $serverSpan['id']);
164+ }
165+
166+ /**
167+ * @test
168+ **/
169+ public function server_span_respects_sampling()
170+ {
171+ $_SERVER['HTTP_X_B3_TRACEID'] = generateNextId();
172+ $_SERVER['HTTP_X_B3_SPANID'] = generateNextId();
173+ $_SERVER['HTTP_X_B3_PARENTSPANID'] = generateNextId();
174+ $_SERVER['HTTP_X_B3_SAMPLED'] = '0';
175+
176+ list($ytu, $flusher) = $this->createTestTracer();
177+ $ytu->serverSpan('/consumerAPI/public/getCurrentWait');
178+
179+ $spans = $flusher();
180+ $this->assertCount(0, $spans);
181+ }
182+
183+ /**
184+ * @test
185+ **/
186+ public function client_span()
187+ {
188+ list($ytu, $flusher) = $this->createTestTracer();
189 $ytu->clientSpan('SELECT * from users;');
190
191 $spans = $flusher();