Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ jobs:
if: ${{ matrix.os == 'windows-latest' || matrix.php.version == '7.2' || matrix.php.version == '7.3' || matrix.php.version == '7.4' || matrix.php.version == '8.0' }}
run: composer remove spiral/roadrunner-http spiral/roadrunner-worker --dev --no-interaction --no-update

- name: Remove OpenTelemetry dependencies on unsupported PHP versions
if: ${{ matrix.php.version == '7.2' || matrix.php.version == '7.3' || matrix.php.version == '7.4' || matrix.php.version == '8.0' }}
run: composer remove open-telemetry/api open-telemetry/exporter-otlp open-telemetry/sdk --dev --no-interaction --no-update

- name: Set phpunit/phpunit version constraint
run: composer require phpunit/phpunit:'${{ matrix.php.phpunit }}' --dev --no-interaction --no-update

Expand Down
9 changes: 8 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
"monolog/monolog": "^1.6|^2.0|^3.0",
"nyholm/psr7": "^1.8",
"open-telemetry/api": "^1.0",
"open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/sdk": "^1.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^8.5.52|^9.6.34",
Expand Down Expand Up @@ -74,7 +77,11 @@
"phpstan": "vendor/bin/phpstan analyse"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
}
},
"prefer-stable": true
}
8 changes: 8 additions & 0 deletions src/Dsn.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ public function getCspReportEndpointUrl(): string
return $this->getBaseEndpointUrl() . '/security/?sentry_key=' . $this->publicKey;
}

/**
* Returns the URL of the API for the OTLP traces endpoint.
*/
public function getOtlpTracesEndpointUrl(): string
{
return $this->getBaseEndpointUrl() . '/integration/otlp/v1/traces/';
}

/**
* @see https://www.php.net/manual/en/language.oop5.magic.php#object.tostring
*/
Expand Down
206 changes: 206 additions & 0 deletions src/Integration/OTLPIntegration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<?php

declare(strict_types=1);

namespace Sentry\Integration;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Sentry\Client;
use Sentry\Options;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Sentry\Util\Http;

final class OTLPIntegration implements OptionAwareIntegrationInterface
{
/**
* @var bool
*/
private $setupOtlpTracesExporter;

/**
* @var string|null
*/
private $collectorUrl;

/**
* @var Options|null
*/
private $options;

public function __construct(bool $setupOtlpTracesExporter = true, ?string $collectorUrl = null)
{
$this->setupOtlpTracesExporter = $setupOtlpTracesExporter;
$this->collectorUrl = $collectorUrl;
}

public function setOptions(Options $options): void
{
$this->options = $options;
}

public function setupOnce(): void
{
$options = $this->options;

if ($options === null) {
$this->logDebug('Skipping OTLPIntegration setup because client options were not provided.');

return;
}

if ($options->isTracingEnabled()) {
$this->logDebug('Skipping OTLPIntegration because Sentry tracing is enabled. Disable "traces_sample_rate", "traces_sampler", and "enable_tracing" before using OTLPIntegration.');

return;
}

Scope::registerExternalPropagationContext(static function (): ?array {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);

if (!$integration instanceof self) {
return null;
}

return $integration->getCurrentOpenTelemetryPropagationContext();
});

if ($this->setupOtlpTracesExporter) {
$this->configureOtlpTracesExporter($options);
}
}

public function getCollectorUrl(): ?string
{
return $this->collectorUrl;
}

/**
* @return array{trace_id: string, span_id: string}|null
*/
private function getCurrentOpenTelemetryPropagationContext(): ?array
{
if (!class_exists(\OpenTelemetry\API\Trace\Span::class)) {
return null;
}

$spanContext = \OpenTelemetry\API\Trace\Span::getCurrent()->getContext();

if (!$spanContext->isValid()) {
return null;
}

return [
'trace_id' => $spanContext->getTraceId(),
'span_id' => $spanContext->getSpanId(),
];
}

private function configureOtlpTracesExporter(Options $options): void
{
$endpoint = $this->collectorUrl;
$headers = [];
$dsn = $options->getDsn();

if ($endpoint === null && $dsn !== null) {
$endpoint = $dsn->getOtlpTracesEndpointUrl();
$headers['X-Sentry-Auth'] = Http::getSentryAuthHeader($dsn, Client::SDK_IDENTIFIER, Client::SDK_VERSION);
}

if ($endpoint === null) {
$this->logDebug('Skipping automatic OTLP exporter setup because neither a DSN nor a collector URL is configured.');

return;
}

if (!$this->shouldConfigureOtlpTracesExporter()) {
return;
}

try {
$transport = (new \OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory())->create(
$endpoint,
\OpenTelemetry\Contrib\Otlp\ContentTypes::PROTOBUF,
$headers
);
$spanExporter = new \OpenTelemetry\Contrib\Otlp\SpanExporter($transport);
$batchSpanProcessor = new \OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor(
$spanExporter,
\OpenTelemetry\API\Common\Time\Clock::getDefault()
);

(new \OpenTelemetry\SDK\SdkBuilder())
->setTracerProvider(new \OpenTelemetry\SDK\Trace\TracerProvider($batchSpanProcessor))
->buildAndRegisterGlobal();
} catch (\Throwable $exception) {
$this->logDebug(\sprintf('Skipping automatic OTLP exporter setup because it could not be configured: %s', $exception->getMessage()));
}
}

