diff --git a/_register.php b/_register.php index f16bdbe..afbf15d 100644 --- a/_register.php +++ b/_register.php @@ -3,6 +3,10 @@ declare(strict_types=1); use OpenTelemetry\SDK\Sdk; +use Spryker\Service\Opentelemetry\Instrumentation\ElasticaInstrumentation; +use Spryker\Service\Opentelemetry\Instrumentation\PropelInstrumentation; +use Spryker\Service\Opentelemetry\Instrumentation\RabbitMqInstrumentation; +use Spryker\Service\Opentelemetry\Instrumentation\RedisInstrumentation; use Spryker\Service\Opentelemetry\Instrumentation\SprykerInstrumentationBootstrap; if (!class_exists(Sdk::class)) { @@ -17,4 +21,8 @@ return; } +ElasticaInstrumentation::register(); +PropelInstrumentation::register(); +RabbitMqInstrumentation::register(); +RedisInstrumentation::register(); SprykerInstrumentationBootstrap::register(); diff --git a/composer.json b/composer.json index a42e209..7bf2f12 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,6 @@ "description": "Opentelemetry module", "license": "proprietary", "require": { - "ext-grpc": "*", - "ext-opentelemetry": "*", "open-telemetry/api": "^1.0", "open-telemetry/exporter-otlp": "^1.0", "open-telemetry/gen-otlp-protobuf": "^1.1", @@ -14,7 +12,7 @@ "open-telemetry/sem-conv": "^1.0", "php": ">=8.2", "spryker/kernel": "^3.30.0", - "spryker/monitoring-extension": "^1.0.0", + "spryker/monitoring-extension": "^1.1.0", "spryker/symfony": "^3.0.0" }, "require-dev": { diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php new file mode 100644 index 0000000..a4fb592 --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/ElasticaInstrumentation.php @@ -0,0 +1,130 @@ +tracer() + ->spanBuilder(static::SPAN_NAME) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::ATTRIBUTE_SEARCH_INDEX, $params[0]) + ->setAttribute(static::ATTRIBUTE_SEARCH_QUERY, serialize($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(); + }, + ); + } +} diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/PropelInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/PropelInstrumentation.php new file mode 100644 index 0000000..9637432 --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/PropelInstrumentation.php @@ -0,0 +1,107 @@ +getStatement()->queryString; + $criticalAttr = str_contains($query, 'SELECT') ? CriticalSpanRatioSampler::NO_CRITICAL_ATTRIBUTE : CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE; + $span = CachedInstrumentation::getCachedInstrumentation() + ->tracer() + ->spanBuilder(sprintf(static::SPAN_NAME_PATTERN, substr($query, 0, 20))) + ->setParent($context) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setAttribute(static::ATTRIBUTE_QUERY, $query) + ->setAttribute($criticalAttr, true) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (StatementInterface $statement, 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->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + } + ); + } +} diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php new file mode 100644 index 0000000..fe2fbbb --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/RabbitMqInstrumentation.php @@ -0,0 +1,168 @@ + static::SPAN_NAME_SEND_MESSAGE, + static::FUNCTION_SEND_MESSAGES => static::SPAN_NAME_SEND_MESSAGES, + ]; + + foreach ($functions as $function => $spanName) { + static::registerHook($function, $spanName); + } + } + + /** + * @param string $functionName + * @param string $spanName + * + * @return void + */ + protected static function registerHook(string $functionName, string $spanName): void + { + //BC check + if (class_exists('\Spryker\Service\OtelRabbitMqInstrumentation\OpenTelemetry\RabbitMqInstrumentation')) { + return; + } + + if (Sdk::isInstrumentationDisabled(static::NAME) === true) { + return; + } + + $instrumentation = CachedInstrumentation::getCachedInstrumentation(); + $request = (new RequestProcessor())->getRequest(); + + hook( + class: RabbitMqAdapter::class, + function: $functionName, + pre: function (RabbitMqAdapter $rabbitMqAdapter, array $params) use ($instrumentation, $spanName, $request): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder($spanName) + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->getMethod()) + ->setAttribute(static::ATTRIBUTE_QUEUE_NAME, $params[0]); + + if (static::isValidMessage($params)) { + $eventQueueSendMessageBodyArray = json_decode($params[1][0]->getBody(), true); + if (array_key_exists(EventQueueSendMessageBodyTransfer::EVENT_NAME, $eventQueueSendMessageBodyArray)) { + $span->setAttribute(TraceAttributes::EVENT_NAME, $eventQueueSendMessageBodyArray[EventQueueSendMessageBodyTransfer::EVENT_NAME]); + } + if (array_key_exists(EventQueueSendMessageBodyTransfer::LISTENER_CLASS_NAME, $eventQueueSendMessageBodyArray)) { + $span->setAttribute(static::ATTRIBUTE_EVENT_LISTENER_CLASS_NAME, $eventQueueSendMessageBodyArray[EventQueueSendMessageBodyTransfer::LISTENER_CLASS_NAME]); + } + } + + $span = $span->setAttribute(TraceAttributes::URL_DOMAIN, $request->headers->get(static::HEADER_HOST)) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: function (RabbitMqAdapter $rabbitMqAdapter, array $params, $response, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + } + ); + } + + /** + * @param array $params + * + * @return bool + */ + protected static function isValidMessage(array $params): bool + { + $queueSendMessageTransfer = $params[1][0] ?? null; + + if ($queueSendMessageTransfer === null) { + return false; + } + + return $queueSendMessageTransfer instanceof QueueSendMessageTransfer + && is_string($queueSendMessageTransfer->getBody()); + } +} diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php b/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php new file mode 100644 index 0000000..ff1453d --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/RedisInstrumentation.php @@ -0,0 +1,270 @@ +tracer() + ->spanBuilder('Redis::get') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::DB_QUERY_TEXT, isset($params[0]) ? 'GET ' . $params[0] : 'undefined') + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + RedisAdapterInterface::class, + 'mget', + pre: static function (RedisAdapterInterface $redis, array $params) use ($instrumentation): void { + + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder('Redis::mget') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::DB_QUERY_TEXT, implode(' ', $params[0])) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + RedisAdapterInterface::class, + 'eval', + pre: static function (RedisAdapterInterface $redis, array $params) use ($instrumentation): void { + + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder('Redis::eval') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(static::PARAM_EVAL_SCRIPT, $params[0] ?? 'undefined') + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + RedisAdapterInterface::class, + 'mset', + pre: static function (RedisAdapterInterface $redis, array $params) use ($instrumentation): void { + + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder('Redis::mset') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::DB_QUERY_TEXT, implode(' ', array_keys($params[0]))) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + RedisAdapterInterface::class, + 'set', + pre: static function (RedisAdapterInterface $redis, array $params) use ($instrumentation): void { + + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder('Redis::set') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined') + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + + hook( + Redis::class, + 'setex', + pre: static function (RedisAdapterInterface $redis, array $params) use ($instrumentation): void { + + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $context = Context::getCurrent(); + $span = $instrumentation->tracer() + ->spanBuilder('Redis::set') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setParent($context) + ->setAttribute(CriticalSpanRatioSampler::IS_CRITICAL_ATTRIBUTE, true) + ->setAttribute(TraceAttributes::DB_QUERY_TEXT, $params[0] ?? 'undefined') + ->setAttribute(static::PARAM_EXPIRATION, $params[0] ?? 'undefined') + ->setAttribute(TraceAttributes::DB_NAMESPACE, $redis->getDBNum()) + ->startSpan(); + + Context::storage()->attach($span->storeInContext($context)); + }, + post: static function (RedisAdapterInterface $redis, array $params, mixed $ret, ?Throwable $exception): void { + if (TraceSampleResult::shouldSkipTraceBody()) { + return; + } + $span = Span::fromContext(Context::getCurrent()); + + if ($exception !== null) { + $span->recordException($exception); + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + }, + ); + } +} diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/Sampler/TraceSampleResult.php b/src/Spryker/Service/Opentelemetry/Instrumentation/Sampler/TraceSampleResult.php index dbe6862..d17d1cf 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/Sampler/TraceSampleResult.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/Sampler/TraceSampleResult.php @@ -35,6 +35,7 @@ class TraceSampleResult public static function shouldSample(Request $request): int { $route = $request->attributes->get('_route'); + $isCli = (bool)$request->server->get('argv'); if ($request->getMethod() !== Request::METHOD_GET) { static::$result = static::SAMPLING_RESULT_ALLOW_ALL; @@ -48,7 +49,7 @@ public static function shouldSample(Request $request): int return static::$result; } - if (static::decideForRootSpan()) { + if (static::decideForRootSpan($isCli)) { static::$result = static::SAMPLING_RESULT_ALLOW_ROOT_SPAN; return static::$result; @@ -76,10 +77,13 @@ public static function shouldSkipRootSpan(): bool } /** + * @param bool $isCli + * * @return bool */ - protected static function decideForRootSpan(): bool + protected static function decideForRootSpan(bool $isCli): bool { - return (mt_rand() / mt_getrandmax()) >= OpentelemetryInstrumentationConfig::getTraceSamplerProbability(); + $probability = $isCli ? OpentelemetryInstrumentationConfig::getTraceCLISamplerProbability() : OpentelemetryInstrumentationConfig::getTraceSamplerProbability(); + return (mt_rand() / mt_getrandmax()) >= $probability; } } diff --git a/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php b/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php index 684b650..c6c8656 100644 --- a/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php +++ b/src/Spryker/Service/Opentelemetry/Instrumentation/SprykerInstrumentationBootstrap.php @@ -35,6 +35,7 @@ use Spryker\Service\Opentelemetry\Instrumentation\Tracer\TracerProvider; use Spryker\Service\Opentelemetry\OpentelemetryInstrumentationConfig; use Spryker\Service\Opentelemetry\Plugin\OpentelemetryMonitoringExtensionPlugin; +use Spryker\Service\Opentelemetry\Storage\CustomEventsStorage; use Spryker\Service\Opentelemetry\Storage\CustomParameterStorage; use Spryker\Service\Opentelemetry\Storage\ExceptionStorage; use Spryker\Service\Opentelemetry\Storage\ResourceNameStorage; @@ -42,7 +43,10 @@ use Spryker\Shared\Opentelemetry\Instrumentation\CachedInstrumentation; use Spryker\Shared\Opentelemetry\Request\RequestProcessor; use Spryker\Zed\Opentelemetry\Business\Generator\HookGenerator; +use Symfony\Component\Console\Application; use Symfony\Component\HttpFoundation\Request; +use Throwable; +use function OpenTelemetry\Instrumentation\hook; /** * @method \Spryker\Service\Opentelemetry\OpentelemetryServiceFactory getFactory() @@ -94,6 +98,11 @@ class SprykerInstrumentationBootstrap */ protected static ?SpanInterface $rootSpan = null; + /** + * @var bool|null + */ + protected static ?bool $cliSuccess = null; + /** * @return void */ @@ -117,7 +126,7 @@ public static function register(): void ->setPropagator(TraceContextPropagator::getInstance()) ->buildAndRegisterGlobal(); - static::registerRootSpan($serviceName, $request); + static::registerRootSpan($request); ShutdownHandler::register($tracerProvider->shutdown(...)); ShutdownHandler::register([static::class, 'shutdownHandler']); @@ -130,6 +139,7 @@ public static function register(): void */ protected static function registerAdditionalHooks(): void { + static::registerConsoleReturnCodeHook(); if (TraceSampleResult::shouldSkipTraceBody()) { putenv('OTEL_PHP_DISABLED_INSTRUMENTATIONS=all'); @@ -154,6 +164,21 @@ protected static function registerAdditionalHooks(): void spl_autoload_register($autoload, true, true); } + /** + * @return void + */ + protected static function registerConsoleReturnCodeHook(): void + { + hook( + Application::class, + 'doRun', + function () {}, + function ($instance, array $params, $returnValue, ?Throwable $exception) { + static::$cliSuccess = $returnValue === 0 || $returnValue === null; + } + ); + } + /** * @param string $serviceName * @@ -251,11 +276,11 @@ protected static function resolveServiceName(): string } /** - * @param string $servicedName + * @param Request $request * * @return void */ - protected static function registerRootSpan(string $servicedName, Request $request): void + protected static function registerRootSpan(Request $request): void { $cli = $request->server->get('argv'); if ($cli) { @@ -267,7 +292,7 @@ protected static function registerRootSpan(string $servicedName, Request $reques $instrumentation = CachedInstrumentation::getCachedInstrumentation(); $parent = Context::getCurrent(); $span = $instrumentation->tracer() - ->spanBuilder($servicedName . ' ' . $name) + ->spanBuilder($name) ->setParent($parent) ->setSpanKind(SpanKind::KIND_SERVER) ->setAttribute(TraceAttributes::URL_QUERY, $request->getQueryString()) @@ -322,7 +347,12 @@ public static function shutdownHandler(): void $span->recordException($exception); } - $span->setStatus($exceptions ? StatusCode::STATUS_ERROR : StatusCode::STATUS_OK); + $events = CustomEventsStorage::getInstance()->getEvents(); + foreach ($events as $eventName => $eventAttributes) { + $span->addEvent($eventName, $eventAttributes); + } + + $span->setStatus($exceptions || static::$cliSuccess === false ? StatusCode::STATUS_ERROR : StatusCode::STATUS_OK); $span->setAttributes($customParamsStorage->getAttributes()); $span->end(); diff --git a/src/Spryker/Service/Opentelemetry/OpentelemetryInstrumentationConfig.php b/src/Spryker/Service/Opentelemetry/OpentelemetryInstrumentationConfig.php index b83c447..6540537 100644 --- a/src/Spryker/Service/Opentelemetry/OpentelemetryInstrumentationConfig.php +++ b/src/Spryker/Service/Opentelemetry/OpentelemetryInstrumentationConfig.php @@ -74,6 +74,11 @@ class OpentelemetryInstrumentationConfig */ protected const OTEL_TRACE_PROBABILITY = 'OTEL_TRACE_PROBABILITY'; + /** + * @string + */ + protected const OTEL_CLI_TRACE_PROBABILITY = 'OTEL_CLI_TRACE_PROBABILITY'; + /** * Specification: * - The threshold in nanoseconds for the span to be sampled. @@ -84,7 +89,7 @@ class OpentelemetryInstrumentationConfig */ public static function getSamplerThresholdNano(): int { - $multiplicator = getenv(static::OTEL_BSP_MIN_SPAN_DURATION_THRESHOLD) ?: 20; + $multiplicator = getenv(static::OTEL_BSP_MIN_SPAN_DURATION_THRESHOLD) ?: 5; return $multiplicator * 1000000; } @@ -99,7 +104,7 @@ public static function getSamplerThresholdNano(): int */ public static function getSamplerThresholdNanoForCriticalSpan(): int { - $multiplicator = getenv(static::OTEL_BSP_MIN_CRITICAL_SPAN_DURATION_THRESHOLD) ?: 10; + $multiplicator = getenv(static::OTEL_BSP_MIN_CRITICAL_SPAN_DURATION_THRESHOLD) ?: 0; return $multiplicator * 1000000; } @@ -190,7 +195,7 @@ public static function getSamplerProbability(): float */ public static function getSamplerProbabilityForCriticalSpans(): float { - $probability = getenv(static::OTEL_TRACES_CRITICAL_SAMPLER_ARG) ?: 0.5; + $probability = getenv(static::OTEL_TRACES_CRITICAL_SAMPLER_ARG) ?: 1; return (float)$probability; } @@ -251,7 +256,17 @@ public static function getSpanProcessorMaxBatchSize(): int */ public static function getTraceSamplerProbability(): float { - $probability = getenv(static::OTEL_TRACE_PROBABILITY) ?: 0.1; + $probability = getenv(static::OTEL_TRACE_PROBABILITY) ?: 0.3; + + return (float)$probability; + } + + /** + * @return float + */ + public static function getTraceCLISamplerProbability(): float + { + $probability = getenv(static::OTEL_CLI_TRACE_PROBABILITY) ?: 0.5; return (float)$probability; } diff --git a/src/Spryker/Service/Opentelemetry/OpentelemetryService.php b/src/Spryker/Service/Opentelemetry/OpentelemetryService.php index df82177..9ec3173 100644 --- a/src/Spryker/Service/Opentelemetry/OpentelemetryService.php +++ b/src/Spryker/Service/Opentelemetry/OpentelemetryService.php @@ -73,4 +73,19 @@ public function setError(string $message, Throwable $exception): void { $this->getFactory()->createExceptionStorage()->addException($exception); } + + /** + * {@inheritDoc} + * + * @api + * + * @param string $name + * @param array $attributes + * + * @return void + */ + public function addEvent(string $name, array $attributes): void + { + $this->getFactory()->createCustomEventsStorage()->addEvent($name, $attributes); + } } diff --git a/src/Spryker/Service/Opentelemetry/OpentelemetryServiceFactory.php b/src/Spryker/Service/Opentelemetry/OpentelemetryServiceFactory.php index dc4ee3c..e39427b 100644 --- a/src/Spryker/Service/Opentelemetry/OpentelemetryServiceFactory.php +++ b/src/Spryker/Service/Opentelemetry/OpentelemetryServiceFactory.php @@ -8,6 +8,8 @@ namespace Spryker\Service\Opentelemetry; use Spryker\Service\Kernel\AbstractServiceFactory; +use Spryker\Service\Opentelemetry\Storage\CustomEventsStorage; +use Spryker\Service\Opentelemetry\Storage\CustomEventsStorageInterface; use Spryker\Service\Opentelemetry\Storage\CustomParameterStorage; use Spryker\Service\Opentelemetry\Storage\CustomParameterStorageInterface; use Spryker\Service\Opentelemetry\Storage\ExceptionStorage; @@ -49,4 +51,12 @@ public function createExceptionStorage(): ExceptionStorageInterface { return ExceptionStorage::getInstance(); } + + /** + * @return \Spryker\Service\Opentelemetry\Storage\CustomEventsStorageInterface + */ + public function createCustomEventsStorage(): CustomEventsStorageInterface + { + return CustomEventsStorage::getInstance(); + } } diff --git a/src/Spryker/Service/Opentelemetry/OpentelemetryServiceInterface.php b/src/Spryker/Service/Opentelemetry/OpentelemetryServiceInterface.php index 499b4b2..1ae934d 100644 --- a/src/Spryker/Service/Opentelemetry/OpentelemetryServiceInterface.php +++ b/src/Spryker/Service/Opentelemetry/OpentelemetryServiceInterface.php @@ -61,4 +61,17 @@ public function setResourceName(string $name): void; * @return void */ public function setError(string $message, Throwable $exception): void; + + /** + * Specification: + * - Adds a custom event to the root span. + * + * @api + * + * @param string $name + * @param array $attributes + * + * @return void + */ + public function addEvent(string $name, array $attributes): void; } diff --git a/src/Spryker/Service/Opentelemetry/Plugin/OpentelemetryMonitoringExtensionPlugin.php b/src/Spryker/Service/Opentelemetry/Plugin/OpentelemetryMonitoringExtensionPlugin.php index 6350084..7da5f96 100644 --- a/src/Spryker/Service/Opentelemetry/Plugin/OpentelemetryMonitoringExtensionPlugin.php +++ b/src/Spryker/Service/Opentelemetry/Plugin/OpentelemetryMonitoringExtensionPlugin.php @@ -8,12 +8,13 @@ namespace Spryker\Service\Opentelemetry\Plugin; use Spryker\Service\Kernel\AbstractPlugin; +use Spryker\Service\MonitoringExtension\Dependency\Plugin\CustomEventsMonitoringExtensionPluginInterface; use Spryker\Service\MonitoringExtension\Dependency\Plugin\MonitoringExtensionPluginInterface; /** * @method \Spryker\Service\Opentelemetry\OpentelemetryServiceInterface getService() */ -class OpentelemetryMonitoringExtensionPlugin extends AbstractPlugin implements MonitoringExtensionPluginInterface +class OpentelemetryMonitoringExtensionPlugin extends AbstractPlugin implements MonitoringExtensionPluginInterface, CustomEventsMonitoringExtensionPluginInterface { /** * @var string @@ -152,4 +153,18 @@ public function addCustomTracer(string $tracer): void { return; } + + /** + * Specification: + * - Adds a custom event that will be added to the root span. + * + * @param string $name + * @param array $attributes + * + * @return void + */ + public function addEvent(string $name, array $attributes): void + { + $this->getService()->addEvent($name, $attributes); + } } diff --git a/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorage.php b/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorage.php new file mode 100644 index 0000000..cfc2e68 --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorage.php @@ -0,0 +1,53 @@ + + */ + protected array $events = []; + + /** + * @return \Spryker\Service\Opentelemetry\Storage\CustomEventsStorageInterface + */ + public static function getInstance(): CustomEventsStorageInterface + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param string $name + * @param array $attributes + * + * @return void + */ + public function addEvent(string $name, array $attributes): void + { + $this->events[$name] = $attributes; + } + + /** + * @return array + */ + public function getEvents(): array + { + return $this->events; + } +} diff --git a/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorageInterface.php b/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorageInterface.php new file mode 100644 index 0000000..40e7f5c --- /dev/null +++ b/src/Spryker/Service/Opentelemetry/Storage/CustomEventsStorageInterface.php @@ -0,0 +1,29 @@ +