Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements and Refactoring #13

Merged
merged 6 commits into from
Dec 27, 2023
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
13 changes: 9 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"spiral/boot": "^3.0",
"spiral/snapshots": "^3.0",
"sentry/sentry": "^4.0",
"psr/http-factory": "^1.0.1",
"psr/http-message": "^1.0.1 || ^2.0",
"php-http/curl-client": "^2.3.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5.5",
"mockery/mockery": "^1.5",
"vimeo/psalm": "^5.17",
"psr/log": "^3.0",
"spiral/testing": "^2.2"
"spiral/testing": "^2.6"
},
"autoload": {
"psr-4": {
Expand All @@ -34,5 +34,10 @@
}
},
"minimum-stability": "dev",
"prefer-stable": true
"prefer-stable": true,
"config": {
"allow-plugins": {
"php-http/discovery": true
}
}
}
144 changes: 131 additions & 13 deletions src/Bootloader/ClientBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,148 @@

use Sentry\ClientBuilder;
use Sentry\ClientInterface;
use Sentry\Integration\RequestFetcherInterface;
use Sentry\Options;
use Sentry\SentrySdk;
use Sentry\State\Hub;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Boot\DirectoriesInterface;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Boot\FinalizerInterface;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Sentry\Config\SentryConfig;
use Spiral\Sentry\Http\RequestScope;
use Spiral\Sentry\Version;
use Sentry\Integration as SdkIntegration;

