-
-
Notifications
You must be signed in to change notification settings - Fork 466
feat(client-reports): Add support for client reports #1978
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
base: master
Are you sure you want to change the base?
Changes from all commits
0070bf4
b5f7d86
7875acc
5cdbb8a
f30739a
3212b3f
0bb64a5
f3501e3
967ef89
371d711
811bd8f
9de7813
a38edb4
d1d639c
b848371
91e128d
30d2fc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\ClientReport; | ||
|
|
||
| class ClientReport | ||
| { | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $reason; | ||
|
|
||
| /** | ||
| * @var string | ||
| */ | ||
| private $category; | ||
|
|
||
| /** | ||
| * @var int | ||
| */ | ||
| private $quantity; | ||
|
|
||
| public function __construct(string $category, string $reason, int $quantity) | ||
| { | ||
| $this->category = $category; | ||
| $this->reason = $reason; | ||
| $this->quantity = $quantity; | ||
| } | ||
|
|
||
| public function getCategory(): string | ||
| { | ||
| return $this->category; | ||
| } | ||
|
|
||
| public function getQuantity(): int | ||
| { | ||
| return $this->quantity; | ||
| } | ||
|
|
||
| public function getReason(): string | ||
| { | ||
| return $this->reason; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\ClientReport; | ||
|
|
||
| use Sentry\Event; | ||
| use Sentry\State\HubAdapter; | ||
| use Sentry\Transport\DataCategory; | ||
|
|
||
| class ClientReportAggregator | ||
| { | ||
| /** | ||
| * @var self | ||
| */ | ||
| private static $instance; | ||
|
|
||
| /** | ||
| * Nested array for local aggregation. The first key is the category and the second one is the reason. | ||
| * | ||
| * ``` | ||
| * [ | ||
| * 'example-category' => [ | ||
| * 'example-reason' => 10 | ||
| * ] | ||
| * ] | ||
| *``` | ||
| * | ||
| * @var array<array<string, int>> | ||
| */ | ||
| private $reports = []; | ||
|
|
||
| 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, | ||
| ]); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
| $this->reports[$category][$reason] = ($this->reports[$category][$reason] ?? 0) + $quantity; | ||
| } | ||
|
|
||
| public function flush(): void | ||
| { | ||
| if (empty($this->reports)) { | ||
| return; | ||
| } | ||
| $reports = []; | ||
| foreach ($this->reports as $category => $reasons) { | ||
| foreach ($reasons as $reason => $quantity) { | ||
| $reports[] = new ClientReport($category, $reason, $quantity); | ||
| } | ||
| } | ||
| $event = Event::createClientReport(); | ||
| $event->setClientReports($reports); | ||
|
|
||
| // 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 (HubAdapter::getInstance()->captureEvent($event) !== null) { | ||
| $this->reports = []; | ||
| } | ||
| } | ||
|
|
||
| public static function getInstance(): self | ||
| { | ||
| if (self::$instance === null) { | ||
| self::$instance = new self(); | ||
| } | ||
|
|
||
| return self::$instance; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\ClientReport; | ||
|
|
||
| class Reason | ||
| { | ||
| /** | ||
| * @var string | ||
| */ | ||
| private $value; | ||
|
|
||
| /** | ||
| * @var array<self> | ||
| */ | ||
| private static $instances = []; | ||
|
|
||
| public function __construct(string $value) | ||
| { | ||
| $this->value = $value; | ||
| } | ||
|
|
||
| public static function queueOverflow(): self | ||
| { | ||
| return self::getInstance('queue_overflow'); | ||
| } | ||
|
|
||
| public static function cacheOverflow(): self | ||
| { | ||
| return self::getInstance('cache_overflow'); | ||
| } | ||
|
|
||
| public static function bufferOverflow(): self | ||
| { | ||
| return self::getInstance('buffer_overflow'); | ||
| } | ||
|
|
||
| public static function ratelimitBackoff(): self | ||
| { | ||
| return self::getInstance('ratelimit_backoff'); | ||
| } | ||
|
|
||
| public static function networkError(): self | ||
| { | ||
| return self::getInstance('network_error'); | ||
| } | ||
|
|
||
| public static function sampleRate(): self | ||
| { | ||
| return self::getInstance('sample_rate'); | ||
| } | ||
|
|
||
| public static function beforeSend(): self | ||
| { | ||
| return self::getInstance('before_send'); | ||
| } | ||
|
|
||
| public static function eventProcessor(): self | ||
| { | ||
| return self::getInstance('event_processor'); | ||
| } | ||
|
|
||
| public static function sendError(): self | ||
| { | ||
| return self::getInstance('send_error'); | ||
| } | ||
|
|
||
| public static function internalSdkError(): self | ||
| { | ||
| return self::getInstance('internal_sdk_error'); | ||
| } | ||
|
|
||
| public static function insufficientData(): self | ||
| { | ||
| return self::getInstance('insufficient_data'); | ||
| } | ||
|
|
||
| public static function backpressure(): self | ||
| { | ||
| return self::getInstance('backpressure'); | ||
| } | ||
|
|
||
| public function getValue(): string | ||
| { | ||
| return $this->value; | ||
| } | ||
|
|
||
| public function __toString() | ||
| { | ||
| return $this->value; | ||
| } | ||
|
|
||
| private static function getInstance(string $value): self | ||
| { | ||
| if (!isset(self::$instances[$value])) { | ||
| self::$instances[$value] = new self($value); | ||
| } | ||
|
|
||
| return self::$instances[$value]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\Serializer\EnvelopItems; | ||
|
|
||
| use Sentry\ClientReport\ClientReport; | ||
| use Sentry\Event; | ||
|
|
||
| class ClientReportItem implements EnvelopeItemInterface | ||
| { | ||
| public static function toEnvelopeItem(Event $event): ?string | ||
| { | ||
| $reports = $event->getClientReports(); | ||
|
|
||
| $headers = ['type' => 'client_report']; | ||
| $body = [ | ||
| 'timestamp' => $event->getTimestamp(), | ||
| 'discarded_events' => array_map(static function (ClientReport $report) { | ||
| return [ | ||
| 'category' => $report->getCategory(), | ||
| 'reason' => $report->getReason(), | ||
| 'quantity' => $report->getQuantity(), | ||
| ]; | ||
| }, $reports), | ||
| ]; | ||
|
|
||
| return \sprintf("%s\n%s", json_encode($headers), json_encode($body)); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Client report item ignores JSON encoding errorsMedium Severity
|
||
| } | ||


Uh oh!
There was an error while loading. Please reload this page.