Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aefb310
test(otel): fix OTLP discovery setup
Litarnus Jun 22, 2026
0fdad47
test(otel): pin sem-conv dev dependency
Litarnus Jun 22, 2026
b3d22a3
ci: remove sem-conv on unsupported PHP
Litarnus Jun 22, 2026
2aa577c
Merge branch '5.x' into replace-hubs-with-scopes
Litarnus Jun 24, 2026
54e8b3e
feat(scopes): add functions to merge two scopes (#2113)
Litarnus Jul 16, 2026
5e5df55
feat(scopes): add clients to scopes (#2114)
Litarnus Jul 16, 2026
b4fe051
feat(scopes): extract transaction sampling logic into TransactionSamp…
Litarnus Jul 16, 2026
f477e11
feat(scopes): use TransactionSampler (#2120)
Litarnus Jul 16, 2026
9d367f9
feat(scope): remove Hub from Transaction (#2122)
Litarnus Jul 16, 2026
03f2850
feat(scope): update aggregator flush to use a client (#2125)
Litarnus Jul 16, 2026
6cfa298
feat(scopes): swap many usages of hub to scope (#2129)
Litarnus Jul 16, 2026
cbea51b
feat(scope): dont use hub for capturing events anymore (#2132)
Litarnus Jul 16, 2026
142948b
feat(scopes): reduce usage of hub further (#2133)
Litarnus Jul 16, 2026
ee8a6f9
feat(scopes): remove hub usage in handler and middleware (#2134)
Litarnus Jul 16, 2026
c0f98a8
feat(scopes): update tests to not use Hub (#2135)
Litarnus Jul 16, 2026
e978f09
feat(scopes): remove Hub and related classes (#2138)
Litarnus Jul 16, 2026
606034b
feat(scopes): introduce typed scopes (#2139)
Litarnus Jul 16, 2026
79b8b52
ref(scopes): feedback from stacked PRs (#2158)
Litarnus Jul 16, 2026
c719f64
fix max breadcrumbs
Litarnus Jul 17, 2026
8c8de73
fix dynamic sampling context
Litarnus Jul 17, 2026
744dc13
store last event id on context
Litarnus Jul 17, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:

- 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
run: composer remove open-telemetry/api open-telemetry/exporter-otlp open-telemetry/sem-conv 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
33 changes: 33 additions & 0 deletions UPGRADE-5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@ $logger->pushHandler(new \Sentry\Monolog\LogsHandler());

To continue sending Monolog records to Sentry issues instead, use `Sentry\Monolog\LogToSentryIssueHandler` for log messages or `Sentry\Monolog\ExceptionToSentryIssueHandler` for exceptions.

### Hub APIs Removed

The Hub API has been removed. This includes:

- **Removed classes and interfaces:**
- `Sentry\State\Hub`
- `Sentry\State\HubAdapter`
- `Sentry\State\HubInterface`

- **Removed methods:**
- `SentrySdk::getCurrentHub()`
- `SentrySdk::setCurrentHub()`

The SDK now exposes runtime state directly through `SentrySdk`, global helpers, and `Scope`:

```php
// Before (4.x)
\Sentry\SentrySdk::getCurrentHub()->configureScope(static function (\Sentry\State\Scope $scope): void {
$scope->setTag('feature', 'checkout');
});

// After (5.0)
\Sentry\configureScope(static function (\Sentry\State\Scope $scope): void {
$scope->setTag('feature', 'checkout');
});
```

Use `SentrySdk::getClient()` for the active client, `SentrySdk::getGlobalScope()` for process-global data, and `SentrySdk::getIsolationScope()` for the current runtime isolation scope. Use the capture helpers such as `captureMessage()`, `captureException()`, and `captureEvent()` to send events.

Use `configureScope()` to mutate the current isolation scope, `withIsolationScope()` to run code with a temporary forked scope, and `startTransaction()` to start manual tracing instrumentation.

`SentrySdk::init()` no longer returns a Hub instance. It now returns `void`.

### Metrics API Removed

The entire Metrics API has been removed as it is no longer supported. This includes:
Expand Down
12 changes: 0 additions & 12 deletions analysis-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1368,18 +1368,6 @@ code = "possibly-invalid-argument"
message = '''Possible argument type mismatch for argument #1 of `Sentry\StacktraceBuilder::buildFromBacktrace`: expected `list<array{'args'?: array<array-key, mixed>, 'class'?: class-string, 'file'?: string, 'function'?: string, 'line'?: int, 'type'?: string}>`, but possibly received `array<array-key, mixed>`.'''
count = 1

[[issues]]
file = "src/State/Hub.php"
code = "mixed-operand"
message = "Right operand in `<` comparison has `mixed` type."
count = 1

[[issues]]
file = "src/State/Hub.php"
code = "possibly-null-operand"
message = "Left operand in `<` comparison might be `null` (type `float|null`)."
count = 1

[[issues]]
file = "src/State/Scope.php"
code = "impossible-condition"
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"nyholm/psr7": "^1.8",
"open-telemetry/api": "^1.0",
"open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/sem-conv": "^1.27",
"open-telemetry/sdk": "^1.0",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^8.5.52|^9.6.34",
Expand Down
5 changes: 5 additions & 0 deletions mago.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ excludes = [

[analyzer]
baseline = "analysis-baseline.toml"
ignore = [
{ code = "no-value", pattern = "DebugType::getDebugType" },
"impossible-condition",
"redundant-logical-operation",
]
21 changes: 11 additions & 10 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Sentry\Integration\IntegrationRegistry;
use Sentry\Serializer\RepresentationSerializer;
use Sentry\Serializer\RepresentationSerializerInterface;
use Sentry\State\Scope;
use Sentry\State\IsolationScope;
use Sentry\Transport\Result;
use Sentry\Transport\TransportInterface;

Expand Down Expand Up @@ -140,7 +140,7 @@ public function getCspReportUrl(): ?string
/**
* {@inheritdoc}
*/
public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId
public function captureMessage(string $message, ?Severity $level = null, ?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId
{
$event = Event::createEvent();
$event->setMessage($message);
Expand All @@ -152,7 +152,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope
/**
* {@inheritdoc}
*/
public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId
public function captureException(\Throwable $exception, ?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId
{
$className = \get_class($exception);
if ($this->shouldIgnoreException($className)) {
Expand All @@ -176,7 +176,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E
/**
* {@inheritdoc}
*/
public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId
public function captureEvent(Event $event, ?EventHint $hint = null, ?IsolationScope $scope = null): ?EventId
{
// Client reports don't need to be augmented in the prepareEvent pipeline.
if ($event->getType() !== EventType::clientReport()) {
Expand Down Expand Up @@ -208,7 +208,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop
/**
* {@inheritdoc}
*/
public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId
public function captureLastError(?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId
{
$error = error_get_last();

Expand Down Expand Up @@ -277,13 +277,13 @@ public function getSdkVersion(): string
/**
* Assembles an event and prepares it to be sent of to Sentry.
*
* @param Event $event The payload that will be converted to an Event
* @param EventHint|null $hint May contain additional information about the event
* @param Scope|null $scope Optional scope which enriches the Event
* @param Event $event The payload that will be converted to an Event
* @param EventHint|null $hint May contain additional information about the event
* @param IsolationScope|null $scope Optional scope which enriches the Event
*
* @return Event|null The prepared event object or null if it must be discarded
*/
private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event
private function prepareEvent(Event $event, ?EventHint $hint = null, ?IsolationScope $scope = null): ?Event
{
if ($hint !== null) {
if ($hint->exception !== null && empty($event->getExceptions())) {
Expand Down Expand Up @@ -339,7 +339,8 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco

if ($scope !== null) {
$beforeEventProcessors = $event;
$event = $scope->applyToEvent($event, $hint, $this->options);
$globalScope = SentrySdk::getGlobalScope();
$event = $globalScope->merge($scope)->applyToEvent($event, $hint, $this->options);

if ($event === null) {
$this->logger->info(
Expand Down
34 changes: 17 additions & 17 deletions src/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Sentry;

use Sentry\Integration\IntegrationInterface;
use Sentry\State\Scope;
use Sentry\State\IsolationScope;
use Sentry\Transport\Result;

interface ClientInterface
Expand All @@ -23,38 +23,38 @@ public function getCspReportUrl(): ?string;
/**
* Logs a message.
*
* @param string $message The message (primary description) for the event
* @param Severity|null $level The level of the message to be sent
* @param Scope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
* @param string $message The message (primary description) for the event
* @param Severity|null $level The level of the message to be sent
* @param IsolationScope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
*/
public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId;
public function captureMessage(string $message, ?Severity $level = null, ?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId;

/**
* Logs an exception.
*
* @param \Throwable $exception The exception object
* @param Scope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
* @param \Throwable $exception The exception object
* @param IsolationScope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
*/
public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId;
public function captureException(\Throwable $exception, ?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId;

/**
* Logs the most recent error (obtained with {@link error_get_last}).
*
* @param Scope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
* @param IsolationScope|null $scope An optional scope keeping the state
* @param EventHint|null $hint Object that can contain additional information about the event
*/
public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId;
public function captureLastError(?IsolationScope $scope = null, ?EventHint $hint = null): ?EventId;

/**
* Captures a new event using the provided data.
*
* @param Event $event The event being captured
* @param EventHint|null $hint May contain additional information about the event
* @param Scope|null $scope An optional scope keeping the state
* @param Event $event The event being captured
* @param EventHint|null $hint May contain additional information about the event
* @param IsolationScope|null $scope An optional scope keeping the state
*/
public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId;
public function captureEvent(Event $event, ?EventHint $hint = null, ?IsolationScope $scope = null): ?EventId;

/**
* Returns the integration instance if it is installed on the client.
Expand Down
22 changes: 10 additions & 12 deletions src/ClientReport/ClientReportAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Sentry\ClientReport;

use Sentry\Event;
use Sentry\State\HubAdapter;
use Sentry\SentrySdk;
use Sentry\Transport\DataCategory;

class ClientReportAggregator
Expand Down Expand Up @@ -35,15 +35,13 @@ public function add(DataCategory $category, Reason $reason, int $quantity): void
$category = $category->getValue();
$reason = $reason->getValue();
if ($quantity <= 0) {
$client = HubAdapter::getInstance()->getClient();
if ($client !== null) {
$logger = $client->getOptions()->getLoggerOrNullLogger();
$logger->debug('Dropping Client report with category={category} and reason={reason} because quantity is zero or negative ({quantity})', [
'category' => $category,
'reason' => $reason,
'quantity' => $quantity,
]);
}
$client = SentrySdk::getClient();
$logger = $client->getOptions()->getLoggerOrNullLogger();
$logger->debug('Dropping Client report with category={category} and reason={reason} because quantity is zero or negative ({quantity})', [
'category' => $category,
'reason' => $reason,
'quantity' => $quantity,
]);

return;
}
Expand All @@ -64,11 +62,11 @@ public function flush(): void
$event = Event::createClientReport();
$event->setClientReports($reports);

$client = HubAdapter::getInstance()->getClient();
$client = SentrySdk::getClient();

// Reset the client reports only if we successfully sent an event. If it fails it
// can be sent on the next flush, or it gets discarded anyway.
if ($client !== null && $client->captureEvent($event) !== null) {
if ($client->captureEvent($event) !== null) {
$this->reports = [];
}
}
Expand Down
22 changes: 12 additions & 10 deletions src/Integration/AbstractErrorListenerIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@
namespace Sentry\Integration;

use Sentry\Event;
use Sentry\EventHint;
use Sentry\ExceptionMechanism;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Sentry\State\EventRecorder;
use Sentry\State\IsolationScope;

use function Sentry\withIsolationScope;

abstract class AbstractErrorListenerIntegration implements IntegrationInterface
{
/**
* Captures the exception using the given hub instance.
*
* @param HubInterface $hub The hub instance
* @param \Throwable $exception The exception instance
* @param \Throwable $exception The exception instance
*/
protected function captureException(HubInterface $hub, \Throwable $exception): void
protected function captureException(\Throwable $exception): void
{
$hub->withScope(function (Scope $scope) use ($hub, $exception): void {
$scope->addEventProcessor(\Closure::fromCallable([$this, 'addExceptionMechanismToEvent']));
withIsolationScope(function (IsolationScope $scope) use ($exception): void {
$scope->addEventProcessor(function (Event $event, EventHint $hint): Event {
return $this->addExceptionMechanismToEvent($event);
});

$hub->captureException($exception);
EventRecorder::captureException($exception, null, $scope);
Comment thread
cursor[bot] marked this conversation as resolved.
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/Integration/EnvironmentIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class EnvironmentIntegration implements IntegrationInterface
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$integration = SentrySdk::getCurrentHub()->getIntegration(self::class);
$integration = SentrySdk::getClient()->getIntegration(self::class);

if ($integration !== null) {
$event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext()));
Expand Down
10 changes: 4 additions & 6 deletions src/Integration/ErrorListenerIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,22 @@ public function setupOnce(): void
ErrorHandler::registerOnceErrorHandler($this->options)
->addErrorHandlerListener(
static function (\ErrorException $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = SentrySdk::getClient();
$integration = $client->getIntegration(self::class);

if ($integration === null) {
return;
}

$client = $currentHub->getClient();

if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) {
return;
}

if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) {
if (!$exception instanceof SilencedErrorException && ($client->getOptions()->getErrorTypes() & $exception->getSeverity()) === 0) {
return;
}

$integration->captureException($currentHub, $exception);
$integration->captureException($exception);
}
);
}
Expand Down
8 changes: 3 additions & 5 deletions src/Integration/ExceptionListenerIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ public function setupOnce(): void
{
$errorHandler = ErrorHandler::registerOnceExceptionHandler();
$errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = SentrySdk::getClient();
$integration = $client->getIntegration(self::class);

// The client bound to the current hub, if any, could not have this
// integration enabled. If this is the case, bail out
if ($integration === null) {
return;
}

$integration->captureException($currentHub, $exception);
$integration->captureException($exception);
});
}
}
10 changes: 4 additions & 6 deletions src/Integration/FatalErrorListenerIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,18 @@ public function setupOnce(): void
{
$errorHandler = ErrorHandler::registerOnceFatalErrorHandler();
$errorHandler->addFatalErrorHandlerListener(static function (FatalErrorException $exception): void {
$currentHub = SentrySdk::getCurrentHub();
$integration = $currentHub->getIntegration(self::class);
$client = SentrySdk::getClient();
$integration = $client->getIntegration(self::class);

if ($integration === null) {
return;
}

$client = $currentHub->getClient();

if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) {
if (($client->getOptions()->getErrorTypes() & $exception->getSeverity()) === 0) {
return;
}

$integration->captureException($currentHub, $exception);
$integration->captureException($exception);
});
}
}
2 changes: 1 addition & 1 deletion src/Integration/FrameContextifierIntegration.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function __construct(?LoggerInterface $logger = null)
public function setupOnce(): void
{
Scope::addGlobalEventProcessor(static function (Event $event): Event {
$client = SentrySdk::getCurrentHub()->getClient();
$client = SentrySdk::getClient();

$maxContextLines = $client->getOptions()->getContextLines();
$integration = $client->getIntegration(self::class);
Expand Down
Loading