Skip to content

Commit

Permalink
[RenderTextFormat] Allow value errors to be rendered as comments.
Browse files Browse the repository at this point in the history
Redis and RedisNg allow samples with mismatching labels to be stored, which could cause ValueError to be thrown when
rendering. Rendering would fail as a result, which is not ideal.

- Change render() to accept an optional $silent parameter. When set to true, render the errors as comments instead of
  throwing them and failing the whole operation.

Signed-off-by: Joe Cai <[email protected]>
  • Loading branch information
joec4i committed Dec 20, 2023
1 parent c7c174b commit dc0e6ce
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
19 changes: 17 additions & 2 deletions src/Prometheus/RenderTextFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
namespace Prometheus;

use RuntimeException;
use Throwable;

class RenderTextFormat implements RendererInterface
{
const MIME_TYPE = 'text/plain; version=0.0.4';

/**
* @param MetricFamilySamples[] $metrics
* @param bool $silent If true, render value errors as comments instead of throwing them.
* @return string
*/
public function render(array $metrics): string
public function render(array $metrics, bool $silent = false): string
{
usort($metrics, function (MetricFamilySamples $a, MetricFamilySamples $b): int {
return strcmp($a->getName(), $b->getName());
Expand All @@ -25,7 +27,20 @@ public function render(array $metrics): string
$lines[] = "# HELP " . $metric->getName() . " {$metric->getHelp()}";
$lines[] = "# TYPE " . $metric->getName() . " {$metric->getType()}";
foreach ($metric->getSamples() as $sample) {
$lines[] = $this->renderSample($metric, $sample);
try {
$lines[] = $this->renderSample($metric, $sample);
} catch (Throwable $e) {
// Redis and RedisNg allow samples with mismatching labels to be stored, which could cause ValueError
// to be thrown when rendering. If this happens, users can decide whether to ignore the error or not.
// These errors will normally disappear after the storage is flushed.
if (!$silent) {
throw $e;
}

$lines[] = "# Error: {$e->getMessage()}";
$lines[] = "# Labels: " . json_encode(array_merge($metric->getLabelNames(), $sample->getLabelNames()));
$lines[] = "# Values: " . json_encode(array_merge($sample->getLabelValues()));
}
}
}
return implode("\n", $lines) . "\n";
Expand Down
56 changes: 56 additions & 0 deletions tests/Test/Prometheus/RenderTextFormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Prometheus\RenderTextFormat;
use PHPUnit\Framework\TestCase;
use Prometheus\Storage\InMemory;
use Prometheus\Storage\Redis;
use ValueError;

class RenderTextFormatTest extends TestCase
{
Expand Down Expand Up @@ -70,4 +72,58 @@ private function getExpectedOutput(): string
TEXTPLAIN;
}

public function testValueErrorThrownWithInvalidSamples(): void
{
$namespace = 'foo';
$counter = 'bar';
$storage = new Redis(['host' => REDIS_HOST]);
$storage->wipeStorage();

$registry = new CollectorRegistry($storage, false);
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2'])
->inc(['bob', 'alice']);

// Reload the registry with an updated counter config
$registry = new CollectorRegistry($storage, false);
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2', 'label3'])
->inc(['bob', 'alice', 'eve']);

$this->expectException(ValueError::class);

$renderer = new RenderTextFormat();
$renderer->render($registry->getMetricFamilySamples());

}

public function testOutputWithInvalidSamplesSkipped(): void
{
$namespace = 'foo';
$counter = 'bar';
$storage = new Redis(['host' => REDIS_HOST]);
$storage->wipeStorage();

$registry = new CollectorRegistry($storage, false);
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2'])
->inc(['bob', 'alice']);

// Reload the registry with an updated counter config
$registry = new CollectorRegistry($storage, false);
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2', 'label3'])
->inc(['bob', 'alice', 'eve']);

$expectedOutput = '
# HELP foo_bar counter-help-text
# TYPE foo_bar counter
foo_bar{label1="bob",label2="alice"} 1
# Error: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements
# Labels: ["label1","label2"]
# Values: ["bob","alice","eve"]
';

$renderer = new RenderTextFormat();
$output = $renderer->render($registry->getMetricFamilySamples(), true);

self::assertSame(trim($expectedOutput), trim($output));
}
}

0 comments on commit dc0e6ce

Please sign in to comment.