diff --git a/examples/ElasticApmExamples/UsingPublicApi/ExampleUsingPublicApi.php b/examples/ElasticApmExamples/UsingPublicApi/ExampleUsingPublicApi.php index d7be5ec58..ce44cea62 100644 --- a/examples/ElasticApmExamples/UsingPublicApi/ExampleUsingPublicApi.php +++ b/examples/ElasticApmExamples/UsingPublicApi/ExampleUsingPublicApi.php @@ -8,9 +8,9 @@ use Elastic\Apm\Impl\TracerBuilder; use ElasticApmTests\UnitTests\Util\MockEventSink; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class ExampleUsingPublicApi extends UnitTestCaseBase +class ExampleUsingPublicApi extends TracerUnitTestCaseBase { public function main(): void { diff --git a/src/ElasticApm/AutoInstrument/InterceptedCallTrackerInterface.php b/src/ElasticApm/AutoInstrument/InterceptedCallTrackerInterface.php index ae847abaf..9a7a378f8 100644 --- a/src/ElasticApm/AutoInstrument/InterceptedCallTrackerInterface.php +++ b/src/ElasticApm/AutoInstrument/InterceptedCallTrackerInterface.php @@ -1,11 +1,11 @@ beginCurrentTransaction($name, $type, $timestamp); + return GlobalTracerHolder::get()->beginCurrentTransaction($name, $type, $timestamp, $distributedTracingData); } /** * Begins a new transaction, sets as the current transaction, * runs the provided callback as the new transaction and automatically ends the new transaction. * - * @param string $name New transaction's name - * @param string $type New transaction's type - * @param Closure $callback Callback to execute as the new transaction - * @param float|null $timestamp Start time of the new transaction + * @param string $name New transaction's name + * @param string $type New transaction's type + * @param Closure $callback Callback to execute as the new transaction + * @param float|null $timestamp Start time of the new transaction + * @param DistributedTracingData|null $distributedTracingData + * + * @return mixed The return value of $callback * * @template T * @phpstan-param Closure(TransactionInterface $newTransaction): T $callback @@ -58,16 +62,21 @@ public static function beginCurrentTransaction( * @see TransactionInterface::setName() For the description. * @see TransactionInterface::setType() For the description. * @see TransactionInterface::getTimestamp() For the description. - * - * @return mixed The return value of $callback */ public static function captureCurrentTransaction( string $name, string $type, Closure $callback, - ?float $timestamp = null + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null ) { - return GlobalTracerHolder::get()->captureCurrentTransaction($name, $type, $callback, $timestamp); + return GlobalTracerHolder::get()->captureCurrentTransaction( + $name, + $type, + $callback, + $timestamp, + $distributedTracingData + ); } /** @@ -80,13 +89,74 @@ public static function getCurrentTransaction(): TransactionInterface return GlobalTracerHolder::get()->getCurrentTransaction(); } + /** + * Begins a new transaction. + * + * @param string $name New transaction's name + * @param string $type New transaction's type + * @param float|null $timestamp Start time of the new transaction + * @param DistributedTracingData|null $distributedTracingData + * + * @return TransactionInterface New transaction + * + * @see TransactionInterface::setName() For the description. + * @see TransactionInterface::setType() For the description. + * @see TransactionInterface::getTimestamp() For the description. + * + */ + public static function beginTransaction( + string $name, + string $type, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ): TransactionInterface { + return GlobalTracerHolder::get()->beginTransaction($name, $type, $timestamp, $distributedTracingData); + } + + /** + * Begins a new transaction, + * runs the provided callback as the new transaction and automatically ends the new transaction. + * + * @param string $name New transaction's name + * @param string $type New transaction's type + * @param Closure $callback Callback to execute as the new transaction + * @param float|null $timestamp Start time of the new transaction + * @param DistributedTracingData|null $distributedTracingData + * + * @return mixed The return value of $callback + * + * @template T + * @phpstan-param Closure(TransactionInterface $newTransaction): T $callback + * @phpstan-return T + * + * @see TransactionInterface::setName() For the description. + * @see TransactionInterface::setType() For the description. + * @see TransactionInterface::getTimestamp() For the description. + */ + public static function captureTransaction( + string $name, + string $type, + Closure $callback, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ) { + return GlobalTracerHolder::get()->captureTransaction( + $name, + $type, + $callback, + $timestamp, + $distributedTracingData + ); + } + /** * Creates an error based on the given Throwable instance * with the current execution segment (if there is one) as the parent. * * @param Throwable $throwable + * * @return string|null ID of the reported error event or null if no event was reported - * (for example, becasue recording is disabled) + * (for example, because recording is disabled) * * @link https://github.com/elastic/apm-server/blob/7.0/docs/spec/errors/error.json */ diff --git a/src/ElasticApm/ExecutionSegmentContextInterface.php b/src/ElasticApm/ExecutionSegmentContextInterface.php index d5a2abe12..10ad207b0 100644 --- a/src/ElasticApm/ExecutionSegmentContextInterface.php +++ b/src/ElasticApm/ExecutionSegmentContextInterface.php @@ -1,13 +1,9 @@ pdoAutoInstrumentation = new PdoAutoInstrumentation($tracer); + $this->curlAutoInstrumentation = new CurlAutoInstrumentation($tracer); + } + public function register(RegistrationContextInterface $ctx): void { - PdoAutoInstrumentation::register($ctx); - CurlAutoInstrumentation::register($ctx); + $this->pdoAutoInstrumentation->register($ctx); + $this->curlAutoInstrumentation->register($ctx); } public function getDescription(): string diff --git a/src/ElasticApm/Impl/AutoInstrument/CurlAutoInstrumentation.php b/src/ElasticApm/Impl/AutoInstrument/CurlAutoInstrumentation.php index ff076f82a..5a69f47e6 100644 --- a/src/ElasticApm/Impl/AutoInstrument/CurlAutoInstrumentation.php +++ b/src/ElasticApm/Impl/AutoInstrument/CurlAutoInstrumentation.php @@ -8,8 +8,13 @@ use Elastic\Apm\AutoInstrument\InterceptedCallTrackerInterface; use Elastic\Apm\AutoInstrument\RegistrationContextInterface; +use Elastic\Apm\DistributedTracingData; use Elastic\Apm\ElasticApm; use Elastic\Apm\Impl\Constants; +use Elastic\Apm\Impl\HttpDistributedTracing; +use Elastic\Apm\Impl\Log\LogCategory; +use Elastic\Apm\Impl\Log\Logger; +use Elastic\Apm\Impl\Tracer; use Elastic\Apm\Impl\Util\ArrayUtil; use Elastic\Apm\SpanInterface; @@ -20,7 +25,25 @@ */ final class CurlAutoInstrumentation { - public static function register(RegistrationContextInterface $ctx): void + /** @var Tracer */ + private $tracer; + + /** @var Logger */ + private $logger; + + public function __construct(Tracer $tracer) + { + $this->tracer = $tracer; + + $this->logger = $tracer->loggerFactory()->loggerForClass( + LogCategory::AUTO_INSTRUMENTATION, + __NAMESPACE__, + __CLASS__, + __FILE__ + ); + } + + public function register(RegistrationContextInterface $ctx): void { if (!extension_loaded('curl')) { return; @@ -29,12 +52,12 @@ public static function register(RegistrationContextInterface $ctx): void self::curlExec($ctx); } - public static function curlExec(RegistrationContextInterface $ctx): void + public function curlExec(RegistrationContextInterface $ctx): void { $ctx->interceptCallsToFunction( 'curl_exec', function (): InterceptedCallTrackerInterface { - return new class implements InterceptedCallTrackerInterface { + return new class ($this) implements InterceptedCallTrackerInterface { use InterceptedCallTrackerTrait; /** @var resource|null */ @@ -43,6 +66,14 @@ function (): InterceptedCallTrackerInterface { /** @var SpanInterface */ private $span; + /** @var CurlAutoInstrumentation */ + private $owner; + + public function __construct(CurlAutoInstrumentation $owner) + { + $this->owner = $owner; + } + public function preHook(?object $interceptedCallThis, ...$interceptedCallArgs): void { self::assertInterceptedCallThisIsNull($interceptedCallThis, ...$interceptedCallArgs); @@ -56,6 +87,14 @@ public function preHook(?object $interceptedCallThis, ...$interceptedCallArgs): Constants::SPAN_TYPE_EXTERNAL, Constants::SPAN_TYPE_EXTERNAL_SUBTYPE_HTTP ); + + $distributedTracingData = $this->span->getDistributedTracingData(); + if (!is_null($this->curlHandle) && !is_null($distributedTracingData)) { + $this->owner->injectDistributedTracingDataHeader( + $this->curlHandle, + $distributedTracingData + ); + } } public function postHook( @@ -84,4 +123,38 @@ public function postHook( } ); } + + /** + * @param resource $curlHandle + * @param DistributedTracingData $data + */ + public function injectDistributedTracingDataHeader($curlHandle, DistributedTracingData $data): void + { + $traceParentHeaderValue = $this->tracer->httpDistributedTracing()->buildTraceParentHeader($data); + $headers = [HttpDistributedTracing::TRACE_PARENT_HEADER_NAME . ': ' . $traceParentHeaderValue]; + + ($loggerProxy = $this->logger->ifTraceLevelEnabled(__LINE__, __FUNCTION__)) + && $loggerProxy->log( + 'Injecting outgoing ' . HttpDistributedTracing::TRACE_PARENT_HEADER_NAME . ' HTTP request header...', + ['traceParentHeaderValue' => $traceParentHeaderValue] + ); + + $setOptRetVal = curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headers); + + if ($setOptRetVal) { + ($loggerProxy = $this->logger->ifTraceLevelEnabled(__LINE__, __FUNCTION__)) + && $loggerProxy->log( + 'Successfully injected outgoing ' + . HttpDistributedTracing::TRACE_PARENT_HEADER_NAME . ' HTTP request header', + ['traceParentHeaderValue' => $traceParentHeaderValue] + ); + } else { + ($loggerProxy = $this->logger->ifErrorLevelEnabled(__LINE__, __FUNCTION__)) + && $loggerProxy->log( + 'Failed to inject outgoing ' + . HttpDistributedTracing::TRACE_PARENT_HEADER_NAME . ' HTTP request header', + ['traceParentHeaderValue' => $traceParentHeaderValue] + ); + } + } } diff --git a/src/ElasticApm/Impl/AutoInstrument/InterceptionManager.php b/src/ElasticApm/Impl/AutoInstrument/InterceptionManager.php index a51b9d2ce..ea47f0376 100644 --- a/src/ElasticApm/Impl/AutoInstrument/InterceptionManager.php +++ b/src/ElasticApm/Impl/AutoInstrument/InterceptionManager.php @@ -30,20 +30,20 @@ public function __construct(Tracer $tracer) $this->logger = $tracer->loggerFactory() ->loggerForClass(LogCategory::INTERCEPTION, __NAMESPACE__, __CLASS__, __FILE__); - $this->loadPlugins(); + $this->loadPlugins($tracer); } - private function loadPlugins(): void + private function loadPlugins(Tracer $tracer): void { $registerCtx = new RegistrationContext(); - $this->loadPluginsImpl($registerCtx); + $this->loadPluginsImpl($tracer, $registerCtx); $this->interceptedCallRegistrations = $registerCtx->interceptedCallRegistrations; } - private function loadPluginsImpl(RegistrationContext $registerCtx): void + private function loadPluginsImpl(Tracer $tracer, RegistrationContext $registerCtx): void { - $builtinPlugin = new BuiltinPlugin(); + $builtinPlugin = new BuiltinPlugin($tracer); $registerCtx->dbgCurrentPluginIndex = 0; $registerCtx->dbgCurrentPluginDesc = $builtinPlugin->getDescription(); $builtinPlugin->register($registerCtx); diff --git a/src/ElasticApm/Impl/AutoInstrument/PdoAutoInstrumentation.php b/src/ElasticApm/Impl/AutoInstrument/PdoAutoInstrumentation.php index fd38c9656..f5e01b1c6 100644 --- a/src/ElasticApm/Impl/AutoInstrument/PdoAutoInstrumentation.php +++ b/src/ElasticApm/Impl/AutoInstrument/PdoAutoInstrumentation.php @@ -8,6 +8,7 @@ use Elastic\Apm\AutoInstrument\RegistrationContextInterface; use Elastic\Apm\ElasticApm; use Elastic\Apm\Impl\Constants; +use Elastic\Apm\Impl\Tracer; use Elastic\Apm\Impl\Util\DbgUtil; use Elastic\Apm\SpanInterface; @@ -18,17 +19,25 @@ */ final class PdoAutoInstrumentation { - public static function register(RegistrationContextInterface $ctx): void + /** @var Tracer */ + private $tracer; + + public function __construct(Tracer $tracer) + { + $this->tracer = $tracer; + } + + public function register(RegistrationContextInterface $ctx): void { if (!extension_loaded('pdo')) { return; } - self::pdoConstruct($ctx); - self::pdoExec($ctx); + $this->pdoConstruct($ctx); + $this->pdoExec($ctx); } - public static function pdoConstruct(RegistrationContextInterface $ctx): void + public function pdoConstruct(RegistrationContextInterface $ctx): void { $ctx->interceptCallsToMethod( 'PDO', @@ -70,7 +79,7 @@ public function postHook( ); } - public static function pdoExec(RegistrationContextInterface $ctx): void + public function pdoExec(RegistrationContextInterface $ctx): void { $ctx->interceptCallsToMethod( 'PDO', diff --git a/src/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php b/src/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php index 97dacb510..57531aeec 100644 --- a/src/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php +++ b/src/ElasticApm/Impl/AutoInstrument/TransactionForExtensionRequest.php @@ -4,8 +4,9 @@ namespace Elastic\Apm\Impl\AutoInstrument; -use Elastic\Apm\ElasticApm; +use Elastic\Apm\DistributedTracingData; use Elastic\Apm\Impl\Constants; +use Elastic\Apm\Impl\HttpDistributedTracing; use Elastic\Apm\Impl\Log\LogCategory; use Elastic\Apm\Impl\Log\Logger; use Elastic\Apm\Impl\Tracer; @@ -21,6 +22,9 @@ final class TransactionForExtensionRequest { private const DEFAULT_NAME = 'Unnamed transaction'; + /** @var Tracer */ + private $tracer; + /** @var Logger */ private $logger; @@ -29,8 +33,9 @@ final class TransactionForExtensionRequest public function __construct(Tracer $tracer, float $requestInitStartTime) { + $this->tracer = $tracer; $this->logger = $tracer->loggerFactory() - ->loggerForClass(LogCategory::DISCOVERY, __NAMESPACE__, __CLASS__, __FILE__); + ->loggerForClass(LogCategory::AUTO_INSTRUMENTATION, __NAMESPACE__, __CLASS__, __FILE__); $this->transactionForRequest = $this->beginTransaction($requestInitStartTime); } @@ -40,8 +45,9 @@ private function beginTransaction(float $requestInitStartTime): TransactionInter $name = self::isCliScript() ? $this->discoverCliName() : $this->discoverHttpName(); $type = self::isCliScript() ? Constants::TRANSACTION_TYPE_CLI : Constants::TRANSACTION_TYPE_REQUEST; $timestamp = $this->discoverTimestamp($requestInitStartTime); + $distributedTracingData = $this->discoverIncomingDistributedTracingData(); - return ElasticApm::beginCurrentTransaction($name, $type, $timestamp); + return $this->tracer->beginCurrentTransaction($name, $type, $timestamp, $distributedTracingData); } public function onShutdown(): void @@ -111,11 +117,8 @@ private function discoverHttpName(): string private function discoverTimestamp(float $requestInitStartTime): float { - if ( - !is_null( - $serverRequestTimeAsString = ArrayUtil::getValueIfKeyExistsElse('REQUEST_TIME_FLOAT', $_SERVER, null) - ) - ) { + $serverRequestTimeAsString = ArrayUtil::getValueIfKeyExistsElse('REQUEST_TIME_FLOAT', $_SERVER, null); + if (!is_null($serverRequestTimeAsString)) { $serverRequestTimeInSeconds = floatval($serverRequestTimeAsString); $serverRequestTimeInMicroseconds = $serverRequestTimeInSeconds * 1000000; @@ -136,6 +139,26 @@ private function discoverTimestamp(float $requestInitStartTime): float return $requestInitStartTime; } + private function discoverIncomingDistributedTracingData(): ?DistributedTracingData + { + $headerName = HttpDistributedTracing::TRACE_PARENT_HEADER_NAME; + $traceParentHeaderKey = 'HTTP_' . strtoupper($headerName); + if (!array_key_exists($traceParentHeaderKey, $_SERVER)) { + ($loggerProxy = $this->logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) + && $loggerProxy->log('Incoming ' . $headerName . ' HTTP request header not found'); + return null; + } + + $traceParentHeaderValue = $_SERVER[$traceParentHeaderKey]; + ($loggerProxy = $this->logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) + && $loggerProxy->log( + 'Incoming ' . HttpDistributedTracing::TRACE_PARENT_HEADER_NAME . ' HTTP request header found', + ['traceParentHeaderValue' => $traceParentHeaderValue] + ); + + return $this->tracer->httpDistributedTracing()->parseTraceParentHeader($traceParentHeaderValue); + } + private function discoverHttpResult(): ?string { $statusCode = http_response_code(); diff --git a/src/ElasticApm/Impl/ExecutionSegment.php b/src/ElasticApm/Impl/ExecutionSegment.php index ad2559dea..00c1e7d2f 100644 --- a/src/ElasticApm/Impl/ExecutionSegment.php +++ b/src/ElasticApm/Impl/ExecutionSegment.php @@ -1,7 +1,5 @@ logger = $loggerFactory->loggerForClass( + LogCategory::DISTRIBUTED_TRACING, + __NAMESPACE__, + __CLASS__, + __FILE__ + ); + } + + public function parseTraceParentHeader(string $headerValue): ?DistributedTracingData + { + // 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01 + // ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^ + // || |||||||||||||||||||||||||||||||| |||||||||||||||| -- - flagsAsString + // || |||||||||||||||||||||||||||||||| ---------------- - parentId + // || -------------------------------- - trace-id + // -- - version + + $parentFunc = __FUNCTION__; + $logParsingFailedMessage = function ( + string $reason, + array $context, + int $srcCodeLineNumber + ) use ( + $parentFunc, + $headerValue + ): void { + ($loggerProxy = $this->logger->ifDebugLevelEnabled($srcCodeLineNumber, $parentFunc)) + && $loggerProxy->log( + "Failed to parse HTTP header used for distributed tracing: $reason", + array_merge($context, ['headerValue' => $headerValue]) + ); + }; + + $result = new DistributedTracingData(); + + $expectedNumberOfParts = 4; + $parts = explode(/* delimiter: */ '-', $headerValue, /* limit: */ $expectedNumberOfParts); + if (count($parts) < $expectedNumberOfParts) { + $logParsingFailedMessage( + "there are less than $expectedNumberOfParts delimited parts", + ['parts' => $parts], + __LINE__ + ); + return null; + } + + $version = $parts[0]; + if ($version !== self::SUPPORTED_FORMAT_VERSION) { + $logParsingFailedMessage('unsupported version', ['version' => $version, 'parts' => $parts], __LINE__); + return null; + } + + $traceId = $parts[1]; + if (!IdValidationUtil::isValidHexNumberString($traceId, Constants::TRACE_ID_SIZE_IN_BYTES)) { + $logParsingFailedMessage( + 'traceId is not a valid ' . Constants::TRACE_ID_SIZE_IN_BYTES . ' bytes hex ID', + ['traceId' => $traceId, 'parts' => $parts], + __LINE__ + ); + return null; + } + if ($traceId === self::INVALID_TRACE_ID) { + $logParsingFailedMessage( + 'traceId that is all bytes as zero (00000000000000000000000000000000) is considered an invalid value', + ['traceId' => $traceId, 'parts' => $parts], + __LINE__ + ); + return null; + } + $result->traceId = strtolower($traceId); + + $parentId = $parts[2]; + if (!IdValidationUtil::isValidHexNumberString($parentId, Constants::EXECUTION_SEGMENT_ID_SIZE_IN_BYTES)) { + $logParsingFailedMessage( + 'parentId is not a valid ' . Constants::EXECUTION_SEGMENT_ID_SIZE_IN_BYTES . ' bytes hex ID', + ['parentId' => $parentId, 'parts' => $parts], + __LINE__ + ); + return null; + } + if ($parentId === self::INVALID_PARENT_ID) { + $logParsingFailedMessage( + 'parentId that is all bytes as zero (0000000000000000) is considered an invalid value', + ['parentId' => $parentId, 'parts' => $parts], + __LINE__ + ); + return null; + } + $result->parentId = strtolower($parentId); + + $flagsAsString = $parts[3]; + if (!IdValidationUtil::isValidHexNumberString($flagsAsString, /* $expectedSizeInBytes */ 1)) { + $logParsingFailedMessage( + 'flagsAsString is not a valid 1 byte hex number', + ['$flagsAsString' => $flagsAsString, 'parts' => $parts], + __LINE__ + ); + return null; + } + $flagsAsInt = hexdec($flagsAsString); + $result->isSampled = ($flagsAsInt & self::SAMPLED_FLAG) === 1; + + return $result; + } + + public function buildTraceParentHeader(DistributedTracingData $data): string + { + return self::SUPPORTED_FORMAT_VERSION + . '-' . $data->traceId + . '-' . $data->parentId + . '-' . ($data->isSampled ? '01' : '00'); + } +} diff --git a/src/ElasticApm/Impl/Log/LogCategory.php b/src/ElasticApm/Impl/Log/LogCategory.php index a99480e8d..598631437 100644 --- a/src/ElasticApm/Impl/Log/LogCategory.php +++ b/src/ElasticApm/Impl/Log/LogCategory.php @@ -15,9 +15,11 @@ final class LogCategory { use StaticClassTrait; + public const AUTO_INSTRUMENTATION = 'Auto-Instrumentation'; public const BACKEND_COMM = 'Backend-Comm'; public const CONFIGURATION = 'Configuration'; public const DISCOVERY = 'Discovery'; + public const DISTRIBUTED_TRACING = 'Distributed-Tracing'; public const INTERCEPTION = 'Interception'; public const PUBLIC_API = 'Public-API'; } diff --git a/src/ElasticApm/Impl/Log/SinkBase.php b/src/ElasticApm/Impl/Log/SinkBase.php index 0fb55a704..e70e44e66 100644 --- a/src/ElasticApm/Impl/Log/SinkBase.php +++ b/src/ElasticApm/Impl/Log/SinkBase.php @@ -4,6 +4,8 @@ namespace Elastic\Apm\Impl\Log; +use Elastic\Apm\Impl\Util\TextUtil; + /** * Code in this file is part of implementation internals and thus it is not covered by the backward compatibility. * @@ -36,7 +38,8 @@ public function consume( = LoggablePhpStacktrace::buildForCurrent($numberOfStackFramesToSkip + 1); } - $messageWithContext = $message . '. ' . LoggableToString::convert($combinedContext); + $afterMessageDelimiter = TextUtil::isSuffixOf('.', $message) ? '' : '.'; + $messageWithContext = $message . $afterMessageDelimiter . ' ' . LoggableToString::convert($combinedContext); $this->consumePreformatted( $statementLevel, diff --git a/src/ElasticApm/Impl/NoopExecutionSegment.php b/src/ElasticApm/Impl/NoopExecutionSegment.php index 268c001b7..85cd1d221 100644 --- a/src/ElasticApm/Impl/NoopExecutionSegment.php +++ b/src/ElasticApm/Impl/NoopExecutionSegment.php @@ -7,6 +7,7 @@ namespace Elastic\Apm\Impl; use Closure; +use Elastic\Apm\DistributedTracingData; use Elastic\Apm\ExecutionSegmentInterface; use Elastic\Apm\Impl\Log\LoggableInterface; use Elastic\Apm\SpanInterface; @@ -31,6 +32,9 @@ abstract class NoopExecutionSegment implements ExecutionSegmentInterface, Loggab /** @var string */ public const TYPE = 'noop'; + /** @var DistributedTracingData */ + private static $noopDistributedTracingData; + /** @inheritDoc */ public function getTimestamp(): float { @@ -82,6 +86,12 @@ public function captureChildSpan( return $callback(NoopSpan::singletonInstance()); } + /** @inheritDoc */ + public function getDistributedTracingData(): ?DistributedTracingData + { + return null; + } + /** @inheritDoc */ public function end(?float $duration = null): void { diff --git a/src/ElasticApm/Impl/NoopTracer.php b/src/ElasticApm/Impl/NoopTracer.php index 02b522497..4897f4c29 100644 --- a/src/ElasticApm/Impl/NoopTracer.php +++ b/src/ElasticApm/Impl/NoopTracer.php @@ -1,12 +1,11 @@ data->subtype = $this->tracer->limitNullableKeywordString($subtype); } + /** @inheritDoc */ + public function getDistributedTracingData(): ?DistributedTracingData + { + $spanAsParent = $this->shouldBeSentToApmServer() ? $this : null; + return $this->containingTransaction->doGetDistributedTracingData($spanAsParent); + } + /** @inheritDoc */ public function beginChildSpan( string $name, diff --git a/src/ElasticApm/Impl/Tracer.php b/src/ElasticApm/Impl/Tracer.php index f6ecf485a..95fcce43b 100644 --- a/src/ElasticApm/Impl/Tracer.php +++ b/src/ElasticApm/Impl/Tracer.php @@ -5,6 +5,7 @@ namespace Elastic\Apm\Impl; use Closure; +use Elastic\Apm\DistributedTracingData; use Elastic\Apm\Impl\BackendComm\EventSender; use Elastic\Apm\Impl\Config\AllOptionsMetadata; use Elastic\Apm\Impl\Config\CompositeRawSnapshotSource; @@ -61,6 +62,9 @@ final class Tracer implements TracerInterface, LoggableInterface /** @var Metadata */ private $currentMetadata; + /** @var HttpDistributedTracing */ + private $httpDistributedTracing; + public function __construct(TracerDependencies $providedDependencies) { $this->providedDependencies = $providedDependencies; @@ -89,6 +93,8 @@ public function __construct(TracerDependencies $providedDependencies) ); $this->currentMetadata = MetadataDiscoverer::discoverMetadata($this->config); + + $this->httpDistributedTracing = new HttpDistributedTracing($this->loggerFactory); } private function buildConfig(): ConfigSnapshot @@ -114,30 +120,26 @@ public function getConfig(): ConfigSnapshot return $this->config; } - private function beginTransactionImpl(string $name, string $type, ?float $timestamp): ?Transaction - { + private function beginTransactionImpl( + string $name, + string $type, + ?float $timestamp, + ?DistributedTracingData $distributedTracingData + ): ?Transaction { if (!$this->isRecording) { return null; } - return new Transaction($this, $name, $type, $timestamp); - } - - /** @inheritDoc */ - public function beginTransaction(string $name, string $type, ?float $timestamp = null): TransactionInterface - { - $newTransaction = $this->beginTransactionImpl($name, $type, $timestamp); - - if (is_null($newTransaction)) { - return NoopTransaction::singletonInstance(); - } - - return $newTransaction; + return new Transaction($this, $name, $type, $timestamp, $distributedTracingData); } /** @inheritDoc */ - public function beginCurrentTransaction(string $name, string $type, ?float $timestamp = null): TransactionInterface - { + public function beginCurrentTransaction( + string $name, + string $type, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ): TransactionInterface { if (!is_null($this->currentTransaction)) { ($loggerProxy = $this->logger->ifWarningLevelEnabled(__LINE__, __FUNCTION__)) && $loggerProxy->log( @@ -147,7 +149,7 @@ public function beginCurrentTransaction(string $name, string $type, ?float $time ); } - $this->currentTransaction = $this->beginTransactionImpl($name, $type, $timestamp); + $this->currentTransaction = $this->beginTransactionImpl($name, $type, $timestamp, $distributedTracingData); if (is_null($this->currentTransaction)) { return NoopTransaction::singletonInstance(); } @@ -156,24 +158,59 @@ public function beginCurrentTransaction(string $name, string $type, ?float $time } /** @inheritDoc */ - public function captureTransaction(string $name, string $type, Closure $callback, ?float $timestamp = null) - { - $newTransaction = $this->beginTransaction($name, $type, $timestamp); + public function captureCurrentTransaction( + string $name, + string $type, + Closure $callback, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ) { + $newTransaction = $this->beginCurrentTransaction($name, $type, $timestamp, $distributedTracingData); try { return $callback($newTransaction); } catch (Throwable $throwable) { $newTransaction->createError($throwable); - /** @noinspection PhpUnhandledExceptionInspection */ throw $throwable; } finally { $newTransaction->end(); } } - /** @inheritDoc */ - public function captureCurrentTransaction(string $name, string $type, Closure $callback, ?float $timestamp = null) + public function getCurrentTransaction(): TransactionInterface + { + return $this->currentTransaction ?? NoopTransaction::singletonInstance(); + } + + public function resetCurrentTransaction(): void { - $newTransaction = $this->beginCurrentTransaction($name, $type, $timestamp); + $this->currentTransaction = null; + } + + /** @inheritDoc */ + public function beginTransaction( + string $name, + string $type, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ): TransactionInterface { + $newTransaction = $this->beginTransactionImpl($name, $type, $timestamp, $distributedTracingData); + + if (is_null($newTransaction)) { + return NoopTransaction::singletonInstance(); + } + + return $newTransaction; + } + + /** @inheritDoc */ + public function captureTransaction( + string $name, + string $type, + Closure $callback, + ?float $timestamp = null, + ?DistributedTracingData $distributedTracingData = null + ) { + $newTransaction = $this->beginTransaction($name, $type, $timestamp, $distributedTracingData); try { return $callback($newTransaction); } catch (Throwable $throwable) { @@ -233,16 +270,6 @@ public function isNoop(): bool return false; } - public function getCurrentTransaction(): TransactionInterface - { - return $this->currentTransaction ?? NoopTransaction::singletonInstance(); - } - - public function resetCurrentTransaction(): void - { - $this->currentTransaction = null; - } - public static function limitKeywordString(string $keywordString): string { return TextUtil::ensureMaxLength($keywordString, Constants::KEYWORD_STRING_MAX_LENGTH); @@ -266,6 +293,11 @@ public function loggerFactory(): LoggerFactory return $this->loggerFactory; } + public function httpDistributedTracing(): HttpDistributedTracing + { + return $this->httpDistributedTracing; + } + public function pauseRecording(): void { $this->isRecording = false; diff --git a/src/ElasticApm/Impl/TracerInterface.php b/src/ElasticApm/Impl/TracerInterface.php index 2368341e9..57b8c70a9 100644 --- a/src/ElasticApm/Impl/TracerInterface.php +++ b/src/ElasticApm/Impl/TracerInterface.php @@ -1,12 +1,11 @@ data = new TransactionData(); + if (is_null($distributedTracingData)) { + $traceId = IdGenerator::generateId(Constants::TRACE_ID_SIZE_IN_BYTES); + } else { + $traceId = $distributedTracingData->traceId; + $this->data->parentId = $distributedTracingData->parentId; + } + parent::__construct( $tracer, - IdGenerator::generateId(Constants::TRACE_ID_SIZE_IN_BYTES), + $traceId, $name, $type, $timestamp @@ -57,7 +70,9 @@ public function __construct(Tracer $tracer, string $name, string $type, ?float $ $this->logger = $this->createLogger(__NAMESPACE__, __CLASS__, __FILE__); - $this->data->isSampled = $this->makeSamplingDecision(); + $this->data->isSampled = is_null($distributedTracingData) + ? $this->makeSamplingDecision() + : $distributedTracingData->isSampled; ($loggerProxy = $this->logger->ifDebugLevelEnabled(__LINE__, __FUNCTION__)) && $loggerProxy->log('Transaction created'); @@ -295,6 +310,29 @@ public function createError(Throwable $throwable): ?string return $this->currentSpan->createError($throwable); } + /** @inheritDoc */ + public function getDistributedTracingData(): ?DistributedTracingData + { + if (is_null($this->currentSpan)) { + return $this->doGetDistributedTracingData(/* span */ null); + } + + return $this->currentSpan->getDistributedTracingData(); + } + + public function doGetDistributedTracingData(?Span $span): ?DistributedTracingData + { + if (!$this->tracer->isRecording()) { + return null; + } + + $result = new DistributedTracingData(); + $result->traceId = $this->data->traceId; + $result->parentId = is_null($span) ? $this->data->id : $span->getId(); + $result->isSampled = $this->data->isSampled; + return $result; + } + public function queueSpanDataToSend(SpanData $spanData): void { if ($this->hasEnded()) { diff --git a/src/ElasticApm/Impl/Util/IdValidationUtil.php b/src/ElasticApm/Impl/Util/IdValidationUtil.php new file mode 100644 index 000000000..af9ad8eb8 --- /dev/null +++ b/src/ElasticApm/Impl/Util/IdValidationUtil.php @@ -0,0 +1,36 @@ +filename); self::assertSame(self::$callToMethodThrowingDummyExceptionForTestsLineNumber, $topFrame->lineno); } - // TODO: Sergey Kleyman: Add checks for errors } } diff --git a/tests/ElasticApmTests/UnitTests/ExamplePublicApiElasticApmTest.php b/tests/ElasticApmTests/UnitTests/ExamplePublicApiElasticApmTest.php index ce206fbfb..f27457db4 100644 --- a/tests/ElasticApmTests/UnitTests/ExamplePublicApiElasticApmTest.php +++ b/tests/ElasticApmTests/UnitTests/ExamplePublicApiElasticApmTest.php @@ -7,9 +7,9 @@ use Elastic\Apm\Impl\SpanData; use Elastic\Apm\Impl\TransactionData; use ElasticApmTests\UnitTests\Util\ArrayTestUtil; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class ExamplePublicApiElasticApmTest extends UnitTestCaseBase +class ExamplePublicApiElasticApmTest extends TracerUnitTestCaseBase { public function test(): void { diff --git a/tests/ElasticApmTests/UnitTests/GlobalTracerTest.php b/tests/ElasticApmTests/UnitTests/GlobalTracerTest.php index 14f1cdeab..bbf740051 100644 --- a/tests/ElasticApmTests/UnitTests/GlobalTracerTest.php +++ b/tests/ElasticApmTests/UnitTests/GlobalTracerTest.php @@ -5,9 +5,9 @@ namespace ElasticApmTests\UnitTests; use Elastic\Apm\Impl\GlobalTracerHolder; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class GlobalTracerTest extends UnitTestCaseBase +class GlobalTracerTest extends TracerUnitTestCaseBase { public function testGlobalTracerIsInitializedOnFirstAccess(): void { diff --git a/tests/ElasticApmTests/UnitTests/HttpDistributedTracingTest.php b/tests/ElasticApmTests/UnitTests/HttpDistributedTracingTest.php new file mode 100644 index 000000000..edac2c38b --- /dev/null +++ b/tests/ElasticApmTests/UnitTests/HttpDistributedTracingTest.php @@ -0,0 +1,108 @@ + + * @phpstan-return array{string, ?DistributedTracingData} + */ + private static function buildValidInput(string $traceId, string $parentId, bool $isSampled): array + { + $data = new DistributedTracingData(); + $data->traceId = strtolower($traceId); + $data->parentId = strtolower($parentId); + $data->isSampled = $isSampled; + return ['00' . '-' . $traceId . '-' . $parentId . '-' . ($isSampled ? '01' : '00'), $data]; + } + + /** + * @return iterable> + * @phpstan-return iterable + */ + public function dataProviderForTestParseTraceParentHeader(): iterable + { + yield self::buildValidInput('0af7651916cd43dd8448eb211c80319c', 'b9c7c989f97918e1', true); + yield self::buildValidInput('0af7651916cd43dd8448eb211c80319c', 'b9c7c989f97918e1', false); + yield self::buildValidInput('11111111111111111111111111111111', '2222222222222222', true); + yield self::buildValidInput('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'BBBBBBBBBBBBBBBB', true); + + // Erroneous input: + + yield ['', null]; // empty string + yield ['000af7651916cd43dd8448eb211c80319cb9c7c989f97918e101', null]; // delimiters are missing + yield ['000af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // delimiters are missing + yield ['00-0af7651916cd43dd8448eb211c80319cb9c7c989f97918e1-01', null]; // delimiters are missing + yield ['00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e101', null]; // delimiters are missing + yield ['000af7651916cd43dd8448eb211c80319cb9c7c989f97918e1-01', null]; // delimiters are missing + yield ['00-0af7651916cd43dd8448eb211c80319cb9c7c989f97918e101', null]; // delimiters are missing + + yield ['01-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: unsupported + yield ['-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: missing + yield ['0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: missing + yield ['0-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: too short + yield ['000-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: too long + yield ['0k-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: not a hex number + yield ['k0-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01', null]; // version: not a hex number + + yield ['00-00000000000000000000000000000000-b9c7c989f97918e1-01', null]; // traceId: invalid + yield ['01-0af7651916cd43dd8448eb211c80319-b9c7c989f97918e1-01', null]; // traceId too short + yield ['01-0af7651916cd43dd8448eb211c80319cc-b9c7c989f97918e1-01', null]; // traceId too long + + yield ['00-0af7651916cd43dd8448eb211c80319c-0000000000000000-01', null]; // parentId: invalid + } + + /** + * @dataProvider dataProviderForTestParseTraceParentHeader + * + * @param string $headerValue + * @param DistributedTracingData|null $expectedData + */ + public function testParseTraceParentHeader(string $headerValue, ?DistributedTracingData $expectedData): void + { + $httpDistributedTracing = new HttpDistributedTracing(self::noopLoggerFactory()); + $actualData = $httpDistributedTracing->parseTraceParentHeader($headerValue); + self::assertEquals($expectedData, $actualData); + } + + /** + * @return iterable> + * @phpstan-return iterable + */ + public function dataProviderForTestBuildTraceParentHeader(): iterable + { + foreach (self::dataProviderForTestParseTraceParentHeader() as $headerDataPair) { + if (is_null($headerDataPair[1])) { + continue; + } + + yield [$headerDataPair[1], strtolower($headerDataPair[0])]; + } + } + + /** + * @dataProvider dataProviderForTestBuildTraceParentHeader + * + * @param DistributedTracingData $data + * @param string $expectedHeaderValue + */ + public function testBuildHeader(DistributedTracingData $data, string $expectedHeaderValue): void + { + $httpDistributedTracing = new HttpDistributedTracing(self::noopLoggerFactory()); + $builtHeaderValue = $httpDistributedTracing->buildTraceParentHeader($data); + self::assertEquals($expectedHeaderValue, $builtHeaderValue); + } +} diff --git a/tests/ElasticApmTests/UnitTests/LoggingVariousEventTypesTest.php b/tests/ElasticApmTests/UnitTests/LoggingVariousEventTypesTest.php index b10fcd57a..7a69cea43 100644 --- a/tests/ElasticApmTests/UnitTests/LoggingVariousEventTypesTest.php +++ b/tests/ElasticApmTests/UnitTests/LoggingVariousEventTypesTest.php @@ -6,9 +6,9 @@ use Elastic\Apm\ElasticApm; use ElasticApmTests\UnitTests\LogTests\LoggingVariousTypesTest; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class LoggingVariousEventTypesTest extends UnitTestCaseBase +class LoggingVariousEventTypesTest extends TracerUnitTestCaseBase { public function testTransaction(): void { diff --git a/tests/ElasticApmTests/UnitTests/NoopEventsTest.php b/tests/ElasticApmTests/UnitTests/NoopEventsTest.php index ef1abc574..022829473 100644 --- a/tests/ElasticApmTests/UnitTests/NoopEventsTest.php +++ b/tests/ElasticApmTests/UnitTests/NoopEventsTest.php @@ -11,9 +11,9 @@ use Elastic\Apm\Impl\TracerBuilder; use Elastic\Apm\SpanInterface; use Elastic\Apm\TransactionInterface; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class NoopEventsTest extends UnitTestCaseBase +class NoopEventsTest extends TracerUnitTestCaseBase { public function setUp(): void { diff --git a/tests/ElasticApmTests/UnitTests/PublicApiTest.php b/tests/ElasticApmTests/UnitTests/PublicApiTest.php index 204ff8b7a..b96ecd96c 100644 --- a/tests/ElasticApmTests/UnitTests/PublicApiTest.php +++ b/tests/ElasticApmTests/UnitTests/PublicApiTest.php @@ -8,9 +8,9 @@ use Elastic\Apm\ElasticApm; use ElasticApmTests\UnitTests\Util\NotFoundException; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class PublicApiTest extends UnitTestCaseBase +class PublicApiTest extends TracerUnitTestCaseBase { public function testBeginEndTransaction(): void { diff --git a/tests/ElasticApmTests/UnitTests/SamplingUnitTest.php b/tests/ElasticApmTests/UnitTests/SamplingUnitTest.php index c81d9884a..2d035751b 100644 --- a/tests/ElasticApmTests/UnitTests/SamplingUnitTest.php +++ b/tests/ElasticApmTests/UnitTests/SamplingUnitTest.php @@ -9,9 +9,9 @@ use Elastic\Apm\Impl\TracerBuilder; use ElasticApmTests\TestsSharedCode\SamplingTestSharedCode; use ElasticApmTests\UnitTests\Util\MockConfigRawSnapshotSource; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class SamplingUnitTest extends UnitTestCaseBase +class SamplingUnitTest extends TracerUnitTestCaseBase { /** * @return iterable diff --git a/tests/ElasticApmTests/UnitTests/ServerApiSchemaValidationTest.php b/tests/ElasticApmTests/UnitTests/ServerApiSchemaValidationTest.php index 98e65d43a..23cdfb147 100644 --- a/tests/ElasticApmTests/UnitTests/ServerApiSchemaValidationTest.php +++ b/tests/ElasticApmTests/UnitTests/ServerApiSchemaValidationTest.php @@ -6,10 +6,8 @@ use Closure; use Elastic\Apm\Impl\BackendComm\SerializationUtil; -use Elastic\Apm\Impl\TracerBuilder; use Elastic\Apm\Impl\Util\JsonUtil; use ElasticApmTests\UnitTests\Util\MockEventSink; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; use ElasticApmTests\Util\Deserialization\ServerApiSchemaValidationException; use ElasticApmTests\Util\Deserialization\ServerApiSchemaValidator; use ElasticApmTests\Util\TestCaseBase; diff --git a/tests/ElasticApmTests/UnitTests/StacktraceUnitTest.php b/tests/ElasticApmTests/UnitTests/StacktraceUnitTest.php index 1bc217e1b..e3756a91a 100644 --- a/tests/ElasticApmTests/UnitTests/StacktraceUnitTest.php +++ b/tests/ElasticApmTests/UnitTests/StacktraceUnitTest.php @@ -6,9 +6,9 @@ use Elastic\Apm\ElasticApm; use ElasticApmTests\TestsSharedCode\StacktraceTestSharedCode; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class StacktraceUnitTest extends UnitTestCaseBase +class StacktraceUnitTest extends TracerUnitTestCaseBase { public function testAllSpanCreatingApis(): void { diff --git a/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingMockClockTest.php b/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingMockClockTest.php index 4df3116a3..1275c5617 100644 --- a/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingMockClockTest.php +++ b/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingMockClockTest.php @@ -6,9 +6,9 @@ use Elastic\Apm\Impl\TracerBuilder; use ElasticApmTests\UnitTests\Util\MockClock; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class TimeRelatedApiUsingMockClockTest extends UnitTestCaseBase +class TimeRelatedApiUsingMockClockTest extends TracerUnitTestCaseBase { /** @var MockClock */ protected $mockClock; diff --git a/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingRealClockTest.php b/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingRealClockTest.php index 66a06c89a..cd3ff6691 100644 --- a/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingRealClockTest.php +++ b/tests/ElasticApmTests/UnitTests/TimeRelatedApiUsingRealClockTest.php @@ -4,9 +4,9 @@ namespace ElasticApmTests\UnitTests; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class TimeRelatedApiUsingRealClockTest extends UnitTestCaseBase +class TimeRelatedApiUsingRealClockTest extends TracerUnitTestCaseBase { public function testTransactionBeginEnd(): void { diff --git a/tests/ElasticApmTests/UnitTests/TransactionMaxSpansUnitTest.php b/tests/ElasticApmTests/UnitTests/TransactionMaxSpansUnitTest.php index d9fabdb38..bf2614d67 100644 --- a/tests/ElasticApmTests/UnitTests/TransactionMaxSpansUnitTest.php +++ b/tests/ElasticApmTests/UnitTests/TransactionMaxSpansUnitTest.php @@ -11,9 +11,9 @@ use ElasticApmTests\TestsSharedCode\TransactionMaxSpansTest\Args; use ElasticApmTests\TestsSharedCode\TransactionMaxSpansTest\SharedCode; use ElasticApmTests\UnitTests\Util\MockConfigRawSnapshotSource; -use ElasticApmTests\UnitTests\Util\UnitTestCaseBase; +use ElasticApmTests\UnitTests\Util\TracerUnitTestCaseBase; -class TransactionMaxSpansUnitTest extends UnitTestCaseBase +class TransactionMaxSpansUnitTest extends TracerUnitTestCaseBase { private const IS_FULL_TESTING_MODE = false; diff --git a/tests/ElasticApmTests/UnitTests/Util/NotFoundException.php b/tests/ElasticApmTests/UnitTests/Util/NotFoundException.php index 7b714c94b..d837f3032 100644 --- a/tests/ElasticApmTests/UnitTests/Util/NotFoundException.php +++ b/tests/ElasticApmTests/UnitTests/Util/NotFoundException.php @@ -1,7 +1,5 @@ $data @@ -496,6 +502,16 @@ public static function buildTracerForTests(?EventSinkInterface $eventSink = null ->withEventSink($eventSink ?? NoopEventSink::singletonInstance()); } + public static function noopLoggerFactory(): LoggerFactory + { + if (!isset(self::$noopLoggerFactory)) { + self::$noopLoggerFactory = new LoggerFactory( + new LogBackend(LogLevel::OFF, NoopLogSink::singletonInstance()) + ); + } + return self::$noopLoggerFactory; + } + public static function getParentId(ExecutionSegmentData $execSegData): ?string { if ($execSegData instanceof SpanData) { diff --git a/tests/ElasticApmTests/Util/ValidationUtil.php b/tests/ElasticApmTests/Util/ValidationUtil.php index 6db2a7a0c..76b5f495a 100644 --- a/tests/ElasticApmTests/Util/ValidationUtil.php +++ b/tests/ElasticApmTests/Util/ValidationUtil.php @@ -23,6 +23,7 @@ use Elastic\Apm\Impl\TransactionContextData; use Elastic\Apm\Impl\TransactionData; use Elastic\Apm\Impl\Util\ExceptionUtil; +use Elastic\Apm\Impl\Util\IdValidationUtil; use Elastic\Apm\Impl\Util\StaticClassTrait; use Elastic\Apm\Impl\Util\TextUtil; use PHPUnit\Framework\TestCase; @@ -71,13 +72,7 @@ public static function assertThat(bool $condition): void public static function assertValidId($id, int $expectedSizeInBytes): string { self::assertThat(is_string($id)); - - self::assertThat($expectedSizeInBytes * 2 === strlen($id)); - - foreach (str_split($id) as $idChar) { - self::assertThat(ctype_xdigit($idChar)); - } - + self::assertThat(IdValidationUtil::isValidHexNumberString($id, $expectedSizeInBytes)); return $id; }