Skip to content

Commit 0e1297c

Browse files
authored
Otel Codec (#17)
* Otel Codec * adding annotation * removing strict_types to maintain compatibility * renaming parentId to spanId
1 parent 8e34536 commit 0e1297c

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed

class_map/TextCodec.php

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
namespace Jaeger\Codec;
4+
5+
use Hyperf\Tracer\Support\TextCodecOtel;
6+
use Exception;
7+
use Jaeger\SpanContext;
8+
9+
use const Jaeger\TRACE_ID_HEADER;
10+
use const Jaeger\BAGGAGE_HEADER_PREFIX;
11+
use const Jaeger\DEBUG_ID_HEADER_KEY;
12+
13+
/**
14+
* @codeCoverageIgnore
15+
*/
16+
class TextCodec implements CodecInterface
17+
{
18+
private $urlEncoding;
19+
private $traceIdHeader;
20+
private $baggagePrefix;
21+
private $debugIdHeader;
22+
private $prefixLength;
23+
24+
private TextCodecOtel $openTelemetryCodec;
25+
26+
/**
27+
* @param bool $urlEncoding
28+
* @param string $traceIdHeader
29+
* @param string $baggageHeaderPrefix
30+
* @param string $debugIdHeader
31+
*/
32+
public function __construct(
33+
bool $urlEncoding = false,
34+
string $traceIdHeader = TRACE_ID_HEADER,
35+
string $baggageHeaderPrefix = BAGGAGE_HEADER_PREFIX,
36+
string $debugIdHeader = DEBUG_ID_HEADER_KEY
37+
)
38+
{
39+
$this->urlEncoding = $urlEncoding;
40+
$this->traceIdHeader = str_replace('_', '-', strtolower($traceIdHeader));
41+
$this->baggagePrefix = str_replace('_', '-', strtolower($baggageHeaderPrefix));
42+
$this->debugIdHeader = str_replace('_', '-', strtolower($debugIdHeader));
43+
$this->prefixLength = strlen($baggageHeaderPrefix);
44+
$this->openTelemetryCodec = new TextCodecOtel();
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*
50+
* @param SpanContext $spanContext
51+
* @param mixed $carrier
52+
*
53+
* @return void
54+
* @see \Jaeger\Tracer::inject
55+
*
56+
*/
57+
public function inject(SpanContext $spanContext, &$carrier)
58+
{
59+
$this->openTelemetryCodec->inject($spanContext, $carrier);
60+
61+
$carrier[$this->traceIdHeader] = $this->spanContextToString(
62+
$spanContext->getTraceId(),
63+
$spanContext->getSpanId(),
64+
$spanContext->getParentId(),
65+
$spanContext->getFlags()
66+
);
67+
68+
$baggage = $spanContext->getBaggage();
69+
if (empty($baggage)) {
70+
return;
71+
}
72+
73+
foreach ($baggage as $key => $value) {
74+
$encodedValue = $value;
75+
76+
if ($this->urlEncoding) {
77+
$encodedValue = urlencode($value);
78+
}
79+
80+
$carrier[$this->baggagePrefix . $key] = $encodedValue;
81+
}
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*
87+
* @param mixed $carrier
88+
* @return SpanContext|null
89+
*
90+
* @throws Exception
91+
* @see \Jaeger\Tracer::extract
92+
*
93+
*/
94+
public function extract($carrier)
95+
{
96+
$spanContext = $this->openTelemetryCodec->extract($carrier);
97+
if ($spanContext !== null) {
98+
return $spanContext;
99+
}
100+
101+
$traceId = null;
102+
$spanId = null;
103+
$parentId = null;
104+
$flags = null;
105+
$baggage = null;
106+
$debugId = null;
107+
108+
foreach ((array)$carrier as $key => $value) {
109+
$ucKey = strtolower($key);
110+
111+
if ($ucKey === $this->traceIdHeader) {
112+
if ($this->urlEncoding) {
113+
$value = urldecode($value);
114+
}
115+
[$traceId, $spanId, $parentId, $flags] =
116+
$this->spanContextFromString($value);
117+
} elseif ($this->startsWith($ucKey, $this->baggagePrefix)) {
118+
if ($this->urlEncoding) {
119+
$value = urldecode($value);
120+
}
121+
$attrKey = substr($key, $this->prefixLength);
122+
if ($baggage === null) {
123+
$baggage = [strtolower($attrKey) => $value];
124+
} else {
125+
$baggage[strtolower($attrKey)] = $value;
126+
}
127+
} elseif ($ucKey === $this->debugIdHeader) {
128+
if ($this->urlEncoding) {
129+
$value = urldecode($value);
130+
}
131+
$debugId = $value;
132+
}
133+
}
134+
135+
if ($traceId === null && $baggage !== null) {
136+
throw new Exception('baggage without trace ctx');
137+
}
138+
139+
if ($traceId === null) {
140+
if ($debugId !== null) {
141+
return new SpanContext(null, null, null, null, [], $debugId);
142+
}
143+
return null;
144+
}
145+
146+
return new SpanContext($traceId, $spanId, $parentId, $flags, $baggage);
147+
}
148+
149+
/**
150+
* Store a span context to a string.
151+
*
152+
* @param int $traceId
153+
* @param int $spanId
154+
* @param int $parentId
155+
* @param int $flags
156+
* @return string
157+
*/
158+
private function spanContextToString($traceId, $spanId, $parentId, $flags)
159+
{
160+
$parentId = $parentId ?? 0;
161+
if (is_int($traceId)) {
162+
$traceId = sprintf('%016x', $traceId);
163+
}
164+
return sprintf('%s:%x:%x:%x', $traceId, $spanId, $parentId, $flags);
165+
}
166+
167+
/**
168+
* Create a span context from a string.
169+
*
170+
* @param string $value
171+
* @return array
172+
*
173+
* @throws Exception
174+
*/
175+
private function spanContextFromString($value): array
176+
{
177+
$parts = explode(':', $value);
178+
179+
if (count($parts) != 4) {
180+
throw new Exception('Malformed tracer state string.');
181+
}
182+
183+
return [
184+
$parts[0],
185+
CodecUtility::hexToInt64($parts[1]),
186+
CodecUtility::hexToInt64($parts[2]),
187+
$parts[3],
188+
];
189+
}
190+
191+
/**
192+
* Checks that a string ($haystack) starts with a given prefix ($needle).
193+
*
194+
* @param string $haystack
195+
* @param string $needle
196+
* @return bool
197+
*/
198+
private function startsWith(string $haystack, string $needle): bool
199+
{
200+
return substr($haystack, 0, strlen($needle)) == $needle;
201+
}
202+
}

src/ConfigProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Jaeger\ThriftUdpTransport;
2323
use OpenTracing\Tracer;
2424
use Zipkin\Propagation\Map;
25+
use Jaeger\Codec\TextCodec;
2526

2627
class ConfigProvider
2728
{
@@ -46,6 +47,7 @@ public function __invoke(): array
4647
Map::class => __DIR__ . '/../class_map/Map.php',
4748
ThriftUdpTransport::class => __DIR__ . '/../class_map/ThriftUdpTransport.php',
4849
SpanContext::class => __DIR__ . '/../class_map/SpanContext.php',
50+
TextCodec::class => __DIR__ . '/../class_map/TextCodec.php',
4951
],
5052
],
5153
],

src/Support/TextCodecOtel.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
3+
/**
4+
* This file is part of Hyperf + OpenCodeCo
5+
*
6+
* @link https://opencodeco.dev
7+
* @document https://hyperf.wiki
8+
* @contact leo@opencodeco.dev
9+
* @license https://github.com/opencodeco/hyperf-metric/blob/main/LICENSE
10+
*/
11+
namespace Hyperf\Tracer\Support;
12+
13+
use Exception;
14+
use Jaeger\Codec\CodecInterface;
15+
use Jaeger\Codec\CodecUtility;
16+
use Jaeger\SpanContext;
17+
18+
/**
19+
* @codeCoverageIgnore
20+
*/
21+
class TextCodecOtel implements CodecInterface
22+
{
23+
const VERSION = '00';
24+
private string $traceIdHeader = 'traceparent';
25+
private string $traceStateHeader = 'tracestate';
26+
27+
/**
28+
* {@inheritdoc}
29+
*
30+
* @param SpanContext $spanContext
31+
* @param mixed $carrier
32+
*
33+
* @return void
34+
* @see \Jaeger\Tracer::inject
35+
*
36+
*/
37+
public function inject(SpanContext $spanContext, &$carrier)
38+
{
39+
$carrier[$this->traceIdHeader] = $this->spanContextToString(
40+
$spanContext->getTraceId(),
41+
$spanContext->getSpanId(),
42+
$spanContext->getFlags()
43+
);
44+
45+
$baggage = $spanContext->getBaggage();
46+
if (empty($baggage)) {
47+
return;
48+
}
49+
50+
$baggageHeader = [];
51+
52+
foreach ($baggage as $key => $value) {
53+
$baggageHeader[] = $key . '=' . $value;
54+
}
55+
$carrier[$this->traceStateHeader] = implode(',', $baggageHeader);
56+
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*
61+
* @param mixed $carrier
62+
* @return SpanContext|null
63+
*
64+
* @throws Exception
65+
* @see \Jaeger\Tracer::extract
66+
*
67+
*/
68+
public function extract($carrier)
69+
{
70+
$baggage = [];
71+
$carrier = (array)$carrier;
72+
73+
if (!isset($carrier[$this->traceIdHeader])) {
74+
return null;
75+
}
76+
77+
[$version, $traceId, $spanId, $flags] = $this->spanContextFromString($carrier[$this->traceIdHeader]);
78+
if (!empty($carrier[$this->traceStateHeader])) {
79+
$traceStateHeaders = $carrier[$this->traceStateHeader];
80+
$state = explode(',', $traceStateHeaders);
81+
foreach ($state as $stateItem) {
82+
$stateItem = trim($stateItem);
83+
$stateItem = explode('=', $stateItem);
84+
if (count($stateItem) !== 2) {
85+
continue;
86+
}
87+
$stateKey = $stateItem[0];
88+
$stateValue = $stateItem[1];
89+
$baggage[$stateKey] = $stateValue;
90+
}
91+
}
92+
93+
if ($traceId === null && $baggage !== []) {
94+
throw new Exception('baggage without trace ctx');
95+
}
96+
97+
if ($traceId === null) {
98+
return null;
99+
}
100+
101+
return new SpanContext($traceId, $spanId, null, $flags, $baggage);
102+
}
103+
104+
/**
105+
* Store a span context to a string.
106+
*
107+
* @param string $traceId
108+
* @param string $spanId
109+
* @param string $flags
110+
* @return string
111+
*/
112+
private function spanContextToString($traceId, $spanId, $flags)
113+
{
114+
if (strlen($traceId) < 32) {
115+
$start = mb_substr($traceId, 0, 3);
116+
$end = mb_substr($traceId, -3);
117+
$middle = mb_substr($traceId, 3, mb_strlen($traceId) - 6);
118+
$traceId = $start . $middle . $middle . $end;
119+
$spanId = strtolower(dechex((int)$spanId));
120+
}
121+
$flags = str_pad($flags, 2, "0", STR_PAD_LEFT);
122+
return sprintf('%s-%s-%s-%s', self::VERSION, $traceId, $spanId, $flags);
123+
}
124+
125+
/**
126+
* Create a span context from a string.
127+
*
128+
* @param string $value
129+
* @return array
130+
*
131+
* @throws Exception
132+
*/
133+
private function spanContextFromString($value): array
134+
{
135+
$parts = explode('-', $value);
136+
137+
if (count($parts) != 4) {
138+
throw new Exception('Malformed tracer state string.');
139+
}
140+
/**
141+
* TraceId em Otel ja é um hexadecimal de 32 caracteres e precisa permanecer assim.
142+
* Span id sofre conversões no caminho porém é reportado em hexa, por isso é necessária a conversão
143+
*/
144+
145+
return [
146+
$parts[0],
147+
$parts[1],//
148+
CodecUtility::hexToInt64($parts[2]),//
149+
$parts[3],
150+
];
151+
}
152+
}

0 commit comments

Comments
 (0)