class ClientBootloader extends Bootloader
{
protected const SINGLETONS = [
ClientInterface::class => [self::class, 'createClient'],
];
/** @var SdkIntegration\IntegrationInterface[] */
private array $integrations = [];

public function __construct(
private readonly ConfiguratorInterface $config
private readonly ConfiguratorInterface $config,
) {
}

public function init(EnvironmentInterface $env): void
public function defineSingletons(): array
{
return [
Options::class => [self::class, 'createOptions'],
HubInterface::class => [self::class, 'createHub'],
ClientInterface::class => [self::class, 'getClient'],
RequestFetcherInterface::class => RequestScope::class,
];
}

/**
* Register a new integration to be added to the Sentry SDK.
*/
public function addIntegration(SdkIntegration\IntegrationInterface $integration): void

Check warning on line 48 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L48

Added line #L48 was not covered by tests
{
$this->integrations[] = $integration;

Check warning on line 50 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L50

Added line #L50 was not covered by tests
}

public function init(EnvironmentInterface $env, FinalizerInterface $finalizer): void
{
$this->config->setDefaults('sentry', [
'dsn' => \trim($env->get('SENTRY_DSN', ''), "\n\t\r \"'") // typical typos
'dsn' => \trim($env->get('SENTRY_DSN', ''), "\n\t\r \"'"), // typical typos
'environment' => $env->get('SENTRY_ENVIRONMENT') ?? null,
'release' => $env->get('SENTRY_RELEASE') ?? null,
'sample_rate' => $env->get('SENTRY_SAMPLE_RATE') === null
? 1.0
: (float)$env->get('SENTRY_SAMPLE_RATE'),
'traces_sample_rate' => $env->get('SENTRY_TRACES_SAMPLE_RATE') === null
? null
: (float)$env->get('SENTRY_TRACES_SAMPLE_RATE'),
'send_default_pii' => (bool)$env->get('SENTRY_SEND_DEFAULT_PII'),
]);
}

private function createClient(SentryConfig $config): ClientInterface
/**
* Create the Sentry SDK options.
*/
private function createOptions(
SentryConfig $config,
DirectoriesInterface $dirs,
EnvironmentInterface $env,
RequestFetcherInterface $requestScope,
): Options {
$options = new Options([
'dsn' => $config->getDSN(),
'environment' => $config->getEnvironment(),
'release' => $config->getRelease(),
]);

$options->setSampleRate($config->getSampleRate());
$options->setTracesSampleRate($config->getTracesSampleRate());
$options->setSendDefaultPii($config->isSendDefaultPii());

$options->setPrefixes([
$dirs->get('root'),
]);

$options->setInAppExcludedPaths([
$dirs->get('root') . '/vendor',
]);

if ($config->getEnvironment() === null) {
$options->setEnvironment($env->get('APP_ENV'));
}

if ($config->getRelease() === null) {
$options->setRelease($env->get('APP_VERSION'));
}

$options->setIntegrations(function (array $integrations) use ($options, $requestScope): array {
if ($options->hasDefaultIntegrations()) {
// Remove the default error and fatal exception listeners to let Spiral handle those
// itself. These event are still bubbling up through the documented changes in the users
// `ExceptionHandler` of their application or through the log channel integration to Sentry
$integrations = \array_filter(
$integrations,
static function (SdkIntegration\IntegrationInterface $integration): bool {
if ($integration instanceof SdkIntegration\ErrorListenerIntegration) {
return false;

Check warning on line 113 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L113

Added line #L113 was not covered by tests
}

if ($integration instanceof SdkIntegration\ExceptionListenerIntegration) {
return false;

Check warning on line 117 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L117

Added line #L117 was not covered by tests
}

if ($integration instanceof SdkIntegration\FatalErrorListenerIntegration) {
return false;

Check warning on line 121 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L121

Added line #L121 was not covered by tests
}

// We also remove the default request integration so it can be readded
// after with a Laravel specific request fetcher. This way we can resolve
// the request from Laravel instead of constructing it from the global state
if ($integration instanceof SdkIntegration\RequestIntegration) {
return false;
}

return true;
},
);

$integrations[] = new SdkIntegration\RequestIntegration($requestScope);
}

return [...$integrations, ...$this->integrations];
});

return $options;
}

private function createHub(Options $options, FinalizerInterface $finalizer): HubInterface
{
/**
* @psalm-suppress InternalClass
* @psalm-suppress InternalMethod
*/
$builder = ClientBuilder::create([
'dsn' => $config->getDSN(),
]);
$builder = new ClientBuilder($options);

/** @psalm-suppress InternalMethod */
$builder->setSdkIdentifier(Version::SDK_IDENTIFIER);
Expand All @@ -49,10 +156,21 @@
$builder->setSdkVersion(Version::SDK_VERSION);

/** @psalm-suppress InternalMethod */
$client = $builder->getClient();
$hub = new Hub($builder->getClient());

SentrySdk::setCurrentHub($hub);

SentrySdk::setCurrentHub(new Hub($client));
$finalizer->addFinalizer(static function () use ($hub): void {
$hub->configureScope(function (Scope $scope): void {
$scope->clear();
});

Check warning on line 166 in src/Bootloader/ClientBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/ClientBootloader.php#L164-L166

Added lines #L164 - L166 were not covered by tests
});

return $client;
return $hub;
}

private function getClient(HubInterface $hub): ?ClientInterface
{
return $hub->getClient();
}
}
18 changes: 12 additions & 6 deletions src/Bootloader/SentryBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@

final class SentryBootloader extends Bootloader
{
protected const DEPENDENCIES = [
ClientBootloader::class,
];
public function defineDependencies(): array
{
return [
ClientBootloader::class,
];
}

protected const BINDINGS = [
SnapshotterInterface::class => SentrySnapshotter::class,
];
public function defineBindings(): array
{
return [
SnapshotterInterface::class => SentrySnapshotter::class,
];
}
}
9 changes: 6 additions & 3 deletions src/Bootloader/SentryReporterBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@

