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
75 changes: 75 additions & 0 deletions src/Attachment/Attachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Sentry\Attachment;

abstract class Attachment
{
private const DEFAULT_CONTENT_TYPE = 'application/octet-stream';

/**
* @var string
*/
private $filename;

/**
* @var string
*/
private $contentType;

public function __construct(string $filename, string $contentType)
{
$this->filename = $filename;
$this->contentType = $contentType;
}

public function getFilename(): string
{
return $this->filename;
}

public function getContentType(): string
{
return $this->contentType;
}

/**
* Returns the size in bytes for the attachment. This method should aim to use a low overhead
* way of determining the size because it will be called more than once.
* For example, for file attachments it should read the file size from the filesystem instead of
* reading the file in memory and then calculating the length.
* If no low overhead way exists, then the result should be cached so that calling it multiple times
* does not decrease performance.
*
* @return int the size in bytes or null if the length could not be determined, for example if the file
* does not exist
*/
abstract public function getSize(): ?int;

/**
* Fetches and returns the data. Calling this can have a non-trivial impact on memory usage, depending
* on the type and size of attachment.
*
* @return string the content as bytes or null if the content could not be retrieved, for example if the file
* does not exist
*/
abstract public function getData(): ?string;

/**
* Creates a new attachment representing a file referenced by a path.
* The file is not validated and the content is not read when creating the attachment.
*/
public static function fromFile(string $path, string $contentType = self::DEFAULT_CONTENT_TYPE): Attachment
{
return new FileAttachment($path, $contentType);
}

/**
* Creates a new attachment representing a slice of bytes that lives in memory.
*/
public static function fromBytes(string $filename, string $data, string $contentType = self::DEFAULT_CONTENT_TYPE): Attachment
{
return new ByteAttachment($filename, $contentType, $data);
}
}
32 changes: 32 additions & 0 deletions src/Attachment/ByteAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Sentry\Attachment;

/**
* Represents an attachment that is stored in memory and will not be read from the filesystem.
*/
class ByteAttachment extends Attachment
{
/**
* @var string
*/
private $data;

public function __construct(string $filename, string $contentType, string $data)
{
parent::__construct($filename, $contentType);
$this->data = $data;
}

public function getSize(): ?int
{
return \strlen($this->data);
}

public function getData(): ?string
{
return $this->data;
}
}
32 changes: 32 additions & 0 deletions src/Attachment/FileAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Sentry\Attachment;

/**
* Represents a file that is readable by using a path.
*/
class FileAttachment extends Attachment
{
/**
* @var string
*/
private $path;

public function __construct(string $path, string $contentType)
{
parent::__construct(basename($path), $contentType);
$this->path = $path;
}

public function getSize(): ?int
{
return @filesize($this->path) ?: null;
}

public function getData(): ?string
{
return @file_get_contents($this->path) ?: null;
}
}
22 changes: 22 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\Attachment\Attachment;
use Sentry\Context\OsContext;
use Sentry\Context\RuntimeContext;
use Sentry\Logs\Log;
Expand Down Expand Up @@ -204,6 +205,11 @@ final class Event
*/
private $profile;

/**
* @var Attachment[]
*/
private $attachments = [];

private function __construct(?EventId $eventId, EventType $eventType)
{
$this->id = $eventId ?? EventId::generate();
Expand Down Expand Up @@ -933,4 +939,20 @@ public function getTraceId(): ?string

return null;
}

/**
* @return Attachment[]
*/
public function getAttachments(): array
{
return $this->attachments;
}

/**
* @param Attachment[] $attachments
*/
public function setAttachments(array $attachments): void
{
$this->attachments = $attachments;
}
}
40 changes: 40 additions & 0 deletions src/Serializer/EnvelopItems/AttachmentItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Sentry\Serializer\EnvelopItems;

use Sentry\Attachment\Attachment;
use Sentry\Event;
use Sentry\Util\JSON;

