Repository: open-telemetry/opentelemetry-php (php)

Issue:
TraceId mismatches between otel library & jager collector when request processed through JaegerAgentExporter Thrift using UDP
**Describe your environment** 
This issue persists irrespective of the php -v and environment. Verified using main branch as well.

**Steps to reproduce**
By using jaegertracing/all-in-one image we can spin up jager agent & collector locally. 
Then use the otel library to create a span that will have traceId and SpanId with 32 char hex string. 
While using JaegerAgentExporter to send the traceId and spanId along with parent spanId to jager agent container by converting the traceId of 36 char hex string to int64 (as Jager Thrift accepts int64 only) the conversion here (open-telemetry/opentelemetry/src/Contrib/Jaeger/IdConverter.php) doesn't go well and end up sending a wrongly converted int64 to jaeger agent which eventually gets converted to hex while getting stored in jager collector with a different 32 char hex which has no reference of the otel generated traceId. 

**What is the expected behavior?**
Ideally, otel lib generated traceId, spanId & parentSpanId should match with the  traceId, spanId & parentSpanId stored in jager collector while getting passed through Jager Agent UDP exporter like it is happening for the HTTP exporter.

**What is the actual behavior?**
otel lib generated traceId, spanId  doesn't match with the jager collector's traceId and spanId

**Additional context**
I believe we need to work on convertHexStringToSignedInt method on open-telemetry/opentelemetry/src/Contrib/Jaeger/IdConverter.php to pass correctly  converted int64 so when jager agent convert this to hex and stores in jager collector it would be the same which was generated by the otel library.

I tried a small example where I converted a 16 char hex to int64 and convert it back to hex string and it matches. (This is a potential solution to convert 16 char hex spanIds to int64 while passing to thrift, however this doesn't work with the 36 char hex traceId )

`<?php`
`// 16 char hex`
`$hex = bin2hex(random_bytes(8));`
 `echo $hex."\n";`

`$hi = intval(substr($hex, -16, -8), 16);`
`$lo = intval(substr($hex, -8, 8), 16);`

`$traceId = $hi << 32 | $lo;`

`// Converted to int 64`
`echo $traceId."\n";`

`// Converted back to same 16 char hex`
`echo dechex($traceId);`


Pull request description:
Fix #641
#641 
Now the traceId and spanId are matching, and the same can be used to search the traces from Jaeger UI. [Functional Test]

**traceId & spanId using otel lib** 
<img width="891" alt="image" src="https://user-images.githubusercontent.com/6976796/160811265-f42e32a9-b138-46fb-a301-d1dbb4a20572.png">

**traceId & spanId from Jaeger Collector** 
<img width="1642" alt="image" src="https://user-images.githubusercontent.com/6976796/160810993-f8303ef9-5a06-4603-a6a5-3c6f1057bb85.png">

**Able to search with the traceId from Jaeger UI**
<img width="916" alt="image" src="https://user-images.githubusercontent.com/6976796/160811947-fa584d1d-29b3-4112-9efb-1240b4669d39.png">



Patch:
diff --git a/composer.json b/composer.json
index 5c4326f6..943665aa 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,6 @@
     "require": {
         "php": "^7.4 || ^8.0",
         "ext-json": "*",
-        "brick/math": "^0.9.3",
         "google/protobuf": "^3.3.0",
         "grpc/grpc": "^1.30",
         "nyholm/dsn": "^2.0.0",
@@ -96,4 +95,4 @@
         "ext-protobuf": "For more performant protobuf/grpc exporting",
         "ext-sockets": "To use the Thrift UDP Exporter for the Jaeger Agent"
     }
-}
+}
\ No newline at end of file
diff --git a/examples/AlwaysOnJaegerThriftExample.php b/examples/AlwaysOnJaegerThriftExample.php
new file mode 100644
index 00000000..40721262
--- /dev/null
+++ b/examples/AlwaysOnJaegerThriftExample.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+require __DIR__ . '/../vendor/autoload.php';
+
+use Monolog\Handler\StreamHandler;
+use Monolog\Logger;
+use OpenTelemetry\Contrib\Jaeger\AgentExporter;
+use OpenTelemetry\SDK\Attributes;
+use OpenTelemetry\SDK\GlobalLoggerHolder;
+use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
+use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
+use OpenTelemetry\SDK\Trace\TracerProvider;
+use Psr\Log\LogLevel;
+
+$logger = new Logger('otel-php', [new StreamHandler(STDOUT, LogLevel::DEBUG)]);
+GlobalLoggerHolder::set($logger);
+
+$exporter = new AgentExporter('jaeger-thrift', 'jaeger:6831');
+$tracerProvider = new TracerProvider(
+    new SimpleSpanProcessor($exporter),
+    new AlwaysOnSampler(),
+);
+
+echo 'Starting JaegerThriftExample';
+
+$tracer = $tracerProvider->getTracer();
+
+$rootSpan = $tracer->spanBuilder('root')->startSpan();
+$rootSpan->activate();
+echo PHP_EOL . sprintf(
+    'Root Span: %s, Parent: %s, Span: %s',
+    $rootSpan->getContext()->getTraceId(),
+    $rootSpan->getParentContext()->getSpanId(),
+    $rootSpan->getContext()->getSpanId()
+) . PHP_EOL;
+
+for ($i = 0; $i < 1; $i++) {
+    // start a span, register some events
+    $span = $tracer->spanBuilder('session.generate.span-' . $i)->startSpan();
+
+    echo sprintf(
+        PHP_EOL . 'Exporting Trace: %s, Parent: %s, Span: %s',
+        $span->getContext()->getTraceId(),
+        $span->getParentContext()->getSpanId() ?: 'None',
+        $span->getContext()->getSpanId()
+    ) . PHP_EOL;
+
+    $span->setAttribute('remote_ip', '1.2.3.4')
+        ->setAttribute('country', 'USA');
+
+    $span->addEvent('found_login' . $i, new Attributes([
+        'id' => $i,
+        'username' => 'otuser' . $i,
+    ]));
+    $span->addEvent('generated_session', new Attributes([
+        'id' => md5((string) microtime(true)),
+    ]));
+
+    try {
+        throw new Exception('Record exception test event');
+    } catch (Exception $exception) {
+        $span->recordException($exception);
+    }
+
+    $span->end();
+}
+$rootSpan->end();
+echo PHP_EOL . 'JaegerThriftExample complete!  See the results at http://localhost:16686/';
+
+echo PHP_EOL;
diff --git a/src/Contrib/Jaeger/IdConverter.php b/src/Contrib/Jaeger/IdConverter.php
index 0f2fdd3c..c88f8bcb 100644
--- a/src/Contrib/Jaeger/IdConverter.php
+++ b/src/Contrib/Jaeger/IdConverter.php
@@ -4,20 +4,16 @@ declare(strict_types=1);
 
 namespace OpenTelemetry\Contrib\Jaeger;
 
