Skip to content

Commit dc0e6ce

Browse files
committed
[RenderTextFormat] Allow value errors to be rendered as comments.
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 <joe.cai@bigcommerce.com>
1 parent c7c174b commit dc0e6ce

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

src/Prometheus/RenderTextFormat.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
namespace Prometheus;
66

77
use RuntimeException;
8+
use Throwable;
89

910
class RenderTextFormat implements RendererInterface
1011
{
1112
const MIME_TYPE = 'text/plain; version=0.0.4';
1213

1314
/**
1415
* @param MetricFamilySamples[] $metrics
16+
* @param bool $silent If true, render value errors as comments instead of throwing them.
1517
* @return string
1618
*/
17-
public function render(array $metrics): string
19+
public function render(array $metrics, bool $silent = false): string
1820
{
1921
usort($metrics, function (MetricFamilySamples $a, MetricFamilySamples $b): int {
2022
return strcmp($a->getName(), $b->getName());
@@ -25,7 +27,20 @@ public function render(array $metrics): string
2527
$lines[] = "# HELP " . $metric->getName() . " {$metric->getHelp()}";
2628
$lines[] = "# TYPE " . $metric->getName() . " {$metric->getType()}";
2729
foreach ($metric->getSamples() as $sample) {
28-
$lines[] = $this->renderSample($metric, $sample);
30+
try {
31+
$lines[] = $this->renderSample($metric, $sample);
32+
} catch (Throwable $e) {
33+
// Redis and RedisNg allow samples with mismatching labels to be stored, which could cause ValueError
34+
// to be thrown when rendering. If this happens, users can decide whether to ignore the error or not.
35+
// These errors will normally disappear after the storage is flushed.
36+
if (!$silent) {
37+
throw $e;
38+
}
39+
40+
$lines[] = "# Error: {$e->getMessage()}";
41+
$lines[] = "# Labels: " . json_encode(array_merge($metric->getLabelNames(), $sample->getLabelNames()));
42+
$lines[] = "# Values: " . json_encode(array_merge($sample->getLabelValues()));
43+
}
2944
}
3045
}
3146
return implode("\n", $lines) . "\n";

tests/Test/Prometheus/RenderTextFormatTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Prometheus\RenderTextFormat;
1111
use PHPUnit\Framework\TestCase;
1212
use Prometheus\Storage\InMemory;
13+
use Prometheus\Storage\Redis;
14+
use ValueError;
1315

1416
class RenderTextFormatTest extends TestCase
1517
{
@@ -70,4 +72,58 @@ private function getExpectedOutput(): string
7072
7173
TEXTPLAIN;
7274
}
75+
76+
public function testValueErrorThrownWithInvalidSamples(): void
77+
{
78+
$namespace = 'foo';
79+
$counter = 'bar';
80+
$storage = new Redis(['host' => REDIS_HOST]);
81+
$storage->wipeStorage();
82+
83+
$registry = new CollectorRegistry($storage, false);
84+
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2'])
85+
->inc(['bob', 'alice']);
86+
87+
// Reload the registry with an updated counter config
88+
$registry = new CollectorRegistry($storage, false);
89+
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2', 'label3'])
90+
->inc(['bob', 'alice', 'eve']);
91+
92+
$this->expectException(ValueError::class);
93+
94+
$renderer = new RenderTextFormat();
95+
$renderer->render($registry->getMetricFamilySamples());
96+
97+
}
98+
99+
public function testOutputWithInvalidSamplesSkipped(): void
100+
{
101+
$namespace = 'foo';
102+
$counter = 'bar';
103+
$storage = new Redis(['host' => REDIS_HOST]);
104+
$storage->wipeStorage();
105+
106+
$registry = new CollectorRegistry($storage, false);
107+
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2'])
108+
->inc(['bob', 'alice']);
109+
110+
// Reload the registry with an updated counter config
111+
$registry = new CollectorRegistry($storage, false);
112+
$registry->registerCounter($namespace, $counter, 'counter-help-text', ['label1', 'label2', 'label3'])
113+
->inc(['bob', 'alice', 'eve']);
114+
115+
$expectedOutput = '
116+
# HELP foo_bar counter-help-text
117+
# TYPE foo_bar counter
118+
foo_bar{label1="bob",label2="alice"} 1
119+
# Error: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements
120+
# Labels: ["label1","label2"]
121+
# Values: ["bob","alice","eve"]
122+
';
123+
124+
$renderer = new RenderTextFormat();
125+
$output = $renderer->render($registry->getMetricFamilySamples(), true);
126+
127+
self::assertSame(trim($expectedOutput), trim($output));
128+
}
73129
}

0 commit comments

Comments
 (0)