class AttachmentItem implements EnvelopeItemInterface
{
public static function toAttachmentItem(Attachment $attachment): ?string
{
$data = $attachment->getData();
if ($data === null) {
return null;
}

$header = [
'type' => 'attachment',
'filename' => $attachment->getFilename(),
'content_type' => $attachment->getContentType(),
'attachment_type' => 'event.attachment',
'length' => $attachment->getSize(),
];

return \sprintf("%s\n%s", JSON::encode($header), $data);
}

public static function toEnvelopeItem(Event $event): ?string
{
$result = [];
foreach ($event->getAttachments() as $attachment) {
$result[] = self::toAttachmentItem($attachment);
}

return implode("\n", $result);
}
}
3 changes: 3 additions & 0 deletions src/Serializer/PayloadSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Sentry\Event;
use Sentry\EventType;
use Sentry\Options;
use Sentry\Serializer\EnvelopItems\AttachmentItem;
use Sentry\Serializer\EnvelopItems\CheckInItem;
use Sentry\Serializer\EnvelopItems\EventItem;
use Sentry\Serializer\EnvelopItems\LogsItem;
Expand Down Expand Up @@ -60,12 +61,14 @@ public function serialize(Event $event): string
switch ($event->getType()) {
case EventType::event():
$items[] = EventItem::toEnvelopeItem($event);
$items[] = AttachmentItem::toEnvelopeItem($event);
break;
case EventType::transaction():
$items[] = TransactionItem::toEnvelopeItem($event);
if ($event->getSdkMetadata('profile') !== null) {
$items[] = ProfileItem::toEnvelopeItem($event);
}
$items[] = AttachmentItem::toEnvelopeItem($event);
break;
case EventType::checkIn():
$items[] = CheckInItem::toEnvelopeItem($event);
Expand Down
14 changes: 14 additions & 0 deletions src/State/Hub.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Sentry\State;

use Psr\Log\NullLogger;
use Sentry\Attachment\Attachment;
use Sentry\Breadcrumb;
use Sentry\CheckIn;
use Sentry\CheckInStatus;
Expand Down Expand Up @@ -231,6 +232,19 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool
return $breadcrumb !== null;
}

public function addAttachment(Attachment $attachment): bool
{
$client = $this->getClient();

if ($client === null) {
return false;
}

$this->getScope()->addAttachment($attachment);

return true;
}

/**
* {@inheritdoc}
*/
Expand Down
9 changes: 9 additions & 0 deletions src/State/HubAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sentry\State;

use Sentry\Attachment\Attachment;
use Sentry\Breadcrumb;
use Sentry\CheckInStatus;
use Sentry\ClientInterface;
Expand Down Expand Up @@ -155,6 +156,14 @@ public function addBreadcrumb(Breadcrumb $breadcrumb): bool
return SentrySdk::getCurrentHub()->addBreadcrumb($breadcrumb);
}

/**
* {@inheritDoc}
*/
public function addAttachment(Attachment $attachment): bool
{
return SentrySdk::getCurrentHub()->addAttachment($attachment);
}

/**
* {@inheritdoc}
*/
Expand Down
6 changes: 6 additions & 0 deletions src/State/HubInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Sentry\State;

use Sentry\Attachment\Attachment;
use Sentry\Breadcrumb;
use Sentry\CheckInStatus;
use Sentry\ClientInterface;
Expand Down Expand Up @@ -152,4 +153,9 @@ public function getSpan(): ?Span;
* Sets the span on the Hub.
*/
public function setSpan(?Span $span): HubInterface;

/**
* Records a new attachment that will be attached to error and transaction events.
*/
public function addAttachment(Attachment $attachment): bool;
}
28 changes: 28 additions & 0 deletions src/State/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Sentry\State;

use Sentry\Attachment\Attachment;
use Sentry\Breadcrumb;
use Sentry\Event;
use Sentry\EventHint;
use Sentry\EventType;
use Sentry\Options;
use Sentry\Severity;
use Sentry\Tracing\DynamicSamplingContext;
Expand Down Expand Up @@ -75,6 +77,11 @@ class Scope
*/
private $span;

/**
* @var Attachment[]
*/
private $attachments = [];

/**
* @var callable[] List of event processors
*
Expand Down Expand Up @@ -333,6 +340,7 @@ public function clear(): self
$this->tags = [];
$this->extra = [];
$this->contexts = [];
$this->attachments = [];

return $this;
}
Expand Down Expand Up @@ -411,6 +419,12 @@ public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $op
$hint = new EventHint();
}

if ($event->getType() === EventType::event() || $event->getType() === EventType::transaction()) {
if (empty($event->getAttachments())) {
$event->setAttachments($this->attachments);
}
}

foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) {
$event = $processor($event, $hint);

Expand Down Expand Up @@ -481,4 +495,18 @@ public function __clone()
$this->propagationContext = clone $this->propagationContext;
}
}

public function addAttachment(Attachment $attachment): self
{
$this->attachments[] = $attachment;

return $this;
}

public function clearAttachments(): self
{
$this->attachments = [];

return $this;
}
}
Loading
Loading