-use Brick\Math\BigInteger;
-
 /**
  * An implementation of the section of the spec around converting OTEL's span/trace id representations to Thrift's format
  * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#ids
  */
 class IdConverter
 {
-    const HEX_STR_FOR_2_TO_THE_POWER_63 = '8000000000000000';
-
     public static function convertOtelToJaegerTraceIds(string $traceId): array
     {
-        $traceIdLow = self::convertHexStringToSignedInt(substr($traceId, 0, 16));
-        $traceIdHigh = self::convertHexStringToSignedInt(substr($traceId, 16, 32));
+        $traceIdLow = self::convert16CharHexStringToSignedInt(substr($traceId, 16, 32));
+        $traceIdHigh = self::convert16CharHexStringToSignedInt(substr($traceId, 0, 16));
 
         return [
             'traceIdLow' => $traceIdLow,
@@ -27,19 +23,19 @@ class IdConverter
 
     public static function convertOtelToJaegerSpanId(string $spanId): int
     {
-        return self::convertHexStringToSignedInt($spanId);
+        return self::convert16CharHexStringToSignedInt($spanId);
     }
 
-    private static function convertHexStringToSignedInt(string $hexString): int
+    /**
+     * PHP has the limitation to correctly convert int64 from the 16 character hex only
+     * @param string $hex
+     * @return int
+     */
+    private static function convert16CharHexStringToSignedInt(string $hex): int
     {
-        //This is using high precision arithmetic because PHP doesn't support integers greater than 2^63 - 1 (PHP_INT_MAX on a 64-bit system)
-        //But the input 16-digit hexadecimal number can represent any number between 0 and 2^64 - 1, which built-ins like intval can't handle without capping outputs at 2^63 - 1
-        $hexStringAsInteger = BigInteger::fromBase($hexString, 16);
-
-        $shiftedInteger = $hexStringAsInteger->minus(BigInteger::fromBase(self::HEX_STR_FOR_2_TO_THE_POWER_63, 16));
-
-        $signedInt = $shiftedInteger->toInt();
+        $hi = intval(substr($hex, -16, -8), 16);
+        $lo = intval(substr($hex, -8, 8), 16);
 
-        return $signedInt;
+        return $hi << 32 | $lo;
     }
 }


Test patch:
diff --git a/tests/Unit/Contrib/IdConverterTest.php b/tests/Unit/Contrib/IdConverterTest.php
index 076ddb08..8ce08a6f 100644
--- a/tests/Unit/Contrib/IdConverterTest.php
+++ b/tests/Unit/Contrib/IdConverterTest.php
@@ -12,21 +12,22 @@ use PHPUnit\Framework\TestCase;
  */
 class IdConverterTest extends TestCase
 {
-    /**
-     * @dataProvider edgeCaseData
-     */
-    public function test_correctly_converts_edge_cases($spanId, $convertedSpanId)
+    public function test_correctly_converted_span_id()
     {
-        $this->assertSame($convertedSpanId, IdConverter::convertOtelToJaegerSpanId($spanId));
+        // 16 char hex string
+        $hex = bin2hex(random_bytes(8));
+
+        $this->assertEquals($hex, dechex(IdConverter::convertOtelToJaegerSpanId($hex)));
     }
 
-    public function edgeCaseData(): iterable
+    public function test_correctly_converted_trace_id()
     {
-        yield '0 -> -2^63' => ['0000000000000000', PHP_INT_MIN];
-        yield '1 -> -2^63 + 1' => ['0000000000000001', -9223372036854775807];
-        yield '2^63 - 1 -> -1' => ['7FFFFFFFFFFFFFFF', -1];
-        yield '2^63 -> 0' => ['8000000000000000', 0];
-        yield '2^64 - 2 -> 2^63 - 2' => ['FFFFFFFFFFFFFFFE', 9223372036854775806];
-        yield '2^64 - 1 -> 2^63 - 1' => ['FFFFFFFFFFFFFFFF', 9223372036854775807];
+        // 32 char hex string
+        $hex = bin2hex(random_bytes(16));
+
+        $traceId = IdConverter::convertOtelToJaegerTraceIds($hex);
+        $convertedTraceId = dechex((int) $traceId['traceIdHigh']);
+        $convertedTraceId .= dechex((int) $traceId['traceIdLow']);
+        $this->assertEquals($hex, $convertedTraceId);
     }
 }
diff --git a/tests/Unit/Contrib/JaegerSpanConverterTest.php b/tests/Unit/Contrib/JaegerSpanConverterTest.php
index 6123fdce..cf75563d 100644
--- a/tests/Unit/Contrib/JaegerSpanConverterTest.php
+++ b/tests/Unit/Contrib/JaegerSpanConverterTest.php
@@ -27,14 +27,14 @@ class JaegerSpanConverterTest extends TestCase
     public function test_should_convert_an_otel_span_to_a_jaeger_thrift_span()
     {
         $span = (new SpanData())
-                    ->setName('otelSpanName');
+            ->setName('otelSpanName');
 
         [$convertedSpan] = (new SpanConverter())->convert([$span]);
 
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->traceIdLow);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->traceIdHigh);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->spanId);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->parentSpanId);
+        $this->assertSame(0, $convertedSpan->traceIdLow);
+        $this->assertSame(0, $convertedSpan->traceIdHigh);
+        $this->assertSame(0, $convertedSpan->spanId);
+        $this->assertSame(0, $convertedSpan->parentSpanId);
         $this->assertSame('otelSpanName', $convertedSpan->operationName);
         $this->assertSame([], $convertedSpan->references);
         $this->assertSame(0, $convertedSpan->flags);
