diff --git a/phpunit.xml b/phpunit.xml
index 254b6617..f531804a 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -4,21 +4,20 @@
bootstrap="./tests/bootstrap.php"
colors="true"
cacheDirectory=".phpunit.cache"
- defaultTestSuite="Rollbar Test Suite"
->
+ defaultTestSuite="Rollbar Test Suite">
./tests/
- ./tests/Performance/
- ./tests/TestHelpers/
- ./tests/FakeDataBuilder.php
- ./tests/bootstrap.php
- ./tests/BaseRollbarTest.php
+ ./tests/Performance/
+ ./tests/TestHelpers/
+ ./tests/FakeDataBuilder.php
+ ./tests/bootstrap.php
+ ./tests/BaseRollbarTest.php
-
- ./tests/Performance/
-
+
+ ./tests/Performance/
+
@@ -27,10 +26,9 @@
-
-
- ./src
-
-
-
+
+
+ src
+
+
diff --git a/src/Config.php b/src/Config.php
index 1a93ec1f..6ebd855e 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -2,6 +2,7 @@
namespace Rollbar;
+use InvalidArgumentException;
use Monolog\Handler\ErrorLogHandler;
use Monolog\Handler\NoopHandler;
use Monolog\Logger;
@@ -12,6 +13,8 @@
use Rollbar\Senders\AgentSender;
use Rollbar\Senders\CurlSender;
use Rollbar\Senders\SenderInterface;
+use Rollbar\Telemetry\Telemeter;
+use Rollbar\Telemetry\TelemetryFilterInterface;
use Throwable;
use Rollbar\Senders\FluentSender;
@@ -60,6 +63,7 @@ class Config
'scrub_safelist',
'timeout',
'transmit',
+ 'telemetry',
'custom_truncation',
'report_suppressed',
'use_error_reporting',
@@ -218,6 +222,13 @@ class Config
*/
private bool $raiseOnError = false;
+ /**
+ * @var null|array The telemetry config. If null, telemetry is disabled.
+ *
+ * @since 4.1.0
+ */
+ private ?array $telemetry;
+
/**
* @var int The maximum number of items reported to Rollbar within one
* request.
@@ -303,6 +314,7 @@ protected function updateConfig(array $config): void
$this->setCheckIgnoreFunction($config);
$this->setSendMessageTrace($config);
$this->setRaiseOnError($config);
+ $this->setTelemetry($config);
if (isset($config['included_errno'])) {
$this->includedErrno = $config['included_errno'];
@@ -333,7 +345,7 @@ private function setAccessToken(array $config): void
if (isset($_ENV['ROLLBAR_ACCESS_TOKEN']) && !isset($config['access_token'])) {
$config['access_token'] = $_ENV['ROLLBAR_ACCESS_TOKEN'];
}
-
+
$this->utilities()->validateString(
$config['access_token'],
"config['access_token']",
@@ -443,7 +455,7 @@ private function setTransformer(array $config): void
private function setMinimumLevel(array $config): void
{
- $this->minimumLevel = \Rollbar\Defaults::get()->minimumLevel();
+ $this->minimumLevel = Defaults::get()->minimumLevel();
$override = $config['minimum_level'] ?? null;
$override = array_key_exists('minimumLevel', $config) ? $config['minimumLevel'] : $override;
@@ -468,7 +480,7 @@ private function setReportSuppressed(array $config): void
}
if (!$this->reportSuppressed) {
- $this->reportSuppressed = \Rollbar\Defaults::get()->reportSuppressed();
+ $this->reportSuppressed = Defaults::get()->reportSuppressed();
}
}
@@ -508,10 +520,24 @@ private function setRaiseOnError(array $config): void
if (array_key_exists('raise_on_error', $config)) {
$this->raiseOnError = $config['raise_on_error'];
} else {
- $this->raiseOnError = \Rollbar\Defaults::get()->raiseOnError();
+ $this->raiseOnError = Defaults::get()->raiseOnError();
}
}
+ /**
+ * Sets and cleans the telemetry config.
+ *
+ * @param array $config The config array.
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ private function setTelemetry(array $config): void
+ {
+ $telemetry = key_exists('telemetry', $config) ? $config['telemetry']: true;
+ $this->telemetry = Defaults::get()->telemetry($telemetry);
+ }
+
private function setBatchSize(array $config): void
{
if (array_key_exists('batch_size', $config)) {
@@ -746,7 +772,7 @@ protected function setupWithOptions(
}
if (!$this->$keyName instanceof $expectedType) {
- throw new \InvalidArgumentException(
+ throw new InvalidArgumentException(
"$keyName must be a $expectedType"
);
}
@@ -817,6 +843,61 @@ public function getRaiseOnError(): bool
return $this->raiseOnError;
}
+ /**
+ * Returns the telemetry instance or null if telemetry is disabled.
+ *
+ * @param Telemeter|null $telemeter An optional telemeter instance to scope the telemetry to. This allows us to
+ * keep the same telemeter instance when mutating the config. Otherwise, we would
+ * destroy the telemetry queue when mutating the config.
+ * @return Telemeter|null The telemetry instance or null if telemetry is disabled.
+ *
+ * @since 4.1.0
+ */
+ public function getTelemetry(?Telemeter $telemeter): ?Telemeter
+ {
+ if (null === $this->telemetry) {
+ return null;
+ }
+ $config = $this->telemetry;
+ $config['filter'] = $this->initTelemetryFilter($config['filter']);
+ if (null === $telemeter) {
+ return new Telemeter(...$config);
+ }
+ $telemeter->scope(...$config);
+ return $telemeter;
+ }
+
+ /**
+ * Returns the telemetry filter instance or null if no filter is configured.
+ *
+ * @param string|null $filterClass The fully qualified class name of the telemetry filter class or null if no
+ * filter is configured.
+ * @return TelemetryFilterInterface|null
+ *
+ * @throws InvalidArgumentException if the configured filter class does not exist or does not implement
+ * {@see TelemetryFilterInterface}.
+ *
+ * @since 4.1.0
+ */
+ private function initTelemetryFilter(?string $filterClass): ?TelemetryFilterInterface
+ {
+ if (null === $filterClass) {
+ return null;
+ }
+ if (!class_exists($filterClass)) {
+ throw new InvalidArgumentException(
+ "Telemetry filter class $filterClass does not exist"
+ );
+ }
+ $filter = new $filterClass($this->telemetry);
+ if (!$filter instanceof TelemetryFilterInterface) {
+ throw new InvalidArgumentException(
+ "Telemetry filter class $filterClass must implement TelemetryFilterInterface"
+ );
+ }
+ return $filter;
+ }
+
public function transform(
Payload $payload,
Level|string $level,
@@ -1058,9 +1139,9 @@ public function shouldSuppress(): bool
// > the value E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR |
// > E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE.
// https://www.php.net/manual/en/language.operators.errorcontrol.php
- if (version_compare(PHP_VERSION, '8.0', 'ge') && $errorReporting === (
- E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE
- )) {
+ if ($errorReporting === (
+ E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE
+ )) {
return true;
}
diff --git a/src/DataBuilder.php b/src/DataBuilder.php
index 4096d5a5..55ed93d3 100644
--- a/src/DataBuilder.php
+++ b/src/DataBuilder.php
@@ -10,11 +10,11 @@
use Rollbar\Payload\Server;
use Rollbar\Payload\Request;
use Rollbar\Payload\Data;
+use Rollbar\Payload\TelemetryEvent;
use Rollbar\Payload\Trace;
use Rollbar\Payload\Frame;
use Rollbar\Payload\TraceChain;
use Rollbar\Payload\ExceptionInfo;
-use Rollbar\Rollbar;
use Stringable;
use Throwable;
@@ -411,7 +411,7 @@ protected function getBody(Throwable|string|Stringable $toLog, array $context):
} else {
$content = $this->getMessage($toLog);
}
- return new Body($content, $context);
+ return new Body($content, $context, $this->getTelemetry());
}
public function getErrorTrace(ErrorWrapper $error)
@@ -562,8 +562,8 @@ protected function getMessage($toLog)
return new Message(
(string)$toLog,
$this->sendMessageTrace ?
- debug_backtrace($this->localVarsDump ? 0 : DEBUG_BACKTRACE_IGNORE_ARGS) :
- null
+ debug_backtrace($this->localVarsDump ? 0 : DEBUG_BACKTRACE_IGNORE_ARGS) :
+ null
);
}
@@ -669,7 +669,19 @@ protected function getRequest()
}
return $request;
}
-
+
+ /**
+ * Returns the array of telemetry events to be sent with the payload or null if telemetry is not enabled.
+ *
+ * @return TelemetryEvent[]|null
+ *
+ * @since 4.1.0
+ */
+ protected function getTelemetry(): ?array
+ {
+ return Rollbar::getTelemeter()?->copyEvents();
+ }
+
public function parseForwardedString($forwarded)
{
$result = array();
@@ -913,8 +925,7 @@ protected function getPerson()
try {
$personData = ($this->personFunc)();
} catch (\Exception $exception) {
- Rollbar::scope(array('person_fn' => null))->
- log(Level::ERROR, $exception);
+ Rollbar::scope(array('person_fn' => null))->log(Level::ERROR, $exception);
}
}
@@ -1247,27 +1258,27 @@ private function stripShutdownFrames($backTrace)
{
foreach ($backTrace as $index => $frame) {
extract($frame);
-
+
$fatalHandlerMethod = (isset($method)
- && $method === 'Rollbar\\Handlers\\FatalHandler::handle');
-
+ && $method === 'Rollbar\\Handlers\\FatalHandler::handle');
+
$fatalHandlerClassAndFunction = (isset($class)
- && $class === 'Rollbar\\Handlers\\FatalHandler'
- && isset($function)
- && $function === 'handle');
-
+ && $class === 'Rollbar\\Handlers\\FatalHandler'
+ && isset($function)
+ && $function === 'handle');
+
$errorHandlerMethod = (isset($method)
- && $method === 'Rollbar\\Handlers\\ErrorHandler::handle');
-
+ && $method === 'Rollbar\\Handlers\\ErrorHandler::handle');
+
$errorHandlerClassAndFunction = (isset($class)
- && $class === 'Rollbar\\Handlers\\ErrorHandler'
- && isset($function)
- && $function === 'handle');
-
+ && $class === 'Rollbar\\Handlers\\ErrorHandler'
+ && isset($function)
+ && $function === 'handle');
+
if ($fatalHandlerMethod ||
- $fatalHandlerClassAndFunction ||
- $errorHandlerMethod ||
- $errorHandlerClassAndFunction) {
+ $fatalHandlerClassAndFunction ||
+ $errorHandlerMethod ||
+ $errorHandlerClassAndFunction) {
return array_slice($backTrace, $index+1);
}
}
diff --git a/src/Defaults.php b/src/Defaults.php
index a84a5cfd..c29fb407 100644
--- a/src/Defaults.php
+++ b/src/Defaults.php
@@ -5,6 +5,7 @@
use Monolog\Logger;
use Rollbar\Payload\Notifier;
use Psr\Log\LogLevel;
+use Rollbar\Telemetry\Telemeter;
use Throwable;
class Defaults
@@ -328,12 +329,33 @@ public function minimumLevel($value = null)
{
return $value ?? $this->minimumLevel;
}
-
+
public function raiseOnError($value = null)
{
return $value ?? $this->raiseOnError;
}
+ /**
+ * Returns the telemetry configuration array or null if telemetry should be disabled.
+ *
+ * @param bool|array|null $value If true or null returns the default telemetry configuration. If false returns null.
+ *
+ * @return array|null The telemetry configuration.
+ *
+ * @since 4.1.0
+ */
+ public function telemetry(bool|array|null $value = null): ?array
+ {
+ if (true === $value) {
+ return $this->telemetry;
+ }
+ if (is_array($value)) {
+ // Ensure that the telemetry array contains only the keys we expect and includes all the required keys.
+ return array_merge($this->telemetry, array_intersect_key($value, $this->telemetry));
+ }
+ return null;
+ }
+
private $psrLevels;
private $errorLevels;
private $autodetectBranch = false;
@@ -384,4 +406,16 @@ public function raiseOnError($value = null)
private $maxItems = 10;
private $minimumLevel = 0;
private $raiseOnError = false;
+
+ /**
+ * @var array $telemetry The default telemetry configuration.
+ *
+ * @since 4.1.0
+ */
+ private array $telemetry = [
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => null,
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => false,
+ ];
}
diff --git a/src/Payload/Body.php b/src/Payload/Body.php
index 66e0132b..6b870cee 100644
--- a/src/Payload/Body.php
+++ b/src/Payload/Body.php
@@ -9,43 +9,119 @@ class Body implements SerializerInterface
{
use UtilitiesTrait;
+ /**
+ * Creates a new instance of the Body class.
+ *
+ * @param ContentInterface $value The value to assign to the content property.
+ * @param array $extra An array to assign to the extra property. Default value is an empty
+ * array.
+ * @param TelemetryEvent[]|null $telemetry An optional array of telemetry events. Default value is null.
+ * @return void
+ *
+ * @since 4.1.0 The $telemetry property was added.
+ */
public function __construct(
private ContentInterface $value,
- private array $extra = array()
+ private array $extra = [],
+ private ?array $telemetry = null
) {
}
+ /**
+ * Returns the main content of the payload body.
+ *
+ * @return ContentInterface
+ */
public function getValue(): ContentInterface
{
return $this->value;
}
+ /**
+ * Sets the main content of the payload body.
+ *
+ * @param ContentInterface $value The value to assign to the content of the payload body.
+ *
+ * @return self
+ */
public function setValue(ContentInterface $value): self
{
$this->value = $value;
return $this;
}
-
+
+ /**
+ * Sets the array of extra data.
+ *
+ * @param array $extra The array of extra data.
+ *
+ * @return self
+ */
public function setExtra(array $extra): self
{
$this->extra = $extra;
return $this;
}
-
+
+ /**
+ * Returns the array of extra data.
+ *
+ * @return array
+ */
public function getExtra(): array
{
return $this->extra;
}
+ /**
+ * Returns the array of telemetry events or null if there were none.
+ *
+ * @return TelemetryEvent[]|null
+ *
+ * @since 4.1.0
+ */
+ public function getTelemetry(): ?array
+ {
+ if (empty($this->telemetry)) {
+ return null;
+ }
+ return $this->telemetry;
+ }
+
+ /**
+ * Sets the list of telemetry events for this payload body.
+ *
+ * @param array|null $telemetry The list of telemetry events or null if there were none.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function setTelemetry(?array $telemetry): void
+ {
+ $this->telemetry = $telemetry;
+ }
+
+ /**
+ * Returns the JSON serializable representation of the payload body.
+ *
+ * @return array
+ *
+ * @since 4.1.0 Includes the 'telemetry' key, if it is not empty.
+ */
public function serialize()
{
$result = array();
$result[$this->value->getKey()] = $this->value;
-
+
if (!empty($this->extra)) {
$result['extra'] = $this->extra;
}
-
+
+ if (!empty($this->telemetry)) {
+ $result['telemetry'] = $this->telemetry;
+ }
+
return $this->utilities()->serializeForRollbarInternal($result, array('extra'));
}
}
diff --git a/src/Payload/TelemetryBody.php b/src/Payload/TelemetryBody.php
new file mode 100644
index 00000000..3bb84a50
--- /dev/null
+++ b/src/Payload/TelemetryBody.php
@@ -0,0 +1,103 @@
+extra = $extra;
+ }
+
+ /**
+ * Returns the array representation of the telemetry body.
+ *
+ * @return array
+ */
+ public function serialize(): array
+ {
+ // This filters out any null or empty values.
+ $result = array_filter([
+ 'message' => $this->message,
+ 'method' => $this->method,
+ 'url' => $this->url,
+ 'status_code' => $this->status_code,
+ 'subtype' => $this->subtype,
+ 'stack' => $this->stack,
+ 'from' => $this->from,
+ 'to' => $this->to,
+ 'start_timestamp_ms' => $this->start_timestamp_ms,
+ 'end_timestamp_ms' => $this->end_timestamp_ms,
+ ]);
+
+ if (empty($this->extra)) {
+ return $result;
+ }
+
+ // This keeps the extra data from overwriting the defined keys when the extra data is merged into the result.
+ $extra = array_diff_key($this->extra, array_fill_keys(self::DEFINED_KEYS, null));
+
+ return $this->utilities()->serializeForRollbarInternal(array_merge($result, $extra));
+ }
+}
diff --git a/src/Payload/TelemetryEvent.php b/src/Payload/TelemetryEvent.php
new file mode 100644
index 00000000..6754473e
--- /dev/null
+++ b/src/Payload/TelemetryEvent.php
@@ -0,0 +1,66 @@
+timestamp)) {
+ $this->timestamp = floor(microtime(true) * 1000);
+ }
+ $this->body = is_array($body) ? new TelemetryBody(...$body): $body;
+ }
+
+ public function serialize(): array
+ {
+ $result = array_filter([
+ 'uuid' => $this->uuid,
+ 'source' => $this->source,
+ 'level' => $this->level,
+ 'type' => $this->type,
+ 'body' => $this->body->serialize(),
+ 'timestamp_ms' => $this->timestamp,
+ ]);
+
+ return $this->utilities()->serializeForRollbarInternal($result);
+ }
+}
diff --git a/src/Rollbar.php b/src/Rollbar.php
index 1261e7e3..81d9ea61 100644
--- a/src/Rollbar.php
+++ b/src/Rollbar.php
@@ -8,6 +8,9 @@
use Rollbar\Handlers\FatalHandler;
use Rollbar\Handlers\ErrorHandler;
use Rollbar\Handlers\ExceptionHandler;
+use Rollbar\Payload\TelemetryBody;
+use Rollbar\Payload\TelemetryEvent;
+use Rollbar\Telemetry\Telemeter;
use Stringable;
use Throwable;
@@ -42,6 +45,19 @@ class Rollbar
*/
private static ?ExceptionHandler $exceptionHandler = null;
+ /**
+ * The instance of the telemeter. This is null if Rollbar has not been initialized or {@see Rollbar::destroy()} has
+ * been called.
+ *
+ * The Telemeter instance is placed here so that it is not destroyed and the queue lost if the Rollbar config is
+ * changed.
+ *
+ * @var Telemeter|null
+ *
+ * @since 4.1.0
+ */
+ private static ?Telemeter $telemeter = null;
+
/**
* Sets up Rollbar monitoring and logging.
*
@@ -92,6 +108,8 @@ public static function init(
}
self::setupBatchHandling();
}
+
+ self::updateTelemeter();
}
/**
@@ -109,6 +127,7 @@ private static function setLogger(RollbarLogger|array $configOrLogger): void
{
if ($configOrLogger instanceof RollbarLogger) {
self::$logger = $configOrLogger;
+ self::updateTelemeter();
return;
}
@@ -119,6 +138,19 @@ private static function setLogger(RollbarLogger|array $configOrLogger): void
}
self::$logger = new RollbarLogger($configOrLogger);
+ self::updateTelemeter();
+ }
+
+ /**
+ * Updates the telemeter instance with the latest configs.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ private static function updateTelemeter(): void
+ {
+ self::$telemeter = self::$logger->getConfig()->getTelemetry(self::$telemeter);
}
/**
@@ -184,7 +216,11 @@ public static function scope(array $config): RollbarLogger
if (is_null(self::$logger)) {
return new RollbarLogger($config);
}
- return self::$logger->scope($config);
+ $logger = self::$logger->scope($config);
+
+ // Reassign the telemeter in case the config changed.
+ self::updateTelemeter();
+ return $logger;
}
/**
@@ -364,6 +400,36 @@ public static function emergency(string|Stringable $message, array $context = ar
self::log(Level::EMERGENCY, $message, $context);
}
+ /**
+ * Captures a telemetry event that may be sent with future payloads.
+ *
+ * @param string $type The type of telemetry data. One of: "log", "network", "dom", "navigation",
+ * "error", or "manual".
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error",
+ * "warning", "info", or "debug".
+ * @param array|TelemetryBody $metadata Additional data about the telemetry event.
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided,
+ * the current time will be used.
+ *
+ * @return TelemetryEvent|null Returns the {@see TelemetryEvent} that was captured or null if Rollbar or the
+ * {@see Telemeter} has not been initialized or the event is filtered out.
+ *
+ * @since 4.1.0
+ */
+ public static function captureTelemetryEvent(
+ string $type,
+ string $level,
+ array|TelemetryBody $metadata,
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ if (is_null(self::$logger)) {
+ return null;
+ }
+ return self::$logger->captureTelemetryEvent($type, $level, $metadata, $uuid, $timestamp);
+ }
+
/**
* Creates a listener that monitors for exceptions.
*
@@ -397,6 +463,18 @@ public static function setupFatalHandling(): void
self::$fatalHandler->register();
}
+ /**
+ * Returns the configured instance of the telemeter.
+ *
+ * @return Telemeter|null
+ *
+ * @since 4.1.0
+ */
+ public static function getTelemeter(): ?Telemeter
+ {
+ return self::$telemeter;
+ }
+
/**
* Creates and returns a {@see Response} to use if Rollbar is attempted to be used prior to being initialized.
*
@@ -490,16 +568,20 @@ public static function getCustom(): ?array
public static function configure(array $config): void
{
self::$logger->configure($config);
+ self::updateTelemeter();
}
/**
- * Destroys the currently stored $logger allowing for a fresh configuration. This is especially used in testing
- * scenarios.
+ * Destroys the currently stored $logger and $telemeter allowing for a fresh configuration. This is especially used
+ * in testing scenarios.
*
* @return void
+ *
+ * @since 4.1.0 Also destroys the telemeter.
*/
public static function destroy(): void
{
self::$logger = null;
+ self::$telemeter = null;
}
}
diff --git a/src/RollbarLogger.php b/src/RollbarLogger.php
index acfab632..9e2e5d98 100644
--- a/src/RollbarLogger.php
+++ b/src/RollbarLogger.php
@@ -5,6 +5,9 @@
use Exception;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface;
+use Rollbar\Payload\TelemetryBody;
+use Rollbar\Payload\TelemetryEvent;
+use Rollbar\Telemetry\Telemeter;
use Stringable;
use Throwable;
use Psr\Log\AbstractLogger;
@@ -212,6 +215,7 @@ public function log($level, $message, array $context = array()): void
* @throws Throwable Rethrown $message if it is {@see Throwable} and {@see Config::raiseOnError} is true.
*
* @since 4.0.0
+ * @since 4.1.0 Will include the reported item in the telemetry events if applicable.
*/
public function report(
string|Level $level,
@@ -239,14 +243,29 @@ public function report(
$this->verboseLogger()->info("Attempting to log: [$level] " . $message);
- if ($this->config->internalCheckIgnored($level, $message)) {
+ $accessToken = $this->getAccessToken();
+ $payload = null;
+ $ignored = $this->config->internalCheckIgnored($level, $message);
+
+ // We don't want to build the payload if it is going to be ignored, so we can avoid the overhead of building it.
+ // This is done before adding the event to the telemetry queue, so the reported occurrence is not duplicated in
+ // the telemetry data.
+ if (!$ignored) {
+ $payload = $this->getPayload($accessToken, $level, $message, $context);
+ }
+ // Add the event to the telemetry queue if it is enabled, but after we have built the payload, so it is not
+ // duplicated in the telemetry data as well.
+ $telemeter = Rollbar::getTelemeter();
+ if (null !== $telemeter && $telemeter->shouldIncludeItemsInTelemetry()) {
+ $uuid = (!$ignored)? $payload->getData()->getUuid(): null;
+ $telemeter->captureRollbarItem($level, $message, $context, $ignored, $uuid);
+ }
+
+ if ($ignored) {
$this->verboseLogger()->info('Occurrence ignored');
return new Response(0, "Ignored");
}
- $accessToken = $this->getAccessToken();
- $payload = $this->getPayload($accessToken, $level, $message, $context);
-
if ($this->config->checkIgnored($payload, $message, $isUncaught)) {
$this->verboseLogger()->info('Occurrence ignored');
$response = new Response(0, "Ignored");
@@ -283,6 +302,33 @@ public function report(
return $response;
}
+ /**
+ * Captures a telemetry event that may be sent with future payloads.
+ *
+ * @param string $type The type of telemetry data. One of: "log", "network", "dom", "navigation",
+ * "error", or "manual".
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error",
+ * "warning", "info", or "debug".
+ * @param array|TelemetryBody $metadata Additional data about the telemetry event.
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided,
+ * the current time will be used.
+ *
+ * @return TelemetryEvent|null Returns the {@see TelemetryEvent} that was captured or null if the {@see Telemeter}
+ * has not been initialized or the event is filtered out.
+ *
+ * @since 4.1.0
+ */
+ public function captureTelemetryEvent(
+ string $type,
+ string $level,
+ array|TelemetryBody $metadata,
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ return Rollbar::getTelemeter()?->capture($type, $level, $metadata, $uuid, $timestamp);
+ }
+
/**
* Sends and flushes the batch payload queue.
*
diff --git a/src/Telemetry/DataType.php b/src/Telemetry/DataType.php
new file mode 100644
index 00000000..be09237b
--- /dev/null
+++ b/src/Telemetry/DataType.php
@@ -0,0 +1,29 @@
+= 8.1.
+ *
+ * @since 4.1.0
+ */
+class DataType
+{
+ const LOG = 'log';
+
+ const NETWORK = 'network';
+
+ /**
+ * This is intended for use with browsers, and is only included here for API completeness. Generally, this should
+ * not be used in a PHP context.
+ */
+ const DOM = 'dom';
+
+ const NAVIGATION = 'navigation';
+
+ const ERROR = 'error';
+
+ const MANUAL = 'manual';
+}
diff --git a/src/Telemetry/Telemeter.php b/src/Telemetry/Telemeter.php
new file mode 100644
index 00000000..dcf20908
--- /dev/null
+++ b/src/Telemetry/Telemeter.php
@@ -0,0 +1,548 @@
+maxQueueSize = max(0, min($maxTelemetryEvents, self::MAX_EVENTS));
+ }
+
+ /**
+ * Returns the Rollbar telemetry type that corresponds to the given PSR-3 log level.
+ *
+ * @param string $level The PSR-3 log level.
+ * @return string
+ */
+ private static function getTypeFromLevel(string $level): string
+ {
+ return match ($level) {
+ Level::EMERGENCY, Level::ALERT, Level::CRITICAL, Level::ERROR, Level::WARNING => DataType::ERROR,
+ Level::NOTICE, Level::INFO => DataType::LOG,
+ default => DataType::MANUAL,
+ };
+ }
+
+ /**
+ * Returns the Rollbar telemetry level that corresponds to the given PSR-3 log level.
+ *
+ * @param string $level The PSR-3 log level.
+ * @return string
+ */
+ private static function getLevelFromLevel(string $level): string
+ {
+ return match ($level) {
+ Level::EMERGENCY, Level::ALERT, Level::CRITICAL => 'critical',
+ Level::ERROR => 'error',
+ Level::WARNING => 'warning',
+ Level::DEBUG => 'debug',
+ default => 'info',
+ };
+ }
+
+ /**
+ * Reconfigures the Telemeter with the given options. See the constructor for a description of the options.
+ *
+ * @param int $maxTelemetryEvents
+ * @param TelemetryFilterInterface|null $filter
+ * @param bool $includeItemsInTelemetry
+ * @param bool $includeIgnoredItemsInTelemetry
+ * @return void
+ */
+ public function scope(
+ int $maxTelemetryEvents = self::MAX_EVENTS,
+ ?TelemetryFilterInterface $filter = null,
+ bool $includeItemsInTelemetry = true,
+ bool $includeIgnoredItemsInTelemetry = false
+ ): void {
+ if ($maxTelemetryEvents !== $this->maxQueueSize) {
+ // We call this method so that the queue is truncated if necessary.
+ $this->setMaxQueueSize($maxTelemetryEvents);
+ }
+ if ($filter !== $this->filter) {
+ $this->filter = $filter;
+ }
+ if ($includeItemsInTelemetry !== $this->includeItemsInTelemetry) {
+ $this->includeItemsInTelemetry = $includeItemsInTelemetry;
+ }
+ if ($includeIgnoredItemsInTelemetry !== $this->includeIgnoredItemsInTelemetry) {
+ $this->includeIgnoredItemsInTelemetry = $includeIgnoredItemsInTelemetry;
+ }
+ }
+
+ /**
+ * Returns the current queue of telemetry events.
+ *
+ * Note: this method returns a copy of the queue array, but the TelemetryEvent objects are not cloned, so modifying
+ * the events in the returned array will modify the events in the queue.
+ *
+ * @return TelemetryEvent[]
+ */
+ public function copyEvents(): array
+ {
+ if (null === $this->filter || !$this->filter->filterOnRead()) {
+ return $this->queue;
+ }
+ $queue = [];
+ $filtered = 0;
+ foreach ($this->queue as $event) {
+ // The queue size needs to be calculated as the number of events in the queue minus the number of events
+ // that have already been filtered.
+ if (!$this->filter->include($event, count($this->queue) - $filtered)) {
+ $filtered++;
+ continue;
+ }
+ $queue[] = $event;
+ }
+ return $queue;
+ }
+
+ /**
+ * Appends a telemetry event to the queue. If the queue is full, the oldest event will be discarded.
+ *
+ * Note: using this method directly will bypass any filters that have been set on the Telemeter.
+ *
+ * @param TelemetryEvent $event The telemetry event to add to the queue.
+ *
+ * @return void
+ */
+ public function push(TelemetryEvent $event): void
+ {
+ if ($this->maxQueueSize === 0) {
+ return;
+ }
+ $this->queue[] = $event;
+ if (count($this->queue) > $this->maxQueueSize) {
+ array_shift($this->queue);
+ }
+ }
+
+ /**
+ * Captures a telemetry event and adds it to the queue.
+ *
+ * @param string $type The type of telemetry data. One of: "log", "network", "dom", "navigation",
+ * "error", or "manual".
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error",
+ * "warning", "info", or "debug".
+ * @param array|TelemetryBody $metadata Additional data about the telemetry event.
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided,
+ * the current time will be used.
+ *
+ * @return TelemetryEvent|null
+ */
+ public function capture(
+ string $type,
+ string $level,
+ array|TelemetryBody $metadata,
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ if ($this->maxQueueSize === 0) {
+ return null;
+ }
+ $event = new TelemetryEvent($type, $level, $metadata, $timestamp);
+ if (null !== $uuid) {
+ $event->uuid = $uuid;
+ }
+ if (null !== $this->filter && !$this->filter->include($event, count($this->queue))) {
+ return null;
+ }
+ $this->push($event);
+ return $event;
+ }
+
+ /**
+ * Captures an error as a telemetry event and adds it to the queue.
+ *
+ * @param array|string|ErrorWrapper|Throwable $error The error to capture. If a string is given, it will be used
+ * as the message. If an array is given, it will be used as
+ * the metadata body. If an ErrorWrapper is given, it will be
+ * parsed for the message and stack trace.
+ * @param string $level The severity level of the telemetry data. One of:
+ * "critical", "error", "warning", "info", or "debug".
+ * Defaults to "error".
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If
+ * not provided, the current time will be used.
+ *
+ * @return TelemetryEvent|null Returns the {@see TelemetryEvent} that was added to the queue, or null if the event
+ * was filtered out.
+ */
+ public function captureError(
+ array|string|ErrorWrapper|Throwable $error,
+ string $level = 'error',
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ if (is_string($error)) {
+ return $this->capture('error', $level, new TelemetryBody(message: $error), $uuid, $timestamp);
+ }
+ if ($error instanceof ErrorWrapper) {
+ $metadata = new TelemetryBody(
+ message: $error->getMessage(),
+ subtype: 'error',
+ stack: $this->stringifyBacktrace($error->getBacktrace()),
+ );
+ return $this->capture('error', $level, $metadata, $uuid, $timestamp);
+ }
+ if ($error instanceof Throwable) {
+ $metadata = new TelemetryBody(
+ message: $error->getMessage(),
+ subtype: 'exception',
+ stack: $this->stringifyBacktrace($error->getTrace())
+ );
+ return $this->capture('error', $level, $metadata, $uuid, $timestamp);
+ }
+ return $this->capture('error', $level, $error, $uuid, $timestamp);
+ }
+
+ /**
+ * Captures a log message as a telemetry event and adds it to the queue.
+ *
+ * @param string $message The log message to capture.
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error", "warning",
+ * "info", or "debug". Defaults to "info".
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided, the
+ * current time will be used.
+ *
+ * @return TelemetryEvent|null
+ */
+ public function captureLog(
+ string $message,
+ string $level = 'info',
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ return $this->capture('log', $level, new TelemetryBody(message: $message), $uuid, $timestamp);
+ }
+
+ /**
+ * Captures a network event as a telemetry event and adds it to the queue.
+ *
+ * @param string $method The HTTP method. E.g. GET, POST, etc.
+ * @param string $url The URL of the request.
+ * @param string $status_code The HTTP status code.
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error", "warning",
+ * "info", or "debug". Defaults to "info".
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided, the
+ * current time will be used.
+ *
+ * @return TelemetryEvent|null
+ */
+ public function captureNetwork(
+ string $method,
+ string $url,
+ string $status_code,
+ string $level = 'info',
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ return $this->capture(
+ type: 'log',
+ level: $level,
+ metadata: new TelemetryBody(
+ method: $method,
+ url: $url,
+ status_code: $status_code,
+ ),
+ uuid: $uuid,
+ timestamp: $timestamp,
+ );
+ }
+
+ /**
+ * Captures a navigation event as a telemetry event and adds it to the queue.
+ *
+ * @param string $from The URL of the previous page.
+ * @param string $to The URL of the next page.
+ * @param string $level The severity level of the telemetry data. One of: "critical", "error", "warning",
+ * "info", or "debug". Defaults to "info".
+ * @param string|null $uuid The Rollbar UUID to associate with this telemetry event.
+ * @param int|null $timestamp When this occurred, as a unix timestamp in milliseconds. If not provided, the
+ * current time will be used.
+ *
+ * @return TelemetryEvent|null
+ */
+ public function captureNavigation(
+ string $from,
+ string $to,
+ string $level = 'info',
+ string $uuid = null,
+ ?int $timestamp = null,
+ ): ?TelemetryEvent {
+ return $this->capture('log', $level, new TelemetryBody(from: $from, to: $to), $uuid, $timestamp);
+ }
+
+ /**
+ * Add a Rollbar captured item to the telemetry queue.
+ *
+ * @param string $level The PSR-3 log level.
+ * @param string|Stringable|Throwable $message The message to log.
+ * @param array $context The context.
+ * @param bool $ignored Whether the item was ignored.
+ * @param string|null $uuid The Rollbar item UUID.
+ *
+ * @return TelemetryEvent|null
+ *
+ * @internal This method is for internal use only and may change without warning.
+ */
+ public function captureRollbarItem(
+ string $level,
+ string|Stringable|Throwable $message,
+ array $context = [],
+ bool $ignored = false,
+ ?string $uuid = null,
+ ): ?TelemetryEvent {
+ if (!$this->includeItemsInTelemetry) {
+ return null;
+ }
+ if (!$this->includeIgnoredItemsInTelemetry && $ignored) {
+ return null;
+ }
+ if (null !== $this->filter && !$this->filter->includeRollbarItem($level, $message, $context, $ignored)) {
+ return null;
+ }
+ // Make sure to respect the PSR exception context. See https://www.php-fig.org/psr/psr-3/#13-context.
+ if (($context['exception'] ?? null) instanceof Throwable) {
+ $event = $this->captureError($context['exception'], self::getLevelFromLevel($level), $uuid);
+ if (null === $event) {
+ return null;
+ }
+ // We have both a message from the exception instance and a message. So we will use the message as the
+ // primary body message, and the exception message will be saved to a custom "error_message" property on
+ // the posted telemetry event body.
+ $event->body->extra['error_message'] = $event->body->message;
+ $event->body->message = $this->getRollbarItemMessage($message);
+ return $event;
+ }
+ // If the rollbar item is an exception, we should capture it as an error event.
+ if ($message instanceof Throwable) {
+ return $this->captureError($message, self::getLevelFromLevel($level), $uuid);
+ }
+ // Otherwise, we will capture it based on the level.
+ return $this->capture(
+ type: self::getTypeFromLevel($level),
+ level: self::getLevelFromLevel($level),
+ metadata: new TelemetryBody(message: $this->getRollbarItemMessage($message)),
+ uuid: $uuid,
+ );
+ }
+
+ /**
+ * Returns the maximum number of telemetry events that can be queued before discarding prior events.
+ *
+ * @return int
+ */
+ public function getMaxQueueSize(): int
+ {
+ return $this->maxQueueSize;
+ }
+
+ /**
+ * Update the maximum number of telemetry events that can be queued before discarding. NOTE: If the new max is less
+ * than the current number of queued events, the oldest events will be discarded.
+ *
+ * @param int $maxQueueSize The maximum number of telemetry events to queue before discarding. Must be between 0
+ * and 100.
+ */
+ public function setMaxQueueSize(int $maxQueueSize): void
+ {
+ $newMax = max(0, min($maxQueueSize, self::MAX_EVENTS));
+ $queueSize = count($this->queue);
+ if ($queueSize > $newMax) {
+ array_splice($this->queue, 0, $queueSize - $newMax);
+ }
+ $this->maxQueueSize = $newMax;
+ }
+
+ /**
+ * Returns the current number of telemetry events in the queue.
+ *
+ * @return int
+ */
+ public function getQueueSize(): int
+ {
+ return count($this->queue);
+ }
+
+ /**
+ * Clears the queue of all telemetry events.
+ *
+ * @return void
+ */
+ public function clearQueue(): void
+ {
+ $this->queue = [];
+ }
+
+ /**
+ * If true, the items caught by Rollbar will be included in the telemetry of future items sent to Rollbar.
+ *
+ * @return bool
+ */
+ public function shouldIncludeItemsInTelemetry(): bool
+ {
+ return $this->includeItemsInTelemetry;
+ }
+
+ /**
+ * Change whether Rollbar items should be included in the telemetry queue.
+ *
+ * @param bool $include True to include Rollbar items in the telemetry data.
+ */
+ public function setIncludeItemsInTelemetry(bool $include): void
+ {
+ $this->includeItemsInTelemetry = $include;
+ }
+
+ /**
+ * Returns the filter instance that is applied to telemetry items before they are added to the queue.
+ *
+ * @return TelemetryFilterInterface|null
+ */
+ public function getFilter(): ?TelemetryFilterInterface
+ {
+ return $this->filter;
+ }
+
+ /**
+ * Sets the filter to apply to telemetry items before they are added to the queue. This will also apply the new
+ * filter to any items already in the queue if $apply is true.
+ *
+ * @param TelemetryFilterInterface|null $filter A filter to apply to telemetry items before they are added to the
+ * queue. If null, no filter will be applied.
+ * @param bool $apply If true, the new filter will be applied to any items already in
+ * the queue.
+ */
+ public function setFilter(?TelemetryFilterInterface $filter, bool $apply = true): void
+ {
+ $this->filter = $filter;
+ if (null === $filter || !$apply) {
+ return;
+ }
+ $tempQueue = [];
+ $filtered = 0;
+ foreach ($this->queue as $event) {
+ // The queue size needs to be calculated as the number of events in the queue minus the number of events
+ // that have already been filtered.
+ if (!$this->filter->include($event, count($this->queue) - $filtered)) {
+ $filtered++;
+ continue;
+ }
+ $tempQueue[] = $event;
+ }
+ $this->queue = $tempQueue;
+ }
+
+ /**
+ * Returns true if a Rollbar captured item that has been ignored should still be included in the telemetry data.
+ *
+ * @return bool
+ */
+ public function shouldIncludeIgnoredItemsInTelemetry(): bool
+ {
+ return $this->includeIgnoredItemsInTelemetry;
+ }
+
+ /**
+ * Sets whether items captured by Rollbar should be included in the telemetry data even if they are ignored.
+ *
+ * @param bool $include True to include ignored items in the telemetry data.
+ * @return void
+ */
+ public function setIncludeIgnoredItemsInTelemetry(bool $include): void
+ {
+ $this->includeIgnoredItemsInTelemetry = $include;
+ }
+
+ /**
+ * Returns the message from a Rollbar reported item.
+ *
+ * @param string|Stringable|Throwable $message The message to log.
+ *
+ * @return string
+ */
+ private function getRollbarItemMessage(string|Stringable|Throwable $message): string
+ {
+ if (is_string($message)) {
+ return $message;
+ }
+ if ($message instanceof Throwable) {
+ return $message->getMessage();
+ }
+ // else $message is a Stringable instance
+ return $message->__toString();
+ }
+
+ /**
+ * Given a standard PHP backtrace array, returns a string representation of the backtrace.
+ *
+ * @param array $backtrace The backtrace array.
+ * @return string
+ */
+ private function stringifyBacktrace(array $backtrace): string
+ {
+ $result = '';
+ foreach ($backtrace as $i => $frame) {
+ $result .= '#' . $i . ' ';
+ $result .= $frame['class'] ?? '';
+ $result .= $frame['type'] ?? '';
+ $result .= $frame['function'] ?? '';
+ if (isset($frame['args'])) {
+ $result .= '(';
+ $result .= implode(', ', array_map(fn($arg) => is_string($arg) ? $arg : gettype($arg), $frame['args']));
+ $result .= ')';
+ }
+ $result .= ' at ';
+ $result .= $frame['file'] ?? '';
+ $result .= ':';
+ $result .= $frame['line'] ?? '';
+ $result .= "\n";
+ }
+ return $result;
+ }
+}
diff --git a/src/Telemetry/TelemetryFilterInterface.php b/src/Telemetry/TelemetryFilterInterface.php
new file mode 100644
index 00000000..35f2697d
--- /dev/null
+++ b/src/Telemetry/TelemetryFilterInterface.php
@@ -0,0 +1,81 @@
+ includeItemsInTelemetry` config
+ * option to `false`.
+ * 2. Ignored Rollbar items are not included in the telemetry data by changing the default value of the
+ * `telemetry => includeIgnoredItemsInTelemetry` config option to `true`. And the reported item is ignored
+ * because of its log level or PHP error reporting level.
+ *
+ * @param string $level The PSR-3 log level.
+ * @param string|Stringable $message The message to log.
+ * @param array $context The context.
+ * @param bool $ignored Whether the item was ignored as a result of the configuration. If false, then
+ * the item will not be sent to Rollbar. However, you may still want to include
+ * it in the telemetry data.
+ *
+ * @return bool True if the item should be included in the telemetry queue, false if it should be excluded.
+ */
+ public function includeRollbarItem(
+ string $level,
+ string|Stringable $message,
+ array $context = [],
+ bool $ignored = false,
+ ): bool;
+
+ /**
+ * Returns `true` if the {@see include()} method should be called not only before the event is added to the queue,
+ * but also before the event is sent to Rollbar. This means the {@see include()} method will be called twice for
+ * each event.
+ *
+ * If this method returns `false`, then the {@see include()} method will only be called before the event is added to
+ * the queue.
+ *
+ * @return bool
+ */
+ public function filterOnRead(): bool;
+}
diff --git a/src/Truncation/TelemetryStrategy.php b/src/Truncation/TelemetryStrategy.php
new file mode 100644
index 00000000..e9b11d1f
--- /dev/null
+++ b/src/Truncation/TelemetryStrategy.php
@@ -0,0 +1,89 @@
+data();
+
+ // If telemetry is not enabled, then remove the telemetry data from the payload entirely.
+ if (null === Rollbar::getTelemeter()) {
+ unset($data['data']['body']['telemetry']);
+ $payload->encode($data);
+ return $payload;
+ }
+
+ if (!isset($data['data']['body']['telemetry'])) {
+ return $payload;
+ }
+
+ $data['data']['body']['telemetry'] = self::selectTelemetry($data['data']['body']['telemetry']);
+ $payload->encode($data);
+
+ return $payload;
+ }
+
+ /**
+ * Returns true if the given payload contains telemetry data. This is irrespective of whether the telemetry is
+ * enabled or not.
+ *
+ * @param EncodedPayload $payload The payload to truncate.
+ *
+ * @return bool
+ *
+ * @since 4.1.0
+ */
+ public function applies(EncodedPayload $payload): bool
+ {
+ // If the payload does not telemetry data, then this strategy does not apply.
+ return isset($payload->data()['data']['body']['telemetry']);
+ }
+}
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
index e090f336..ef4c17c4 100644
--- a/tests/ConfigTest.php
+++ b/tests/ConfigTest.php
@@ -404,12 +404,18 @@ public function testReportSuppressed($errorReporting, $configKey, $configValue,
public function testReportSuppressedActuallySuppressed()
{
+ // To make sure that the suppression actually works, we need to reset the error_reporting.
+ $oldErrorReporting = error_reporting(E_ALL);
+
$config = new Config(array(
'report_suppressed' => false,
"access_token" => $this->getTestAccessToken()
));
$this->assertFalse($config->shouldSuppress());
$this->assertTrue(@$config->shouldSuppress());
+
+ // Reset the error_reporting to its original value.
+ error_reporting($oldErrorReporting);
}
public function testFilter(): void
diff --git a/tests/DefaultsTest.php b/tests/DefaultsTest.php
index b2f49a00..0d34e87a 100644
--- a/tests/DefaultsTest.php
+++ b/tests/DefaultsTest.php
@@ -5,6 +5,7 @@
use Rollbar\Payload\Level;
use Rollbar\Payload\Notifier;
use Psr\Log\LogLevel;
+use Rollbar\Telemetry\Telemeter;
use Throwable;
class DefaultsTest extends BaseRollbarTest
@@ -284,6 +285,50 @@ public function testRaiseOnError(): void
$this->assertEquals(false, $this->defaults->raiseOnError());
}
+ public function testTelemetry(): void
+ {
+ $this->assertSame([
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => null,
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => false,
+ ], $this->defaults->telemetry(true));
+
+ $this->assertSame([
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => null,
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => false,
+ ], $this->defaults->telemetry([]));
+
+ $this->assertNull($this->defaults->telemetry(null));
+ $this->assertNull($this->defaults->telemetry(false));
+
+ $this->assertSame([
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => null,
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => false,
+ ], $this->defaults->telemetry(['foo' => 'bar']));
+
+ $this->assertSame([
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => null,
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => true,
+ ], $this->defaults->telemetry([
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => true,
+ ]));
+
+ $this->assertSame([
+ 'maxTelemetryEvents' => Telemeter::MAX_EVENTS,
+ 'filter' => 'foo',
+ 'includeItemsInTelemetry' => true,
+ 'includeIgnoredItemsInTelemetry' => false,
+ ], $this->defaults->telemetry(['filter' => 'foo']));
+ }
+
/**
* @testWith ["message_level", "warning"]
* ["MESSAGE_LEVEL", "warning"]
diff --git a/tests/Payload/TelemetryBodyTest.php b/tests/Payload/TelemetryBodyTest.php
new file mode 100644
index 00000000..3043455d
--- /dev/null
+++ b/tests/Payload/TelemetryBodyTest.php
@@ -0,0 +1,100 @@
+message);
+ self::assertSame('method', $body->method);
+ self::assertSame('url', $body->url);
+ self::assertSame('status', $body->status_code);
+ self::assertSame('sub', $body->subtype);
+ self::assertSame('stack', $body->stack);
+ self::assertSame('from', $body->from);
+ self::assertSame('to', $body->to);
+ self::assertSame(42, $body->start_timestamp_ms);
+ self::assertSame(43, $body->end_timestamp_ms);
+ self::assertSame([
+ 'extraOne' => 'foo',
+ 'extraTwo' => 'bar',
+ ], $body->extra);
+
+ // Assert array order does not matter.
+ $body = new TelemetryBody(...[
+ 'message' => 'message',
+ 'extraOne' => 'foo',
+ 'stack' => 'stack',
+ ]);
+
+ self::assertSame('message', $body->message);
+ self::assertSame('foo', $body->extra['extraOne']);
+ self::assertSame('stack', $body->stack);
+ }
+
+ public function testSerialize(): void
+ {
+ $body = new TelemetryBody(
+ message: 'message',
+ method: 'method',
+ url: 'url',
+ status_code: 'status',
+ subtype: 'sub',
+ stack: 'stack',
+ from: 'from',
+ to: 'to',
+ start_timestamp_ms: 42,
+ end_timestamp_ms: 43,
+ extraOne: 'foo',
+ extraTwo: 'bar',
+ );
+
+ self::assertSame([
+ 'message' => 'message',
+ 'method' => 'method',
+ 'url' => 'url',
+ 'status_code' => 'status',
+ 'subtype' => 'sub',
+ 'stack' => 'stack',
+ 'from' => 'from',
+ 'to' => 'to',
+ 'start_timestamp_ms' => 42,
+ 'end_timestamp_ms' => 43,
+ 'extraOne' => 'foo',
+ 'extraTwo' => 'bar',
+ ], $body->serialize());
+ }
+
+ public function testEmptyProperties(): void
+ {
+ $body = new TelemetryBody();
+ self::assertEmpty($body->serialize());
+ }
+
+ public function testExtraDoesNotOverrideProperty(): void
+ {
+ $body = new TelemetryBody(message: 'foo');
+ $body->extra['message'] = 'bar';
+
+ self::assertSame(['message' => 'foo'], $body->serialize());
+ }
+}
diff --git a/tests/Payload/TelemetryEventTest.php b/tests/Payload/TelemetryEventTest.php
new file mode 100644
index 00000000..4f94e1d6
--- /dev/null
+++ b/tests/Payload/TelemetryEventTest.php
@@ -0,0 +1,22 @@
+ 'foo']);
+ $after = floor(microtime(true) * 1000);
+
+ self::assertNotNull($event->timestamp);
+ self::assertGreaterThanOrEqual($before, $event->timestamp);
+ self::assertLessThanOrEqual($after, $event->timestamp);
+ }
+}
diff --git a/tests/RollbarLoggerTest.php b/tests/RollbarLoggerTest.php
index 04d26032..818d89cc 100644
--- a/tests/RollbarLoggerTest.php
+++ b/tests/RollbarLoggerTest.php
@@ -193,6 +193,22 @@ public function testReportWithIsUncaught(): void
$this->assertEquals(200, $response->getStatus());
}
+ public function testReportTelemetry(): void
+ {
+ // Init used so that the telemeter is initialized.
+ Rollbar::init([
+ "access_token" => $this->getTestAccessToken(),
+ "environment" => "testing-php",
+ ]);
+
+ Rollbar::logger()->report(Level::WARNING, "Testing PHP Notifier", isUncaught: true);
+ $events = Rollbar::getTelemeter()->copyEvents();
+ $this->assertCount(1, $events);
+ $this->assertSame($events[0]->type, 'error');
+ $this->assertSame($events[0]->level, 'warning');
+ $this->assertSame($events[0]->body->message, 'Testing PHP Notifier');
+ }
+
public function testDefaultVerbose(): void
{
$this->testNotVerbose();
diff --git a/tests/RollbarTest.php b/tests/RollbarTest.php
index b4ad8c2a..642a022e 100644
--- a/tests/RollbarTest.php
+++ b/tests/RollbarTest.php
@@ -2,6 +2,9 @@
use Rollbar\Payload\Payload;
use Rollbar\Payload\Level;
+use Rollbar\Payload\TelemetryEvent;
+use Rollbar\Telemetry\DataType;
+use Rollbar\Telemetry\Telemeter;
use Rollbar\TestHelpers\ArrayLogger;
/**
@@ -63,6 +66,19 @@ public function testInitReplaceLogger(): void
$this->assertSame($logger, Rollbar::logger());
}
+ public function testInitTelemeter(): void
+ {
+ // Default telemeter is enabled
+ Rollbar::init(self::$simpleConfig);
+
+ $this->assertInstanceOf(Telemeter::class, Rollbar::getTelemeter());
+
+ // Ensure telemeter is disabled when config is set to false
+ Rollbar::init(array_merge(['telemetry' => false], self::$simpleConfig));
+
+ $this->assertNull(Rollbar::getTelemeter());
+ }
+
public function testLogException(): void
{
Rollbar::init(self::$simpleConfig);
@@ -151,6 +167,22 @@ public function testEmergency(): void
{
$this->shortcutMethodTestHelper(Level::EMERGENCY);
}
+
+ public function testCaptureTelemetryEvent(): void
+ {
+ Rollbar::init(self::$simpleConfig);
+
+ $event = Rollbar::captureTelemetryEvent(
+ type: DataType::LOG,
+ level: 'info',
+ metadata: ['message' => 'test message'],
+ );
+
+ self::assertInstanceOf(TelemetryEvent::class, $event);
+ self::assertEquals(DataType::LOG, $event->type);
+ self::assertEquals('test message', $event->body->message);
+ self::assertEquals('info', $event->level);
+ }
protected function shortcutMethodTestHelper($level): void
{
diff --git a/tests/Telemetry/TelemeterTest.php b/tests/Telemetry/TelemeterTest.php
new file mode 100644
index 00000000..71015cd5
--- /dev/null
+++ b/tests/Telemetry/TelemeterTest.php
@@ -0,0 +1,369 @@
+getMaxQueueSize());
+
+ $telemeter = new Telemeter(42);
+ self::assertSame(42, $telemeter->getMaxQueueSize());
+
+ $telemeter = new Telemeter(105);
+ self::assertSame(100, $telemeter->getMaxQueueSize());
+ }
+
+ public function testScope(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertSame(100, $telemeter->getMaxQueueSize());
+ self::assertNull($telemeter->getFilter());
+ self::assertTrue($telemeter->shouldIncludeItemsInTelemetry());
+ self::assertFalse($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+
+ $telemeter->scope(42, new TestTelemetryFilter(), false, true);
+ self::assertSame(42, $telemeter->getMaxQueueSize());
+ self::assertInstanceOf(TestTelemetryFilter::class, $telemeter->getFilter());
+ self::assertFalse($telemeter->shouldIncludeItemsInTelemetry());
+ self::assertTrue($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+ }
+
+ public function testPush(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertSame(100, $telemeter->getMaxQueueSize());
+ self::assertSame(0, $telemeter->getQueueSize());
+
+ $telemeter->push(new TelemetryEvent(DataType::LOG, 'info', ['message' => 'foo']));
+ self::assertSame(1, $telemeter->getQueueSize());
+
+ $telemeter->push(new TelemetryEvent(DataType::LOG, 'info', new TelemetryBody('bar')));
+ self::assertSame(2, $telemeter->getQueueSize());
+ }
+
+ public function testCopyEvents(): void
+ {
+ $telemeter = new Telemeter();
+
+ $event1 = new TelemetryEvent(DataType::LOG, 'info', ['message' => 'foo']);
+ $event2 = new TelemetryEvent(DataType::LOG, 'info', new TelemetryBody('bar'));
+
+ $telemeter->push($event1);
+ $telemeter->push($event2);
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(2, count($events));
+ self::assertSame($event1, $events[0]);
+ self::assertSame($event2, $events[1]);
+ }
+
+ public function testCopyEventsFilter(): void
+ {
+ $filter = new TestTelemetryFilter();
+ $filter->includeFunction = Closure::fromCallable(function (TelemetryEvent $event, int $queueSize): bool {
+ return $event->body->message !== 'foo';
+ });
+
+ $telemeter = new Telemeter(filter: $filter);
+
+ $event1 = new TelemetryEvent(DataType::LOG, 'info', ['message' => 'foo']);
+ $event2 = new TelemetryEvent(DataType::LOG, 'info', new TelemetryBody('bar'));
+
+ $telemeter->push($event1);
+ $telemeter->push($event2);
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame($event2, $events[0]);
+ }
+
+ public function testCapture(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->capture(DataType::LOG, 'info', ['message' => 'foo']);
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame('foo', $events[0]->body->message);
+ self::assertSame('info', $events[0]->level);
+ self::assertSame(DataType::LOG, $events[0]->type);
+ self::assertNotNull($events[0]->timestamp);
+ }
+
+ public function testCaptureFilter(): void
+ {
+ $filter = new TestTelemetryFilter();
+ $filter->includeFunction = Closure::fromCallable(function (TelemetryEvent $event, int $queueSize): bool {
+ return $event->body->message !== 'foo';
+ });
+
+ $telemeter = new Telemeter(filter: $filter);
+ $telemeter->capture(DataType::LOG, 'info', ['message' => 'foo']);
+ $telemeter->capture(DataType::LOG, 'info', ['message' => 'bar']);
+
+ // Because the filter is also applied on the copyEvents() call, we want to make sure that the 'foo' event is
+ // filtered out, but the 'bar' event is not.
+ self::assertSame(1, $telemeter->getQueueSize());
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame('bar', $events[0]->body->message);
+ }
+
+ public function testCaptureError(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->captureError('foo');
+ $telemeter->captureError(['message' => 'bar'], 'warning');
+ $telemeter->captureError(['message' => 'baz'], 'critical');
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(3, count($events));
+ self::assertSame('foo', $events[0]->body->message);
+ self::assertSame('error', $events[0]->type);
+ self::assertSame('error', $events[0]->level);
+
+ self::assertSame('bar', $events[1]->body->message);
+ self::assertSame('error', $events[1]->type);
+ self::assertSame('warning', $events[1]->level);
+
+ self::assertSame('baz', $events[2]->body->message);
+ self::assertSame('error', $events[2]->type);
+ self::assertSame('critical', $events[2]->level);
+ }
+
+ public function testCaptureLog(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->captureLog('foo');
+ $telemeter->captureLog('bar', 'debug');
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(2, count($events));
+ self::assertSame('foo', $events[0]->body->message);
+ self::assertSame('log', $events[0]->type);
+ self::assertSame('info', $events[0]->level);
+
+ self::assertSame('bar', $events[1]->body->message);
+ self::assertSame('log', $events[1]->type);
+ self::assertSame('debug', $events[1]->level);
+ }
+
+ public function testCaptureNetwork(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->captureNetwork('POST', 'https://example.com', '200');
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame('POST', $events[0]->body->method);
+ self::assertSame('https://example.com', $events[0]->body->url);
+ self::assertSame('200', $events[0]->body->status_code);
+ }
+
+ public function testCaptureNavigation(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->captureNavigation('https://example.com/foo', 'https://example.com/bar');
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame('https://example.com/foo', $events[0]->body->from);
+ self::assertSame('https://example.com/bar', $events[0]->body->to);
+ }
+
+ public function testCaptureRollbarItem(): void
+ {
+ // Test exclude Rollbar captured item.
+ $telemeter = new Telemeter(includeItemsInTelemetry: false);
+ self::assertNull($telemeter->captureRollbarItem(Level::INFO, 'foo'));
+
+ // Test exclude ignored Rollbar item.
+ $telemeter = new Telemeter(includeIgnoredItemsInTelemetry: false);
+ self::assertNull($telemeter->captureRollbarItem(Level::INFO, 'foo', ignored: true));
+
+ // Test non-ignored Rollbar item not excluded.
+ $event = $telemeter->captureRollbarItem(Level::INFO, 'bar');
+ self::assertSame($event->body->message, 'bar');
+
+ // Test a Throwable in the $context['exception'] is treated as an error.
+ $telemeter = new Telemeter();
+ $error = new Exception('oops');
+ $event = $telemeter->captureRollbarItem(Level::DEBUG, 'baz', context: ['exception' => $error]);
+ self::assertSame(DataType::ERROR, $event->type);
+ self::assertSame('oops', $event->body->extra['error_message']);
+ self::assertSame('baz', $event->body->message);
+
+ // Test a Throwable $message is treated as an error
+ $event = $telemeter->captureRollbarItem(Level::DEBUG, $error);
+ self::assertSame(DataType::ERROR, $event->type);
+ self::assertSame('oops', $event->body->message);
+
+ // Test telemetry type dynamically determined from the Rollbar level.
+ self::assertSame(DataType::ERROR, $telemeter->captureRollbarItem(Level::EMERGENCY, 'foo')->type);
+ self::assertSame(DataType::ERROR, $telemeter->captureRollbarItem(Level::ALERT, 'foo')->type);
+ self::assertSame(DataType::ERROR, $telemeter->captureRollbarItem(Level::CRITICAL, 'foo')->type);
+ self::assertSame(DataType::ERROR, $telemeter->captureRollbarItem(Level::ERROR, 'foo')->type);
+ self::assertSame(DataType::ERROR, $telemeter->captureRollbarItem(Level::WARNING, 'foo')->type);
+ self::assertSame(DataType::LOG, $telemeter->captureRollbarItem(Level::NOTICE, 'foo')->type);
+ self::assertSame(DataType::LOG, $telemeter->captureRollbarItem(Level::INFO, 'foo')->type);
+ self::assertSame(DataType::MANUAL, $telemeter->captureRollbarItem(Level::DEBUG, 'foo')->type);
+ self::assertSame(DataType::MANUAL, $telemeter->captureRollbarItem('bar', 'foo')->type);
+
+ // Test telemetry level dynamically determined from the Rollbar level.
+ self::assertSame('critical', $telemeter->captureRollbarItem(Level::EMERGENCY, 'foo')->level);
+ self::assertSame('critical', $telemeter->captureRollbarItem(Level::ALERT, 'foo')->level);
+ self::assertSame('critical', $telemeter->captureRollbarItem(Level::CRITICAL, 'foo')->level);
+ self::assertSame('error', $telemeter->captureRollbarItem(Level::ERROR, 'foo')->level);
+ self::assertSame('warning', $telemeter->captureRollbarItem(Level::WARNING, 'foo')->level);
+ self::assertSame('info', $telemeter->captureRollbarItem(Level::NOTICE, 'foo')->level);
+ self::assertSame('info', $telemeter->captureRollbarItem(Level::INFO, 'foo')->level);
+ self::assertSame('debug', $telemeter->captureRollbarItem(Level::DEBUG, 'foo')->level);
+ self::assertSame('info', $telemeter->captureRollbarItem('bar', 'foo')->level);
+ }
+
+ public function testGetMaxQueueSize(): void
+ {
+ $telemeter = new Telemeter(42);
+ self::assertSame(42, $telemeter->getMaxQueueSize());
+ }
+
+ public function testSetMaxQueueSize(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertSame(100, $telemeter->getMaxQueueSize());
+
+ $telemeter->setMaxQueueSize(10);
+ self::assertSame(10, $telemeter->getMaxQueueSize());
+
+ foreach (range(1, 10) as $i) {
+ $telemeter->captureLog('foo' . $i);
+ }
+
+ self::assertSame(10, $telemeter->getQueueSize());
+
+ $telemeter->setMaxQueueSize(5);
+ self::assertSame(5, $telemeter->getMaxQueueSize());
+ self::assertSame(5, $telemeter->getQueueSize());
+ }
+
+ public function testGetQueueSize(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertSame(0, $telemeter->getQueueSize());
+
+ $telemeter->captureLog('foo');
+ self::assertSame(1, $telemeter->getQueueSize());
+
+ $telemeter->captureLog('bar');
+ self::assertSame(2, $telemeter->getQueueSize());
+ }
+
+ public function testClearQueue(): void
+ {
+ $telemeter = new Telemeter();
+ $telemeter->captureLog('foo');
+ $telemeter->captureLog('bar');
+
+ self::assertSame(2, $telemeter->getQueueSize());
+ $telemeter->clearQueue();
+ self::assertSame(0, $telemeter->getQueueSize());
+ }
+
+ public function testShouldIncludeItemsInTelemetry(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertTrue($telemeter->shouldIncludeItemsInTelemetry());
+
+ $telemeter = new Telemeter(includeItemsInTelemetry: false);
+ self::assertFalse($telemeter->shouldIncludeItemsInTelemetry());
+ }
+
+ public function testSetIncludeItemsInTelemetry(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertTrue($telemeter->shouldIncludeItemsInTelemetry());
+
+ $telemeter->setIncludeItemsInTelemetry(false);
+ self::assertFalse($telemeter->shouldIncludeItemsInTelemetry());
+
+ $telemeter->setIncludeItemsInTelemetry(true);
+ self::assertTrue($telemeter->shouldIncludeItemsInTelemetry());
+ }
+
+ public function testGetFilter(): void
+ {
+ $filter = new TestTelemetryFilter();
+ $filter->includeFunction = Closure::fromCallable(function (TelemetryEvent $event, int $queueSize): bool {
+ return $event->body->message !== 'foo';
+ });
+ $telemeter = new Telemeter(filter: $filter);
+ self::assertSame($filter, $telemeter->getFilter());
+ }
+
+ public function testSetFilter(): void
+ {
+ $filter = new TestTelemetryFilter();
+ $filter->includeFunction = Closure::fromCallable(function (TelemetryEvent $event, int $queueSize): bool {
+ return $event->body->message !== 'foo';
+ });
+ $telemeter = new Telemeter();
+ self::assertNull($telemeter->getFilter());
+
+ $telemeter->setFilter($filter);
+ self::assertSame($filter, $telemeter->getFilter());
+ }
+
+ public function testShouldIncludeIgnoredItemsInTelemetry(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertFalse($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+
+ $telemeter = new Telemeter(includeIgnoredItemsInTelemetry: false);
+ self::assertFalse($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+
+ $telemeter = new Telemeter(includeIgnoredItemsInTelemetry: true);
+ self::assertTrue($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+ }
+
+ public function testSetIncludeIgnoredItemsInTelemetry(): void
+ {
+ $telemeter = new Telemeter();
+ self::assertFalse($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+
+ $telemeter->setIncludeIgnoredItemsInTelemetry(true);
+ self::assertTrue($telemeter->shouldIncludeIgnoredItemsInTelemetry());
+ }
+
+ public function testSetFilterWithFilterableEvents(): void
+ {
+ $filter = new TestTelemetryFilter();
+ $filter->includeFunction = Closure::fromCallable(function (TelemetryEvent $event, int $queueSize): bool {
+ return $event->body->message !== 'foo';
+ });
+ $telemeter = new Telemeter();
+
+ $event1 = new TelemetryEvent(DataType::LOG, 'info', ['message' => 'foo']);
+ $event2 = new TelemetryEvent(DataType::LOG, 'info', new TelemetryBody('bar'));
+
+ $telemeter->push($event1);
+ $telemeter->push($event2);
+
+ $telemeter->setFilter($filter);
+
+ $events = $telemeter->copyEvents();
+ self::assertSame(1, count($events));
+ self::assertSame($event2, $events[0]);
+ }
+}
diff --git a/tests/TestHelpers/TestTelemetryFilter.php b/tests/TestHelpers/TestTelemetryFilter.php
new file mode 100644
index 00000000..73ab5a77
--- /dev/null
+++ b/tests/TestHelpers/TestTelemetryFilter.php
@@ -0,0 +1,54 @@
+config = $config;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function include(TelemetryEvent $event, int $queueSize): bool
+ {
+ return $this->includeFunction?->call($this, $event, $queueSize) ?? false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function includeRollbarItem(
+ string $level,
+ Stringable|string $message,
+ array $context = [],
+ bool $ignored = false,
+ ): bool {
+ return $this->includeRollbarItemFunction?->call($this, $level, $message, $context, $ignored) ?? false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function filterOnRead(): bool
+ {
+ return $this->filterOnRead;
+ }
+}
diff --git a/tests/Truncation/TelemetryStrategyTest.php b/tests/Truncation/TelemetryStrategyTest.php
new file mode 100644
index 00000000..d0868431
--- /dev/null
+++ b/tests/Truncation/TelemetryStrategyTest.php
@@ -0,0 +1,97 @@
+ $this->getTestAccessToken(),
+ 'environment' => 'test',
+ ]);
+ }
+
+ /**
+ * @dataProvider executeProvider
+ */
+ public function testExecute(array $data, array $expected): void
+ {
+ $config = new Config(['access_token' => $this->getTestAccessToken()]);
+ $truncation = new Truncation($config);
+
+ $strategy = new TelemetryStrategy($truncation);
+
+ $data = new EncodedPayload($data);
+ $data->encode();
+
+ $result = $strategy->execute($data);
+
+ $this->assertEquals($expected, $result->data());
+ }
+
+ /**
+ * @return array
+ */
+ public static function executeProvider(): array
+ {
+ return [
+ 'nothing to truncate: no telemetry data' => [
+ [
+ 'data' => [
+ 'body' => [],
+ ],
+ ],
+ [
+ 'data' => [
+ 'body' => [],
+ ],
+ ],
+ ],
+ 'nothing to truncate: telemetry in range' => [
+ [
+ 'data' => [
+ 'body' => [
+ 'telemetry' => range(1, 6),
+ ],
+ ],
+ ],
+ [
+ 'data' => [
+ 'body' => [
+ 'telemetry' => range(1, 6),
+ ],
+ ],
+ ],
+ ],
+ 'truncate middle: telemetry too long' => [
+ [
+ 'data' => [
+ 'body' => [
+ 'telemetry' => range(1, TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE * 2 + 1),
+ ],
+ ],
+ ],
+ [
+ 'data' => [
+ 'body' => [
+ 'telemetry' => array_merge(
+ range(1, TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE),
+ range(
+ TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE + 2,
+ TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE * 2 + 1
+ ),
+ ),
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
+}