diff --git a/_register.php b/_register.php index afbf15d..92fb6d1 100644 --- a/_register.php +++ b/_register.php @@ -4,6 +4,7 @@ use OpenTelemetry\SDK\Sdk; use Spryker\Service\Opentelemetry\Instrumentation\ElasticaInstrumentation; +use Spryker\Service\Opentelemetry\Instrumentation\GuzzleInstrumentation; use Spryker\Service\Opentelemetry\Instrumentation\PropelInstrumentation; use Spryker\Service\Opentelemetry\Instrumentation\RabbitMqInstrumentation; use Spryker\Service\Opentelemetry\Instrumentation\RedisInstrumentation; @@ -25,4 +26,5 @@ PropelInstrumentation::register(); RabbitMqInstrumentation::register(); RedisInstrumentation::register(); +GuzzleInstrumentation::register(); SprykerInstrumentationBootstrap::register(); diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php index a4fb592..ced5b1d 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php @@ -31,12 +31,62 @@ class ElasticaInstrumentation /** * @var string */ - protected const METHOD_NAME = 'request'; + protected const METHOD_NAME_REQUEST = 'request'; /** * @var string */ - protected const SPAN_NAME = 'elasticsearch-request'; + protected const METHOD_NAME_UPDATE_DOCUMENTS = 'updateDocuments'; + + /** + * @var string + */ + protected const METHOD_NAME_UPDATE_DOCUMENT = 'updateDocument'; + + /** + * @var string + */ + protected const METHOD_NAME_ADD_DOCUMENTS = 'addDocuments'; + + /** + * @var string + */ + protected const METHOD_NAME_DELETE_DOCUMENTS = 'deleteDocuments'; + + /** + * @var string + */ + protected const METHOD_NAME_DELETE_IDS = 'deleteIds'; + + /** + * @var string + */ + protected const SPAN_NAME_REQUEST = 'elasticsearch-request'; + + /** + * @var string + */ + protected const SPAN_NAME_UPDATE_DOCUMENTS = 'elasticsearch-update-documents'; + + /** + * @var string + */ + protected const SPAN_NAME_UPDATE_DOCUMENT = 'elasticsearch-update-document'; + + /** + * @var string + */ + protected const SPAN_NAME_ADD_DOCUMENTS = 'elasticsearch-add-document'; + + /** + * @var string + */ + protected const SPAN_NAME_DELETE_DOCUMENTS = 'elasticsearch-delete-documents'; + + /** + * @var string + */ + protected const SPAN_NAME_DELETE_IDS = 'elasticsearch-delete-ids'; /** * @var string @@ -53,6 +103,21 @@ class ElasticaInstrumentation */ protected const ATTRIBUTE_SEARCH_INDEX = 'search.index'; + /** + * @var string + */ + protected const ATTRIBUTE_SEARCH_INDEXES = 'search.indexes'; + + /** + * @var string + */ + protected const ATTRIBUTE_SEARCH_ID = 'search.id'; + + /** + * @var string + */ + protected const ATTRIBUTE_SEARCH_IDS = 'search.ids'; + /** * @var string */ @@ -79,7 +144,7 @@ public static function register(): void hook( class: Client::class, - function: static::METHOD_NAME, + function: static::METHOD_NAME_REQUEST, pre: function (Client $client, array $params): void { if (TraceSampleResult::shouldSkipTraceBody()) { return; @@ -90,7 +155,7 @@ function: static::METHOD_NAME, $context = Context::getCurrent(); $span = $instrumentation->tracer() - ->spanBuilder(static::SPAN_NAME) + ->spanBuilder(static::SPAN_NAME_REQUEST) ->setSpanKind(SpanKind::KIND_CLIENT) ->setParent($context) ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) @@ -126,5 +191,278 @@ function: static::METHOD_NAME, $span->end(); }, ); + + hook( + class: Client::class, + function: static::METHOD_NAME_UPDATE_DOCUMENTS, + pre: function (Client $client, array $params): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = new RequestProcessor(); + $context = Context::getCurrent(); + + $indexes = static::getIndexesIndexedByIdsFromDocuments($params[0]); + + $span = $instrumentation->tracer() + ->spanBuilder(static::SPAN_NAME_UPDATE_DOCUMENTS) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEXES, implode(',', $indexes)) + ->setAttribute(static::ATTRIBUTE_SEARCH_IDS, implode(',', array_keys($indexes))) + ->setAttribute(static::ATTRIBUTE_ROOT_URL, $request->getRequest()->getUri()) + ->setAttribute(TraceAttributes::URL_DOMAIN, $request->getRequest()->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (Client $client, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setAttribute(static::ATTRIBUTE_QUERY_TIME, $response->getQueryTime()); + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + class: Client::class, + function: static::METHOD_NAME_ADD_DOCUMENTS, + pre: function (Client $client, array $params): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = new RequestProcessor(); + $context = Context::getCurrent(); + + $indexes = static::getIndexesIndexedByIdsFromDocuments($params[0]); + + $span = $instrumentation->tracer() + ->spanBuilder(static::SPAN_NAME_ADD_DOCUMENTS) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEXES, implode(',', $indexes)) + ->setAttribute(static::ATTRIBUTE_SEARCH_IDS, implode(',', array_keys($indexes))) + ->setAttribute(static::ATTRIBUTE_ROOT_URL, $request->getRequest()->getUri()) + ->setAttribute(TraceAttributes::URL_DOMAIN, $request->getRequest()->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (Client $client, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setAttribute(static::ATTRIBUTE_QUERY_TIME, $response->getQueryTime()); + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + class: Client::class, + function: static::METHOD_NAME_UPDATE_DOCUMENT, + pre: function (Client $client, array $params): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = new RequestProcessor(); + $context = Context::getCurrent(); + + $span = $instrumentation->tracer() + ->spanBuilder(static::SPAN_NAME_UPDATE_DOCUMENT) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_ID, $params[0]) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEX, $params[2]) + ->setAttribute(static::ATTRIBUTE_ROOT_URL, $request->getRequest()->getUri()) + ->setAttribute(TraceAttributes::URL_DOMAIN, $request->getRequest()->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (Client $client, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setAttribute(static::ATTRIBUTE_QUERY_TIME, $response->getQueryTime()); + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + class: Client::class, + function: static::METHOD_NAME_DELETE_DOCUMENTS, + pre: function (Client $client, array $params): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = new RequestProcessor(); + $context = Context::getCurrent(); + + $indexes = static::getIndexesIndexedByIdsFromDocuments($params[0]); + + $span = $instrumentation->tracer() + ->spanBuilder(static::SPAN_NAME_DELETE_DOCUMENTS) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEXES, implode(',', $indexes)) + ->setAttribute(static::ATTRIBUTE_SEARCH_IDS, implode(',', array_keys($indexes))) + ->setAttribute(static::ATTRIBUTE_ROOT_URL, $request->getRequest()->getUri()) + ->setAttribute(TraceAttributes::URL_DOMAIN, $request->getRequest()->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (Client $client, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setAttribute(static::ATTRIBUTE_QUERY_TIME, $response->getQueryTime()); + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + class: Client::class, + function: static::METHOD_NAME_DELETE_IDS, + pre: function (Client $client, array $params): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = new RequestProcessor(); + $context = Context::getCurrent(); + + $indexes = static::getIndexesIndexedByIdsFromDocuments($params[0]); + + $span = $instrumentation->tracer() + ->spanBuilder(static::SPAN_NAME_DELETE_IDS) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEXES, implode(',', $indexes)) + ->setAttribute(static::ATTRIBUTE_SEARCH_IDS, implode(',', array_keys($indexes))) + ->setAttribute(static::ATTRIBUTE_ROOT_URL, $request->getRequest()->getUri()) + ->setAttribute(TraceAttributes::URL_DOMAIN, $request->getRequest()->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (Client $client, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setAttribute(static::ATTRIBUTE_QUERY_TIME, $response->getQueryTime()); + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + } + + /** + * @param array<\Elastica\Document> $documents + * + * @return array + */ + protected static function getIndexesIndexedByIdsFromDocuments(array $documents): array + { + $indexes = []; + foreach ($documents as $document) { + $indexes[$document->getId()] = $document->getIndex(); + } + + return $indexes; } } diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/GuzzleInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/GuzzleInstrumentation.php new file mode 100644 index 0000000..c1e5be0 --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/GuzzleInstrumentation.php @@ -0,0 +1,133 @@ +getUri(); + $method = $request->getMethod(); + $url = (string)$uriObject; + + $span = $instrumentation->tracer() + ->spanBuilder(sprintf('Guzzle %s %s', $method, $url)) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) + ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) + ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) + ->setAttribute(TraceAttributes::CODE_LINENO, $lineno) + ->setAttribute(TraceAttributes::SERVER_ADDRESS, $uriObject->getHost()) + ->setAttribute(TraceAttributes::SERVER_PORT, $uriObject->getPort()) + ->setAttribute(TraceAttributes::URL_PATH, $uriObject->getPath()) + ->setAttribute(TraceAttributes::URL_FULL, $url) + ->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $method) + ->setAttribute(TraceAttributes::NETWORK_PROTOCOL_VERSION, $request->getProtocolVersion()) + ->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->getHeaderLine(static::HEADER_USER_AGENT)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (Client $guzzleClient, array $params, PromiseInterface $promise, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); + + if ($exception) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + $span->end(); + + return; + } + + $promise->then( + onFulfilled: function (ResponseInterface $response) use ($span) { + $span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->getStatusCode()); + + if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + $span->end(); + + return $response; + }, + onRejected: function (Throwable $exception) use ($span) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + $span->end(); + + throw $exception; + } + ); + }, + ); + } +} diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php index fe2fbbb..2c5da86 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php @@ -65,6 +65,26 @@ class RabbitMqInstrumentation */ protected const FUNCTION_SEND_MESSAGE = 'sendMessage'; + /** + * @var string + */ + protected const FUNCTION_RECEIVE_MESSAGE = 'receiveMessage'; + + /** + * @var string + */ + protected const FUNCTION_RECEIVE_MESSAGES = 'receiveMessages'; + + /** + * @var string + */ + protected const SPAN_NAME_RECEIVE_MESSAGE = 'rabbitmq-receiveMessage'; + + /** + * @var string + */ + protected const SPAN_NAME_RECEIVE_MESSAGES = 'rabbitmq-receiveMessages'; + /** * @return void */ @@ -73,6 +93,8 @@ public static function register(): void $functions = [ static::FUNCTION_SEND_MESSAGE => static::SPAN_NAME_SEND_MESSAGE, static::FUNCTION_SEND_MESSAGES => static::SPAN_NAME_SEND_MESSAGES, + static::FUNCTION_RECEIVE_MESSAGE => static::SPAN_NAME_RECEIVE_MESSAGE, + static::FUNCTION_RECEIVE_MESSAGES => static::SPAN_NAME_RECEIVE_MESSAGES, ]; foreach ($functions as $function => $spanName) { @@ -135,7 +157,15 @@ function: $functionName, if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php index ff1453d..8f24068 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php @@ -72,7 +72,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); @@ -108,7 +116,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); @@ -144,7 +160,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); @@ -180,7 +204,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); @@ -216,7 +248,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); @@ -254,7 +294,15 @@ public static function register(): void if (TraceSampleResult::shouldSkipTraceBody()) { return; } - $span = Span::fromContext(Context::getCurrent()); + $scope = Context::storage()->scope(); + + if ($scope === null) { + return; + } + + $scope->detach(); + + $span = Span::fromContext($scope->context()); if ($exception !== null) { $span->recordException($exception); diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php b/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php index c6c8656..5d9a6da 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php @@ -355,6 +355,7 @@ public static function shutdownHandler(): void $span->setStatus($exceptions || static::$cliSuccess === false ? StatusCode::STATUS_ERROR : StatusCode::STATUS_OK); $span->setAttributes($customParamsStorage->getAttributes()); + $span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, http_response_code()); $span->end(); } } diff --git a/src/Spryker/Shared/Opentelemetry/Instrumentation/CachedInstrumentation.php b/src/Spryker/Shared/Opentelemetry/Instrumentation/CachedInstrumentation.php index f3f0756..695f6a3 100644 --- a/src/Spryker/Shared/Opentelemetry/Instrumentation/CachedInstrumentation.php +++ b/src/Spryker/Shared/Opentelemetry/Instrumentation/CachedInstrumentation.php @@ -19,7 +19,7 @@ class CachedInstrumentation implements CachedInstrumentationInterface /** * @var string */ - protected const INSTRUMENTATION_VERSION = '0.1.0'; + protected const INSTRUMENTATION_VERSION = '1.4.0'; /** * @var \OpenTelemetry\API\Instrumentation\CachedInstrumentation|null