Skip to content
Merged
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
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ parameters:
count: 1
path: src/Tracing/GuzzleTracingMiddleware.php

-
message: "#^Property Sentry\\\\Tracing\\\\PropagationContext\\:\\:\\$parentSampled is never read, only written\\.$#"
count: 1
path: src/Tracing/PropagationContext.php

Comment on lines +313 to +317
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't find an elegant way to fix this

-
message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
Expand Down
58 changes: 14 additions & 44 deletions src/Tracing/PropagationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

use Sentry\SentrySdk;
use Sentry\State\Scope;
use Sentry\Tracing\Traits\TraceHeaderParserTrait;

final class PropagationContext
{
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
use TraceHeaderParserTrait;

/**
* @var TraceId The trace id
Expand Down Expand Up @@ -183,60 +184,29 @@ public function setSampleRand(?float $sampleRand): self
return $this;
}

// TODO add same logic as in TransactionContext
private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self
{
$context = self::fromDefaults();
$hasSentryTrace = false;
$parsedData = self::parseTraceAndBaggageHeaders($traceparent, $baggage);

if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}

if (!empty($matches['span_id'])) {
$context->parentSpanId = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}

if (isset($matches['sampled'])) {
$context->parentSampled = $matches['sampled'] === '1';
$hasSentryTrace = true;
}
if ($parsedData['traceId'] !== null) {
$context->traceId = $parsedData['traceId'];
}

$samplingContext = DynamicSamplingContext::fromHeader($baggage);
if ($parsedData['parentSpanId'] !== null) {
$context->parentSpanId = $parsedData['parentSpanId'];
}

if ($hasSentryTrace && !$samplingContext->hasEntries()) {
// The request comes from an old SDK which does not support Dynamic Sampling.
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
$samplingContext->freeze();
$context->dynamicSamplingContext = $samplingContext;
if ($parsedData['parentSampled'] !== null) {
$context->parentSampled = $parsedData['parentSampled'];
}

if ($hasSentryTrace && $samplingContext->hasEntries()) {
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
// Propagate this Dynamic Sampling Context.
$context->dynamicSamplingContext = $samplingContext;
if ($parsedData['dynamicSamplingContext'] !== null) {
$context->dynamicSamplingContext = $parsedData['dynamicSamplingContext'];
}

// Store the propagated trace sample rand or generate a new one
if ($samplingContext->has('sample_rand')) {
$context->sampleRand = (float) $samplingContext->get('sample_rand');
} else {
if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) {
if ($context->parentSampled === true) {
// [0, rate)
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
} else {
// [rate, 1)
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6);
}
} elseif ($context->parentSampled !== null) {
// [0, 1)
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
}
if ($parsedData['sampleRand'] !== null) {
$context->sampleRand = $parsedData['sampleRand'];
}

return $context;
Expand Down
106 changes: 106 additions & 0 deletions src/Tracing/Traits/TraceHeaderParserTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace Sentry\Tracing\Traits;

use Sentry\Tracing\DynamicSamplingContext;
use Sentry\Tracing\SpanId;
use Sentry\Tracing\TraceId;

/**
* @internal
*/
trait TraceHeaderParserTrait
{
/**
* @var string The regex for parsing the sentry-trace header
*/
private static $sentryTraceparentHeaderRegex = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';

/**
* Parses the sentry-trace and baggage headers and returns the extracted data.
*
* @param string $sentryTrace The sentry-trace header value
* @param string $baggage The baggage header value
*
* @return array{
* traceId: TraceId|null,
* parentSpanId: SpanId|null,
* parentSampled: bool|null,
* dynamicSamplingContext: DynamicSamplingContext|null,
* sampleRand: float|null,
* parentSamplingRate: float|null
* }
*/
protected static function parseTraceAndBaggageHeaders(string $sentryTrace, string $baggage): array
{
$result = [
'traceId' => null,
'parentSpanId' => null,
'parentSampled' => null,
'dynamicSamplingContext' => null,
'sampleRand' => null,
'parentSamplingRate' => null,
];

$hasSentryTrace = false;

if (preg_match(self::$sentryTraceparentHeaderRegex, $sentryTrace, $matches)) {
if (!empty($matches['trace_id'])) {
$result['traceId'] = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}

if (!empty($matches['span_id'])) {
$result['parentSpanId'] = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}

if (isset($matches['sampled'])) {
$result['parentSampled'] = $matches['sampled'] === '1';
$hasSentryTrace = true;
}
}

$samplingContext = DynamicSamplingContext::fromHeader($baggage);

if ($hasSentryTrace && !$samplingContext->hasEntries()) {
// The request comes from an old SDK which does not support Dynamic Sampling.
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
$samplingContext->freeze();
$result['dynamicSamplingContext'] = $samplingContext;
}

if ($hasSentryTrace && $samplingContext->hasEntries()) {
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
// Propagate this Dynamic Sampling Context.
$result['dynamicSamplingContext'] = $samplingContext;
}

// Store the propagated traces sample rate
if ($samplingContext->has('sample_rate')) {
$result['parentSamplingRate'] = (float) $samplingContext->get('sample_rate');
}

// Store the propagated trace sample rand or generate a new one
if ($samplingContext->has('sample_rand')) {
$result['sampleRand'] = (float) $samplingContext->get('sample_rand');
} else {
if ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) {
if ($result['parentSampled'] === true) {
// [0, rate)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
} else {
// [rate, 1)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6);
}
} elseif ($result['parentSampled'] !== null) {
// [0, 1)
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
}
}

return $result;
}
}
65 changes: 18 additions & 47 deletions src/Tracing/TransactionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Sentry\Tracing;

