diff --git a/src/Event.php b/src/Event.php index 0148069ac..67f5f3407 100644 --- a/src/Event.php +++ b/src/Event.php @@ -61,6 +61,11 @@ final class Event */ private $checkIn; + /** + * @var array|null + */ + private $logs; + /** * @var string|null The name of the server (e.g. the host name) */ @@ -216,6 +221,11 @@ public static function createCheckIn(?EventId $eventId = null): self return new self($eventId, EventType::checkIn()); } + public static function createLogs(?EventId $eventId = null): self + { + return new self($eventId, EventType::logs()); + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ @@ -368,6 +378,18 @@ public function setCheckIn(?CheckIn $checkIn): self return $this; } + public function getLogs(): array + { + return $this->logs; + } + + public function setLogs(array $logs): self + { + $this->logs = $logs; + + return $this; + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ diff --git a/src/EventType.php b/src/EventType.php index b8ffdef94..3c2d13fb3 100644 --- a/src/EventType.php +++ b/src/EventType.php @@ -42,6 +42,11 @@ public static function checkIn(): self return self::getInstance('check_in'); } + public static function logs(): self + { + return self::getInstance('log'); + } + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */ @@ -61,6 +66,7 @@ public static function cases(): array self::event(), self::transaction(), self::checkIn(), + self::logs(), self::metrics(), ]; } diff --git a/src/Logs/LogLevel.php b/src/Logs/LogLevel.php new file mode 100644 index 000000000..6e61f2849 --- /dev/null +++ b/src/Logs/LogLevel.php @@ -0,0 +1,42 @@ + A list of cached enum instances + */ + private static $instances = []; + + private function __construct(string $value) + { + $this->value = $value; + } + + public static function info(): self + { + return self::getInstance('info'); + } + + public function __toString(): string + { + 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]; + } +} diff --git a/src/Logs/Logs.php b/src/Logs/Logs.php new file mode 100644 index 000000000..b639d64bc --- /dev/null +++ b/src/Logs/Logs.php @@ -0,0 +1,44 @@ +aggregator = new LogsAggregator(); + } + + public static function getInstance(): self + { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function info(string $message): void + { + $this->aggregator->add(LogLevel::info(), $message); + } + + public function flush(): ?EventId + { + return $this->aggregator->flush(); + } +} diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php new file mode 100644 index 000000000..d5e6ee202 --- /dev/null +++ b/src/Logs/LogsAggregator.php @@ -0,0 +1,96 @@ +getSpan(); + if ($span !== null) { + $traceId = $span->getTraceId(); + } + + $this->logs[] = [ + 'timestamp' => $timestamp, + 'trace_id' => (string) $traceId, + 'level' => (string) $level, + 'body' => $message, + ]; + } + + public function flush(): ?EventId + { + if (empty($this->logs)) { + return null; + } + + $hub = SentrySdk::getCurrentHub(); + $event = Event::createLogs()->setLogs($this->logs); + + $this->logs = []; + + return $hub->captureEvent($event); + } + + /** + * @param array $tags + * + * @return array + */ + private function serializeTags(array $tags): array + { + $hub = SentrySdk::getCurrentHub(); + $client = $hub->getClient(); + + if ($client !== null) { + $options = $client->getOptions(); + + $defaultTags = [ + 'environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, + ]; + + $release = $options->getRelease(); + if ($release !== null) { + $defaultTags['release'] = $release; + } + + $hub->configureScope(function (Scope $scope) use (&$defaultTags) { + $transaction = $scope->getTransaction(); + if ( + $transaction !== null + // Only include the transaction name if it has good quality + && $transaction->getMetadata()->getSource() !== TransactionSource::url() + ) { + $defaultTags['transaction'] = $transaction->getName(); + } + }); + + $tags = array_merge($defaultTags, $tags); + } + + // It's very important to sort the tags in order to obtain the same bucket key. + ksort($tags); + + return $tags; + } +} diff --git a/src/Serializer/EnvelopItems/LogsItem.php b/src/Serializer/EnvelopItems/LogsItem.php new file mode 100644 index 000000000..89021e72e --- /dev/null +++ b/src/Serializer/EnvelopItems/LogsItem.php @@ -0,0 +1,31 @@ + (string) $event->getType(), + 'content_type' => 'application/json', + ]; + + $payload = ''; + + $logs = $event->getLogs(); + foreach ($logs as $log) { + $payload .= \sprintf("%s\n%s", JSON::encode($header), JSON::encode($log)); + } + + return $payload; + } +} diff --git a/src/Serializer/PayloadSerializer.php b/src/Serializer/PayloadSerializer.php index c109e0336..6e33add8d 100644 --- a/src/Serializer/PayloadSerializer.php +++ b/src/Serializer/PayloadSerializer.php @@ -9,6 +9,7 @@ use Sentry\Options; use Sentry\Serializer\EnvelopItems\CheckInItem; use Sentry\Serializer\EnvelopItems\EventItem; +use Sentry\Serializer\EnvelopItems\LogsItem; use Sentry\Serializer\EnvelopItems\ProfileItem; use Sentry\Serializer\EnvelopItems\TransactionItem; use Sentry\Tracing\DynamicSamplingContext; @@ -77,6 +78,9 @@ public function serialize(Event $event): string case EventType::checkIn(): $items = CheckInItem::toEnvelopeItem($event); break; + case EventType::logs(): + $items = LogsItem::toEnvelopeItem($event); + break; } return \sprintf("%s\n%s", JSON::encode($envelopeHeader), $items); diff --git a/src/functions.php b/src/functions.php index 46a3d99e0..4eadf4944 100644 --- a/src/functions.php +++ b/src/functions.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Sentry\HttpClient\HttpClientInterface; use Sentry\Integration\IntegrationInterface; +use Sentry\Logs\Logs; use Sentry\Metrics\Metrics; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; @@ -377,6 +378,11 @@ function continueTrace(string $sentryTrace, string $baggage): TransactionContext return TransactionContext::fromHeaders($sentryTrace, $baggage); } +function logger(): Logs +{ + return Logs::getInstance(); +} + /** * @deprecated Metrics are no longer supported. Metrics API is a no-op and will be removed in 5.x. */