Skip to content

Commit 27c10bc

Browse files
committed
Added escape for export formats.
1 parent 0862ed6 commit 27c10bc

File tree

5 files changed

+27
-17
lines changed

5 files changed

+27
-17
lines changed

pyprometheus/metrics.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
"""
1313

1414
from pyprometheus.const import TYPES
15+
from pyprometheus.utils import escape_str
1516
from pyprometheus.values import (MetricValue, GaugeValue,
1617
CounterValue, SummaryValue,
1718
HistogramValue)
19+
20+
1821
class BaseMetric(object):
1922

2023
value_class = MetricValue
@@ -93,9 +96,9 @@ def text_export_header(self):
9396
"""
9497
return "\n".join(["# HELP {name} {doc}",
9598
"# TYPE {name} {metric_type}"]).format(
96-
name=self.name,
97-
doc=self.doc,
98-
metric_type=self.TYPE)
99+
name=escape_str(self.name),
100+
doc=escape_str(self.doc),
101+
metric_type=self.TYPE)
99102

100103
def build_samples(self, items):
101104
"""Build samples from objects

pyprometheus/utils/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ def import_storage(path):
2323
return sys.modules[path]
2424

2525

26+
def escape_str(value):
27+
return value.replace("\\", r"\\").replace("\n", r"\n").replace("\"", r"\"")
28+
29+
2630
def format_binary(value):
27-
return ':'.join("{0}>{1}".format(i, x.encode('hex')) for i, x in enumerate(value))
31+
return ":".join("{0}>{1}".format(i, x.encode("hex")) for i, x in enumerate(value))
2832

2933

3034
def format_char_positions(value):

pyprometheus/utils/exposition.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def registry_to_text(registry):
2424
output.append(collector.text_export_header)
2525
for sample in samples:
2626
output.append(sample.export_str)
27-
output.append('')
28-
return '\n'.join(output)
27+
output.append("")
28+
return "\n".join(output)
2929

3030

3131
def write_to_textfile(registry, path):
@@ -34,7 +34,7 @@ def write_to_textfile(registry, path):
3434

3535
tmp_filename = "{0}.{1}.tmp".format(path, os.getpid())
3636

37-
with open(tmp_filename, 'wb') as f:
37+
with open(tmp_filename, "wb") as f:
3838
f.write(registry_to_text(registry))
3939

4040
os.rename(tmp_filename, path)

pyprometheus/values.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import time
1515

16+
from pyprometheus.utils import escape_str
1617
from pyprometheus.const import TYPES
1718
from pyprometheus.managers import TimerManager, InprogressTrackerManager, GaugeTimerManager
1819

@@ -76,12 +77,12 @@ def get(self):
7677

7778
@property
7879
def value(self):
79-
raise RuntimeError("Metric value")
80+
return self.get()
8081

8182
@property
8283
def export_str(self):
8384
return "{name}{postfix}{{{labels}}} {value} {timestamp}".format(
84-
name=self._metric.name, postfix=self.POSTFIX,
85+
name=escape_str(self._metric.name), postfix=self.POSTFIX,
8586
labels=self.export_labels, timestamp=int(time.time() * 1000), value=float(self.value))
8687

8788
@property
@@ -92,7 +93,7 @@ def export_labels(self):
9293
def format_export_label(self, label):
9394
if label == "bucket":
9495
return "le"
95-
return label
96+
return escape_str(label)
9697

9798
def format_export_value(self, value):
9899
if value == float("inf"):
@@ -101,7 +102,7 @@ def format_export_value(self, value):
101102
return "-Inf"
102103
# elif math.isnan(value):
103104
# return "NaN"
104-
return value
105+
return escape_str(str(value))
105106

106107

107108
class GaugeValue(MetricValue):

tests/test_metrics.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
def test_base_metric(storage_cls):
1414
storage = storage_cls()
1515
registry = BaseRegistry(storage=storage)
16-
metric_name = "test_base_metric"
17-
metric = BaseMetric(metric_name, "test_base_metric doc", ("label1", "label2"), registry=registry)
16+
metric_name = "test_base_metric\x00\\"
17+
metric = BaseMetric(metric_name, "test_base_metric doc \u4500", ("label1", "label2"), registry=registry)
1818

1919
assert registry.is_registered(metric)
20-
assert repr(metric) == "<BaseMetric[test_base_metric]: 0 samples>"
20+
assert repr(metric) == "<BaseMetric[test_base_metric\x00\\]: 0 samples>"
2121

2222
with pytest.raises(RuntimeError) as exc_info:
2323
registry.register(metric)
@@ -29,16 +29,18 @@ def test_base_metric(storage_cls):
2929

3030
assert str(exc_info.value) == u"Collector {0} already registered.".format(metric.uid)
3131

32-
labels = metric.labels({"label1": "label1_value", "label2": "label2_value"})
32+
labels = metric.labels({"label1\\x\n\"": "label1_value\\x\n\"", "label2": "label2_value\\x\n\""})
3333

3434
assert isinstance(labels, MetricValue)
3535

3636
labels.inc(1)
3737

3838
assert labels.get() == 1
3939

40-
assert metric.text_export_header == "\n".join(["# HELP test_base_metric test_base_metric doc",
41-
"# TYPE test_base_metric untyped"])
40+
assert metric.text_export_header == "\n".join(["# HELP test_base_metric\x00\\\\ test_base_metric doc \\\\u4500",
41+
"# TYPE test_base_metric\x00\\\\ untyped"])
42+
43+
assert labels.export_str.split(" ")[:2] == 'test_base_metric\x00\\\\{label1\\\\x\\n\\"="label1_value\\\\x\\n\\"", label2="label2_value\\\\x\\n\\""} 1.0'.split(" ")[:2] # noqa
4244

4345

4446
@pytest.mark.parametrize("storage_cls", [LocalMemoryStorage, UWSGIStorage])

0 commit comments

Comments
 (0)