final class SentryReporterBootloader extends Bootloader
{
protected const DEPENDENCIES = [
ClientBootloader::class,
];
public function defineDependencies(): array

Check warning on line 14 in src/Bootloader/SentryReporterBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/SentryReporterBootloader.php#L14

Added line #L14 was not covered by tests
{
return [
ClientBootloader::class,
];

Check warning on line 18 in src/Bootloader/SentryReporterBootloader.php

View check run for this annotation

Codecov / codecov/patch

src/Bootloader/SentryReporterBootloader.php#L16-L18

Added lines #L16 - L18 were not covered by tests
}

public function init(AbstractKernel $kernel): void
{
Expand Down
22 changes: 11 additions & 11 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,36 @@
use Psr\Container\ContainerInterface;
use Psr\Log\LogLevel;
use Sentry\Breadcrumb;
use Sentry\ClientInterface;
use Sentry\EventId;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Spiral\Debug\StateInterface;
use Spiral\Logger\Event\LogEvent;

final class Client
{
public function __construct(
private readonly ClientInterface $client,
private readonly HubInterface $hub,
private readonly ContainerInterface $container,
) {
}

public function send(\Throwable $exception): ?EventId
{
$scope = new Scope();

if ($this->container->has(StateInterface::class)) {
$state = $this->container->get(StateInterface::class);

$scope->setTags($state->getTags());
$scope->setExtras($state->getVariables());
$this->hub->configureScope(function (Scope $scope) use ($state): void {
$scope->setTags($state->getTags());
$scope->setExtras($state->getVariables());

Check warning on line 31 in src/Client.php

View check run for this annotation

Codecov / codecov/patch

src/Client.php#L30-L31

Added lines #L30 - L31 were not covered by tests

foreach ($state->getLogEvents() as $event) {
$scope->addBreadcrumb($this->makeBreadcrumb($event));
}
foreach ($state->getLogEvents() as $event) {
$scope->addBreadcrumb($this->makeBreadcrumb($event));

Check warning on line 34 in src/Client.php

View check run for this annotation

Codecov / codecov/patch

src/Client.php#L33-L34

Added lines #L33 - L34 were not covered by tests
}
});
}

return $this->client->captureException($exception, $scope);
return $this->hub->captureException($exception);
}

private function makeBreadcrumb(LogEvent $event): Breadcrumb
Expand Down Expand Up @@ -68,7 +68,7 @@
'default',
$event->getChannel(),
$event->getMessage(),
$event->getContext()
$event->getContext(),

Check warning on line 71 in src/Client.php

View check run for this annotation

Codecov / codecov/patch

src/Client.php#L71

Added line #L71 was not covered by tests
);
}
}
63 changes: 62 additions & 1 deletion src/Config/SentryConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,71 @@ final class SentryConfig extends InjectableConfig

protected array $config = [
'dsn' => '',
'environment' => null,
'release' => null,
'sample_rate' => 1.0,
'traces_sample_rate' => null,
'send_default_pii' => false,
];

/**
* @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
*/
public function getDSN(): string
{
return $this->config['dsn'];
}
}

/**
* This string is freeform and set to production by default. A release can be associated with
* more than one environment to separate them in the UI (think staging vs production or similar).
*/
public function getEnvironment(): ?string
{
return $this->config['environment'] ?? $_SERVER['SENTRY_ENVIRONMENT'] ?? null;
}

/**
* The release version of your application.
* Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
*/
public function getRelease(): ?string
{
return $this->config['release'] ?? $_SERVER['SENTRY_RELEASE'] ?? null;
}

/**
* Configures the sample rate for error events, in the range of 0.0 to 1.0. The default is 1.0 which means that
* 100% of error events are sent. If set to 0.1 only 10% of error events will be sent. Events are picked randomly.
*
* @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#sample-rate
*/
public function getSampleRate(): float
{
return $this->config['sample_rate'];
}

/**
* A number between 0 and 1, controlling the percentage chance a given transaction will be sent to Sentry.
* (0 represents 0% while 1 represents 100%.) Applies equally to all transactions created in the app. Either this
* or traces_sampler must be defined to enable tracingThe process of logging the events that took place during a
* request, often across multiple services..
*
* @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate
*/
public function getTracesSampleRate(): ?float
{
return $this->config['traces_sample_rate'];
}

/**
* If this flag is enabled, certain personally identifiable information (PII) is added by active integrations.
* By default, no such data is sent.
*
* @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii
*/
public function isSendDefaultPii(): bool
{
return $this->config['send_default_pii'] ?? false;
}
}
Loading
Loading