use Sentry\Tracing\Traits\TraceHeaderParserTrait;

final class TransactionContext extends SpanContext
{
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
use TraceHeaderParserTrait;

public const DEFAULT_NAME = '<unlabeled transaction>';

Expand Down Expand Up @@ -147,61 +149,30 @@ public static function fromHeaders(string $sentryTraceHeader, string $baggageHea
private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self
{
$context = new self();
$hasSentryTrace = false;

if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}

if (!empty($matches['span_id'])) {
$context->parentSpanId = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}

if (isset($matches['sampled'])) {
$context->parentSampled = $matches['sampled'] === '1';
$hasSentryTrace = true;
}
$parsedData = self::parseTraceAndBaggageHeaders($sentryTrace, $baggage);

if ($parsedData['traceId'] !== null) {
$context->traceId = $parsedData['traceId'];
}

$samplingContext = DynamicSamplingContext::fromHeader($baggage);
if ($parsedData['parentSpanId'] !== null) {
$context->parentSpanId = $parsedData['parentSpanId'];
}

if ($hasSentryTrace && !$samplingContext->hasEntries()) {
// The request comes from an old SDK which does not support Dynamic Sampling.
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
$samplingContext->freeze();
$context->getMetadata()->setDynamicSamplingContext($samplingContext);
if ($parsedData['parentSampled'] !== null) {
$context->parentSampled = $parsedData['parentSampled'];
}

if ($hasSentryTrace && $samplingContext->hasEntries()) {
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
// Propagate this Dynamic Sampling Context.
$context->getMetadata()->setDynamicSamplingContext($samplingContext);
if ($parsedData['dynamicSamplingContext'] !== null) {
$context->getMetadata()->setDynamicSamplingContext($parsedData['dynamicSamplingContext']);
}

// Store the propagated traces sample rate
if ($samplingContext->has('sample_rate')) {
$context->getMetadata()->setParentSamplingRate((float) $samplingContext->get('sample_rate'));
if ($parsedData['parentSamplingRate'] !== null) {
$context->getMetadata()->setParentSamplingRate($parsedData['parentSamplingRate']);
}

// Store the propagated trace sample rand or generate a new one
if ($samplingContext->has('sample_rand')) {
$context->getMetadata()->setSampleRand((float) $samplingContext->get('sample_rand'));
} else {
if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) {
if ($context->parentSampled === true) {
// [0, rate)
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6));
} else {
// [rate, 1)
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6));
}
} elseif ($context->parentSampled !== null) {
// [0, 1)
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6));
}
if ($parsedData['sampleRand'] !== null) {
$context->getMetadata()->setSampleRand($parsedData['sampleRand']);
}

return $context;
Expand Down
Loading