Skip to content

Commit

Permalink
Suggest Symfony HttplugClient in symfony guide (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
B-Galati authored Nov 5, 2019
1 parent e0c1e6a commit a1a39eb
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 98 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ phpstan: vendor ## Check PHP code style

.PHONY: phpunit
phpunit: vendor ## Run PhpUnit tests
vendor/bin/phpunit -v --testdox
vendor/bin/phpunit -v

.PHONY: php-cs-fixer-check
cs-check: vendor ## Check php code style
Expand Down
48 changes: 3 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ It is a [Monolog](https://github.com/Seldaek/monolog) handler for Sentry PHP SDK

- Send each log record to a [Sentry](https://sentry.io) server
- Send log records as breadcrumbs when they are handled in batch; the main reported log record is the one with the highest log level
- Send log along with exception when one is set in the main log record context
- Send log along with exception when one is set in the main log record context
- Customize data sent to Sentry to fit your needs
- Compatible with Monolog 1 and 2
- ~~Workaround for [an issue](https://github.com/getsentry/sentry-php/issues/811) that prevents sending logs in long running process~~

Expand Down Expand Up @@ -56,53 +57,10 @@ Check out the [handler constructor](src/SentryHandler.php) to know how to contro
>
>Check out the symfony guide for a complete example that addresses all these points
## Customizations / Extension points

It is required to inherit from `SentryHandler` class and override these methods:

```php
<?php

use BGalati\MonologSentryHandler\SentryHandler;
use Sentry\State\Scope;

class SpySentryHandler extends SentryHandler
{
/** {@inheritdoc} */
protected function processScope(Scope $scope, array $record, array $sentryEvent): void
{
// Your custom logic like this one:
// ....
if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) {
foreach ($record['context']['extra'] as $key => $value) {
$scope->setExtra((string) $key, $value);
}
}

if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) {
foreach ($record['context']['tags'] as $key => $value) {
$scope->setTag($key, $value);
}
}
}

/** {@inheritdoc} */
protected function afterWrite(): void
{
// Your custom code before events are flushed
// ...

// Call parent method to keep default behavior or don't call it if you don't need it
parent::afterWrite();
}
}
```

Please look at these methods within [the code](src/SentryHandler.php) if you want more details.

## Documentation

- [Symfony guide](doc/guide-symfony.md): it gives a way to integrate this handler to your app
- [Extension points](doc/extension-points.md): Customize data sent to Sentry and more

## FAQ

Expand Down
43 changes: 43 additions & 0 deletions doc/extension-points.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Extension points

It is required to inherit from `SentryHandler` class and override these methods:

```php
<?php

use BGalati\MonologSentryHandler\SentryHandler;
use Sentry\State\Scope;

class CustomSentryHandler extends SentryHandler
{
/** {@inheritdoc} */
protected function processScope(Scope $scope, array $record, array $sentryEvent): void
{
// Your custom logic like this one:
// ....
if (isset($record['context']['extra']) && \is_array($record['context']['extra'])) {
foreach ($record['context']['extra'] as $key => $value) {
$scope->setExtra((string) $key, $value);
}
}

if (isset($record['context']['tags']) && \is_array($record['context']['tags'])) {
foreach ($record['context']['tags'] as $key => $value) {
$scope->setTag($key, $value);
}
}
}

/** {@inheritdoc} */
protected function afterWrite(): void
{
// Your custom code before events are flushed
// ...

// Call parent method to keep default behavior or don't call it if you don't need it
parent::afterWrite();
}
}
```

Please look at these methods within [the code](../src/SentryHandler.php) if you want more details.
57 changes: 6 additions & 51 deletions doc/guide-symfony.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

>:information_source:
>
>- It was written for Symfony 4.
>- It was written for Symfony 4.4.
>- Its main purpose is to give ideas of how to integrate this handler in a Symfony project
This guide proposed an opinionated solution to integrate Sentry in a Symfony project.
Expand Down Expand Up @@ -42,10 +42,10 @@ It provides the following benefits:

## Step 1: Configure Sentry Hub

Symfony http client is required:
Symfony http client is suggested because of its native async capabilities:

```
composer require symfony/http-client
composer require symfony/http-client nyholm/psr7 guzzlehttp/promises
```

Let's configure the Sentry Hub with a factory. It gives full flexibility in terms of configuration.
Expand All @@ -54,22 +54,13 @@ Let's configure the Sentry Hub with a factory. It gives full flexibility in term
<?php

use App\Kernel;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpAsyncClient;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Client\Promise\HttpRejectedPromise;
use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Sentry\ClientBuilder;
use Sentry\Integration\RequestIntegration;
use Sentry\SentrySdk;
use Sentry\State\Hub;
use Sentry\State\HubInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpClient\HttplugClient;

class SentryFactory
{
Expand Down Expand Up @@ -98,7 +89,8 @@ class SentryFactory
],
]);

$clientBuilder->setHttpClient(new SentryHttpClient());
$client = HttpClient::create(['timeout' => 2]);
$clientBuilder->setHttpClient(new HttplugClient($client));

// Enable Sentry RequestIntegration
$options = $clientBuilder->getOptions();
Expand All @@ -110,43 +102,6 @@ class SentryFactory
return SentrySdk::setCurrentHub(new Hub($client));
}
}

/**
* @see https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/HttpClient/HttplugClient.php
*
* Beware of the following issue with the default HTTP that comes with the Sentry SDK
* @see https://github.com/getsentry/sentry-php/issues/878
*/
class SentryHttpClient implements HttpAsyncClient
{
private $client;

public function __construct()
{
$factory = new Psr17Factory();

// DO NOT use the http client from Symfony or you could turn into an infinite loop depending on your monolog config and the if logging of HTTP request is enabled or not
$client = HttpClient::create(['timeout' => 2]);

$this->client = new Psr18Client($client, $factory, $factory);
}

/**
* {@inheritdoc}
*/
public function sendAsyncRequest(RequestInterface $request)
{
try {
$response = $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
return new HttpRejectedPromise(new RequestException($e->getMessage(), $request, $e));
} catch (NetworkExceptionInterface $e) {
return new HttpRejectedPromise(new NetworkException($e->getMessage(), $request, $e));
}

return new HttpFulfilledPromise($response);
}
}
```

Then the factory is registered in the Symfony container:
Expand Down
2 changes: 1 addition & 1 deletion src/SentryHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ protected function write(array $record): void
* Extension point.
*
* This method is called when Sentry event is captured by the handler.
* Override it if you want to add custom logic to the $scope
* Override it if you want to add custom data to Sentry $scope.
*
* @param Scope $scope Sentry scope where you can add custom data
* @param array $record Current monolog record
Expand Down

0 comments on commit a1a39eb

Please sign in to comment.