Skip to content
Draft
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
5 changes: 4 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E
*/
public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId
{
$event = $this->prepareEvent($event, $hint, $scope);
// Client reports don't need to be augmented in the prepareEvent pipeline.
if ($event->getType() !== EventType::clientReport()) {
$event = $this->prepareEvent($event, $hint, $scope);
}

if ($event === null) {
return null;
Expand Down
45 changes: 45 additions & 0 deletions src/ClientReport/ClientReport.php
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;
}
}
79 changes: 79 additions & 0 deletions src/ClientReport/ClientReportAggregator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?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={} 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);

HubAdapter::getInstance()->captureEvent($event);
$this->reports = [];
}

public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}

return self::$instance;
}
}
102 changes: 102 additions & 0 deletions src/ClientReport/Reason.php
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];
}
}
29 changes: 29 additions & 0 deletions src/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sentry;

use Sentry\ClientReport\ClientReport;
use Sentry\Context\OsContext;
use Sentry\Context\RuntimeContext;
use Sentry\Logs\Log;
Expand Down Expand Up @@ -210,6 +211,11 @@ final class Event
*/
private $profile;

/**
* @var ClientReport[]
*/
private $clientReports;

private function __construct(?EventId $eventId, EventType $eventType)
{
$this->id = $eventId ?? EventId::generate();
Expand Down Expand Up @@ -252,6 +258,11 @@ public static function createMetrics(?EventId $eventId = null): self
return new self($eventId, EventType::metrics());
}

public static function createClientReport(?EventId $eventId = null): self
{
return new self($eventId, EventType::clientReport());
}

/**
* Gets the ID of this event.
*/
Expand Down Expand Up @@ -978,4 +989,22 @@ public function getTraceId(): ?string

return null;
}

/**
* @param ClientReport[] $clientReports
*/
public function setClientReports(array $clientReports): self
{
$this->clientReports = $clientReports;

return $this;
}

/**
* @return ClientReport[]
*/
public function getClientReports(): array
{
return $this->clientReports;
}
}
6 changes: 6 additions & 0 deletions src/EventType.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public static function metrics(): self
return self::getInstance('trace_metric');
}

public static function clientReport(): self
{
return self::getInstance('client_report');
}

/**
* List of all cases on the enum.
*
Expand All @@ -65,6 +70,7 @@ public static function cases(): array
self::checkIn(),
self::logs(),
self::metrics(),
self::clientReport(),
];
}

Expand Down
8 changes: 8 additions & 0 deletions src/Logs/LogsAggregator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@

use Sentry\Attributes\Attribute;
use Sentry\Client;
use Sentry\ClientReport\ClientReportAggregator;
use Sentry\ClientReport\Reason;
use Sentry\Event;
use Sentry\EventId;
use Sentry\SentrySdk;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Sentry\Transport\DataCategory;
use Sentry\Util\Arr;
use Sentry\Util\Str;

Expand All @@ -35,6 +38,11 @@ public function add(
array $values = [],
array $attributes = []
): void {
if (\count($this->logs) > 5) {
ClientReportAggregator::getInstance()->add(DataCategory::logBytes(), Reason::bufferOverflow(), 1);

return;
}
$timestamp = microtime(true);

$hub = SentrySdk::getCurrentHub();
Expand Down
30 changes: 30 additions & 0 deletions src/Serializer/EnvelopItems/ClientReportItem.php
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(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));
}
}
Loading