Skip to content
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

GitHub Issue #38: truncate payload #167

Merged
merged 16 commits into from
May 16, 2017
Merged
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
46 changes: 46 additions & 0 deletions src/DataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@

class DataBuilder implements DataBuilderInterface
{
const MAX_PAYLOAD_SIZE = 524288; // 512 * 1024

protected static $truncationStrategies = array(
"Rollbar\Truncation\RawStrategy",
"Rollbar\Truncation\FramesStrategy",
"Rollbar\Truncation\StringsStrategy",
"Rollbar\Truncation\MinBodyStrategy"
);

protected static $defaults;

protected $environment;
Expand Down Expand Up @@ -849,4 +858,41 @@ protected static function uuid4()
mt_rand(0, 0xffff)
);
}

/**
* Applies truncation strategies in order to keep the payload size under
* configured limit.
*
* @param array $payload
* @param string $strategy
*
* @return array
*/
public function truncate(array $payload)
{

foreach (static::$truncationStrategies as $strategy) {
if (!$this->needsTruncating($payload)) {
break;
}

$strategy = new $strategy($this);

$payload = $strategy->execute($payload);
}

return $payload;
}

/**
* Check if the payload is too big to be sent
*
* @param array $payload
*
* @return boolean
*/
public function needsTruncating(array $payload)
{
return strlen(json_encode($payload)) > self::MAX_PAYLOAD_SIZE;
}
}
14 changes: 12 additions & 2 deletions src/RollbarLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ public function log($level, $toLog, array $context = array())
if ($this->config->checkIgnored($payload, $accessToken, $toLog, $isUncaught)) {
$response = new Response(0, "Ignored");
} else {
$scrubbed = $this->scrub($payload);
$response = $this->config->send($scrubbed, $accessToken);
$toSend = $this->scrub($payload);
$toSend = $this->truncate($toSend);
$response = $this->config->send($toSend, $accessToken);
}

$this->handleResponse($payload, $response);
Expand Down Expand Up @@ -80,4 +81,13 @@ protected function scrub(Payload $payload)
$serialized['data'] = $this->config->getDataBuilder()->scrub($serialized['data']);
return $serialized;
}

/**
* @param array $payload
* @return array
*/
protected function truncate(array $payload)
{
return $this->config->getDataBuilder()->truncate($payload);
}
}
16 changes: 16 additions & 0 deletions src/Truncation/AbstractStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php namespace Rollbar\Truncation;

class AbstractStrategy implements IStrategy
{
protected $dataBuilder;

public function __construct($dataBuilder)
{
$this->dataBuilder = $dataBuilder;
}

public function execute(array $payload)
{
return $payload;
}
}
32 changes: 32 additions & 0 deletions src/Truncation/FramesStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php namespace Rollbar\Truncation;

class FramesStrategy extends AbstractStrategy
{

const FRAMES_OPTIMIZATION_RANGE = 75;

public function execute(array $payload)
{
$frames = array();

if (isset($payload['data']['body']['trace_chain']['frames'])) {
$frames = $payload['data']['body']['trace_chain']['frames'];
} elseif (isset($payload['data']['body']['trace']['frames'])) {
$frames = $payload['data']['body']['trace']['frames'];
}

return $this->selectFrames($frames);
}

public function selectFrames($frames, $range = self::FRAMES_OPTIMIZATION_RANGE)
{
if (count($frames) <= $range * 2) {
return $frames;
}

return array_merge(
array_splice($frames, 0, $range),
array_splice($frames, -$range, $range)
);
}
}
11 changes: 11 additions & 0 deletions src/Truncation/IStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php namespace Rollbar\Truncation;

interface IStrategy
{

/**
* @param array $payload
* @return array
*/
public function execute(array $payload);
}
45 changes: 45 additions & 0 deletions src/Truncation/MinBodyStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php namespace Rollbar\Truncation;

