Skip to content

Commit fc0a6d6

Browse files
authored
Add debug scope to warn on incorrect scope usage (#823)
* Add debug scope to warn on incorrect scope usage * Enable assertions in unit tests
1 parent 737712d commit fc0a6d6

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

Context.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace OpenTelemetry\Context;
66

7+
use function assert;
8+
79
/**
810
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.6.1/specification/context/context.md#overview
911
*/
@@ -92,7 +94,11 @@ public function withContextValue(ImplicitContextKeyedInterface $value): self
9294
*/
9395
public function activate(): ScopeInterface
9496
{
95-
return self::storage()->attach($this);
97+
$scope = self::storage()->attach($this);
98+
/** @psalm-suppress RedundantCondition */
99+
assert((bool) $scope = new DebugScope($scope));
100+
101+
return $scope;
96102
}
97103

98104
/**

DebugScope.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Context;
6+
7+
use function basename;
8+
use function count;
9+
use function debug_backtrace;
10+
use const DEBUG_BACKTRACE_IGNORE_ARGS;
11+
use function sprintf;
12+
use function trigger_error;
13+
14+
/**
15+
* @internal
16+
*/
17+
final class DebugScope implements ScopeInterface
18+
{
19+
private const DEBUG_TRACE_CREATE = '__debug_trace_create';
20+
private const DEBUG_TRACE_DETACH = '__debug_trace_detach';
21+
22+
private ContextStorageScopeInterface $scope;
23+
24+
public function __construct(ContextStorageScopeInterface $node)
25+
{
26+
$this->scope = $node;
27+
$this->scope[self::DEBUG_TRACE_CREATE] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
28+
}
29+
30+
public function detach(): int
31+
{
32+
$this->scope[self::DEBUG_TRACE_DETACH] ??= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
33+
34+
$flags = $this->scope->detach();
35+
36+
if (($flags & ScopeInterface::DETACHED) !== 0) {
37+
trigger_error(sprintf(
38+
'Scope: unexpected call to Scope::detach() for scope #%d, scope was already detached %s',
39+
spl_object_id($this),
40+
self::formatBacktrace($this->scope[self::DEBUG_TRACE_DETACH]),
41+
));
42+
} elseif (($flags & ScopeInterface::MISMATCH) !== 0) {
43+
trigger_error(sprintf(
44+
'Scope: unexpected call to Scope::detach() for scope #%d, scope successfully detached but another scope should have been detached first',
45+
spl_object_id($this),
46+
));
47+
} elseif (($flags & ScopeInterface::INACTIVE) !== 0) {
48+
trigger_error(sprintf(
49+
'Scope: unexpected call to Scope::detach() for scope #%d, scope successfully detached from different execution context',
50+
spl_object_id($this),
51+
));
52+
}
53+
54+
return $flags;
55+
}
56+
57+
public function __destruct()
58+
{
59+
if (!isset($this->scope[self::DEBUG_TRACE_DETACH])) {
60+
trigger_error(sprintf(
61+
'Scope: missing call to Scope::detach() for scope #%d, created %s',
62+
spl_object_id($this->scope),
63+
self::formatBacktrace($this->scope[self::DEBUG_TRACE_CREATE]),
64+
));
65+
}
66+
}
67+
68+
private static function formatBacktrace(array $trace): string
69+
{
70+
$s = '';
71+
for ($i = 0, $n = count($trace) + 1; ++$i < $n;) {
72+
$s .= "\n\t";
73+
$s .= 'at ';
74+
if (isset($trace[$i]['class'])) {
75+
$s .= strtr($trace[$i]['class'], ['\\' => '.']);
76+
$s .= '.';
77+
}
78+
$s .= strtr($trace[$i]['function'] ?? '{main}', ['\\' => '.']);
79+
$s .= '(';
80+
if (isset($trace[$i - 1]['file'])) {
81+
$s .= basename($trace[$i - 1]['file']);
82+
if (isset($trace[$i - 1]['line'])) {
83+
$s .= ':';
84+
$s .= $trace[$i - 1]['line'];
85+
}
86+
} else {
87+
$s .= 'Unknown Source';
88+
}
89+
$s .= ')';
90+
}
91+
92+
return $s . "\n";
93+
}
94+
}

0 commit comments

Comments
 (0)