Skip to content
Open
Show file tree
Hide file tree
Changes from all 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);

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;
}
}
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