private function shouldConfigureOtlpTracesExporter(): bool
{
if (\PHP_VERSION_ID < 80100) {
$this->logDebug('Skipping automatic OTLP exporter setup because it requires PHP 8.1 or newer.');

return false;
}

foreach ([
\OpenTelemetry\API\Globals::class,
\OpenTelemetry\API\Common\Time\Clock::class,
\OpenTelemetry\SDK\SdkBuilder::class,
\OpenTelemetry\SDK\Trace\TracerProvider::class,
\OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor::class,
\OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory::class,
\OpenTelemetry\Contrib\Otlp\SpanExporter::class,
] as $className) {
if (!class_exists($className)) {
$this->logDebug('Skipping automatic OTLP exporter setup because the required OpenTelemetry SDK/exporter classes are not available.');

return false;
}
}

try {
if (!$this->isNoopTracerProvider(\OpenTelemetry\API\Globals::tracerProvider())) {
$this->logDebug('Skipping automatic OTLP exporter setup because the existing OpenTelemetry tracer provider cannot be modified after construction.');

return false;
}
} catch (\Throwable $exception) {
$this->logDebug(\sprintf('Skipping automatic OTLP exporter setup because the current OpenTelemetry tracer provider could not be inspected: %s', $exception->getMessage()));

return false;
}

return true;
}

private function isNoopTracerProvider(?object $tracerProvider): bool
{
return $tracerProvider === null || $tracerProvider instanceof \OpenTelemetry\API\Trace\NoopTracerProvider;
}

private function logDebug(string $message): void
{
$this->getLogger()->debug($message);
}

private function getLogger(): LoggerInterface
{
if ($this->options !== null) {
return $this->options->getLoggerOrNullLogger();
}

$currentHub = SentrySdk::getCurrentHub();
$client = $currentHub->getClient();

if ($client !== null) {
return $client->getOptions()->getLoggerOrNullLogger();
}

return new NullLogger();
}
}
28 changes: 15 additions & 13 deletions src/Logs/LogsAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ public function add(
$formattedMessage = $message;
}

$log = (new Log($timestamp, $this->getTraceId($hub), $level, $formattedMessage))
$traceContext = $this->getTraceContext($hub);
$traceId = $traceContext['trace_id'];
$parentSpanId = $traceContext['span_id'];

$log = (new Log($timestamp, $traceId, $level, $formattedMessage))
->setAttribute('sentry.release', $options->getRelease())
->setAttribute('sentry.environment', $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT)
->setAttribute('sentry.server.address', $options->getServerName())
->setAttribute('sentry.trace.parent_span_id', $hub->getSpan() ? $hub->getSpan()->getSpanId() : null);
->setAttribute('sentry.trace.parent_span_id', $parentSpanId);
Comment thread
cursor[bot] marked this conversation as resolved.

if ($client instanceof Client) {
$log->setAttribute('sentry.sdk.name', $client->getSdkIdentifier());
Expand Down Expand Up @@ -176,20 +180,18 @@ public function all(): array
return $this->logs;
}

private function getTraceId(HubInterface $hub): string
/**
* @return array{trace_id: string, span_id: string}
*/
private function getTraceContext(HubInterface $hub): array
{
$span = $hub->getSpan();

if ($span !== null) {
return (string) $span->getTraceId();
}

$traceId = '';
$traceContext = null;

$hub->configureScope(static function (Scope $scope) use (&$traceId) {
$traceId = (string) $scope->getPropagationContext()->getTraceId();
$hub->configureScope(static function (Scope $scope) use (&$traceContext): void {
$traceContext = $scope->getTraceContext();
});

return $traceId;
/** @var array{trace_id: string, span_id: string} $traceContext */
return $traceContext;
Comment thread
Litarnus marked this conversation as resolved.
Outdated
}
}
35 changes: 20 additions & 15 deletions src/Metrics/MetricsAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Sentry\SentrySdk;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Sentry\Tracing\SpanId;
use Sentry\Tracing\TraceId;
use Sentry\Unit;
use Sentry\Util\RingBuffer;

Expand Down Expand Up @@ -104,24 +106,12 @@ public function add(
$attributes += $defaultAttributes;
}

$spanId = null;
$traceId = null;

$span = $hub->getSpan();
if ($span !== null) {
$spanId = $span->getSpanId();
$traceId = $span->getTraceId();
} else {
$hub->configureScope(static function (Scope $scope) use (&$traceId, &$spanId) {
$propagationContext = $scope->getPropagationContext();
$traceId = $propagationContext->getTraceId();
$spanId = $propagationContext->getSpanId();
});
}
$traceContext = $this->getTraceContext($hub);
$traceId = new TraceId($traceContext['trace_id']);
$spanId = new SpanId($traceContext['span_id']);

$metricTypeClass = self::METRIC_TYPES[$type];
/** @var Metric $metric */
/** @phpstan-ignore-next-line */
$metric = new $metricTypeClass($name, $value, $traceId, $spanId, $attributes, microtime(true), $unit);

if ($client !== null) {
Expand All @@ -146,4 +136,19 @@ public function flush(?HubInterface $hub = null): ?EventId

return $hub->captureEvent($event);
}

/**
* @return array{trace_id: string, span_id: string}
*/
private function getTraceContext(HubInterface $hub): array
{
$traceContext = null;

$hub->configureScope(static function (Scope $scope) use (&$traceContext): void {
$traceContext = $scope->getTraceContext();
});

/** @var array{trace_id: string, span_id: string} $traceContext */
return $traceContext;
}
}
Loading
Loading