class MinBodyStrategy extends AbstractStrategy
{

const EXCEPTION_MESSAGE_LIMIT = 256;
const EXCEPTION_FRAMES_RANGE = 1;

public function execute(array $payload)
{

$traceData = null;

if (isset($payload['data']['body']['trace'])) {
$traceData = &$payload['data']['body']['trace'];
} elseif (isset($payload['data']['body']['trace_chain'])) {
$traceData = &$payload['data']['body']['trace_chain'];
}

/**
* Delete exception description
*/
unset($traceData['exception']['description']);

/**
* Truncate exception message
*/
$traceData['exception']['message'] = substr(
$traceData['exception']['message'],
0,
static::EXCEPTION_MESSAGE_LIMIT
);

/**
* Limit trace frames
*/
$framesStrategy = new FramesStrategy($this->dataBuilder);
$traceData['frames'] = $framesStrategy->selectFrames(
$traceData['frames'],
static::EXCEPTION_FRAMES_RANGE
);

return $payload;
}
}
9 changes: 9 additions & 0 deletions src/Truncation/RawStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php namespace Rollbar\Truncation;

class RawStrategy extends AbstractStrategy
{
public function execute(array $payload)
{
return $payload;
}
}
28 changes: 28 additions & 0 deletions src/Truncation/StringsStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php namespace Rollbar\Truncation;

class StringsStrategy extends AbstractStrategy
{

public static function getThresholds()
{
return array(1024, 512, 256);
}

public function execute(array $payload)
{
foreach (static::getThresholds() as $threshold) {
if (!$this->dataBuilder->needsTruncating($payload)) {
break;
}

array_walk_recursive($payload, function (&$value) use ($threshold) {

if (is_string($value) && strlen($value) > $threshold) {
$value = substr($value, 0, $threshold);
}
});
}

return $payload;
}
}
32 changes: 32 additions & 0 deletions tests/DataBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rollbar\Payload\Level;
use Rollbar\TestHelpers\MockPhpStream;
use Rollbar\Truncation\FramesStrategy;

class DataBuilderTest extends \PHPUnit_Framework_TestCase
{
Expand Down Expand Up @@ -527,4 +528,35 @@ public function testSetRequestBody()

stream_wrapper_restore("php");
}

/**
* @dataProvider truncateProvider
*/
public function testTruncate($data)
{
$result = $this->dataBuilder->truncate($data);

$size = strlen(json_encode($result));

$this->assertTrue(
$size <= DataBuilder::MAX_PAYLOAD_SIZE,
"Truncation failed. Payload size exceeds MAX_PAYLOAD_SIZE."
);
}

public function truncateProvider()
{

$stringsTest = new Truncation\StringsStrategyTest();
$framesTest = new Truncation\FramesStrategyTest();
$minBodyTest = new Truncation\MinBodyStrategyTest();

$data = array_merge(
$stringsTest->executeProvider(),
$framesTest->executeProvider(),
$minBodyTest->executeProvider()
);

return $data;
}
}
82 changes: 82 additions & 0 deletions tests/Truncation/FramesStrategyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Rollbar\Truncation;

use Rollbar\DataBuilder;

class FramesStrategyTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider executeProvider
*/
public function testExecute($data, $expected)
{
$dataBuilder = new DataBuilder(array(
'accessToken' => 'abcd1234efef5678abcd1234567890be',
'environment' => 'tests'
));

$strategy = new FramesStrategy($dataBuilder);
$result = $strategy->execute($data);

$this->assertEquals($expected, $result);
}

public function executeProvider()
{
$data = array(
'nothing to truncate using trace key' => array(
array('data' => array('body' =>
array('trace' => array('frames' => range(1, 6)))
)),
range(1, 6)
),
'nothing to truncate using trace_chain key' => array(
array('data' => array('body' =>
array('trace_chain' => array('frames' => range(1, 6)))
)),
range(1, 6)
),
'truncate middle using trace key' => array(
array('data' => array('body' =>
array(
'trace' => array(
'frames' => range(
1,
FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1
)
)
)
)),
array_merge(
range(1, FramesStrategy::FRAMES_OPTIMIZATION_RANGE),
range(
FramesStrategy::FRAMES_OPTIMIZATION_RANGE + 2,
FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1
)
)
),
'truncate middle using trace_chain key' => array(
array('data' => array('body' =>
array(
'trace_chain' => array(
'frames' => range(
1,
FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1
)
)
)
)),
array_merge(
range(1, FramesStrategy::FRAMES_OPTIMIZATION_RANGE),
range(
FramesStrategy::FRAMES_OPTIMIZATION_RANGE + 2,
FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1
)
)
)
);

return $data;
}
}
Loading