@@ -168,17 +168,17 @@ class JaegerSpanConverterTest extends TestCase
     public function test_should_correctly_convert_span_event_to_jaeger_log()
     {
         $span = (new SpanData())
-                    ->setEvents(
-                        [
-                        new Event(
-                            'eventName',
-                            1505855794194009601,
-                            new Attributes([
-                                    'eventAttributeKey' => 'eventAttributeValue',
-                                ])
-                        ),
-                        ]
-                    );
+            ->setEvents(
+                [
+                    new Event(
+                        'eventName',
+                        1505855794194009601,
+                        new Attributes([
+                            'eventAttributeKey' => 'eventAttributeValue',
+                        ])
+                    ),
+                ]
+            );
 
         [$convertedSpan] = (new SpanConverter())->convert([$span]);
 
@@ -193,17 +193,17 @@ class JaegerSpanConverterTest extends TestCase
     public function test_should_use_event_attribute_from_event_if_present_for_jaeger_log()
     {
         $span = (new SpanData())
-                    ->setEvents(
-                        [
-                        new Event(
-                            'eventName',
-                            1505855794194009601,
-                            new Attributes([
-                                    'event' => 'valueForTheEventAttributeOnTheEvent',
-                                ])
-                        ),
-                        ]
-                    );
+            ->setEvents(
+                [
+                    new Event(
+                        'eventName',
+                        1505855794194009601,
+                        new Attributes([
+                            'event' => 'valueForTheEventAttributeOnTheEvent',
+                        ])
+                    ),
+                ]
+            );
 
         [$convertedSpan] = (new SpanConverter())->convert([$span]);
 
@@ -216,19 +216,19 @@ class JaegerSpanConverterTest extends TestCase
     public function test_should_correctly_convert_span_link_to_jaeger_span_reference()
     {
         $span = (new SpanData())
-                    ->setLinks(
-                        [
-                            new Link(
-                                SpanContext::getInvalid()
-                            ),
-                        ]
-                    );
+            ->setLinks(
+                [
+                    new Link(
+                        SpanContext::getInvalid()
+                    ),
+                ]
+            );
 
         [$convertedSpan] = (new SpanConverter())->convert([$span]);
 
         $this->assertSame(1, $convertedSpan->references[0]->refType);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->references[0]->traceIdLow);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->references[0]->traceIdHigh);
-        $this->assertSame(PHP_INT_MIN, $convertedSpan->references[0]->spanId);
+        $this->assertSame(0, $convertedSpan->references[0]->traceIdLow);
+        $this->assertSame(0, $convertedSpan->references[0]->traceIdHigh);
+        $this->assertSame(0, $convertedSpan->references[0]->spanId);
     }
 }