From 27c10bc62ca3fd2546714759b48ef9615ec5483e Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 4 Apr 2017 11:26:03 +0000 Subject: [PATCH] Added escape for export formats. --- pyprometheus/metrics.py | 9 ++++++--- pyprometheus/utils/__init__.py | 6 +++++- pyprometheus/utils/exposition.py | 6 +++--- pyprometheus/values.py | 9 +++++---- tests/test_metrics.py | 14 ++++++++------ 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/pyprometheus/metrics.py b/pyprometheus/metrics.py index dd8022e..c3584e0 100644 --- a/pyprometheus/metrics.py +++ b/pyprometheus/metrics.py @@ -12,9 +12,12 @@ """ from pyprometheus.const import TYPES +from pyprometheus.utils import escape_str from pyprometheus.values import (MetricValue, GaugeValue, CounterValue, SummaryValue, HistogramValue) + + class BaseMetric(object): value_class = MetricValue @@ -93,9 +96,9 @@ def text_export_header(self): """ return "\n".join(["# HELP {name} {doc}", "# TYPE {name} {metric_type}"]).format( - name=self.name, - doc=self.doc, - metric_type=self.TYPE) + name=escape_str(self.name), + doc=escape_str(self.doc), + metric_type=self.TYPE) def build_samples(self, items): """Build samples from objects diff --git a/pyprometheus/utils/__init__.py b/pyprometheus/utils/__init__.py index 516616b..1d8d390 100644 --- a/pyprometheus/utils/__init__.py +++ b/pyprometheus/utils/__init__.py @@ -23,8 +23,12 @@ def import_storage(path): return sys.modules[path] +def escape_str(value): + return value.replace("\\", r"\\").replace("\n", r"\n").replace("\"", r"\"") + + def format_binary(value): - return ':'.join("{0}>{1}".format(i, x.encode('hex')) for i, x in enumerate(value)) + return ":".join("{0}>{1}".format(i, x.encode("hex")) for i, x in enumerate(value)) def format_char_positions(value): diff --git a/pyprometheus/utils/exposition.py b/pyprometheus/utils/exposition.py index 633a131..1f5cd48 100644 --- a/pyprometheus/utils/exposition.py +++ b/pyprometheus/utils/exposition.py @@ -24,8 +24,8 @@ def registry_to_text(registry): output.append(collector.text_export_header) for sample in samples: output.append(sample.export_str) - output.append('') - return '\n'.join(output) + output.append("") + return "\n".join(output) def write_to_textfile(registry, path): @@ -34,7 +34,7 @@ def write_to_textfile(registry, path): tmp_filename = "{0}.{1}.tmp".format(path, os.getpid()) - with open(tmp_filename, 'wb') as f: + with open(tmp_filename, "wb") as f: f.write(registry_to_text(registry)) os.rename(tmp_filename, path) diff --git a/pyprometheus/values.py b/pyprometheus/values.py index 9b8f3dc..5e7bd01 100644 --- a/pyprometheus/values.py +++ b/pyprometheus/values.py @@ -13,6 +13,7 @@ import time +from pyprometheus.utils import escape_str from pyprometheus.const import TYPES from pyprometheus.managers import TimerManager, InprogressTrackerManager, GaugeTimerManager @@ -76,12 +77,12 @@ def get(self): @property def value(self): - raise RuntimeError("Metric value") + return self.get() @property def export_str(self): return "{name}{postfix}{{{labels}}} {value} {timestamp}".format( - name=self._metric.name, postfix=self.POSTFIX, + name=escape_str(self._metric.name), postfix=self.POSTFIX, labels=self.export_labels, timestamp=int(time.time() * 1000), value=float(self.value)) @property @@ -92,7 +93,7 @@ def export_labels(self): def format_export_label(self, label): if label == "bucket": return "le" - return label + return escape_str(label) def format_export_value(self, value): if value == float("inf"): @@ -101,7 +102,7 @@ def format_export_value(self, value): return "-Inf" # elif math.isnan(value): # return "NaN" - return value + return escape_str(str(value)) class GaugeValue(MetricValue): diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 89fbb41..ee2ebfa 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -13,11 +13,11 @@ def test_base_metric(storage_cls): storage = storage_cls() registry = BaseRegistry(storage=storage) - metric_name = "test_base_metric" - metric = BaseMetric(metric_name, "test_base_metric doc", ("label1", "label2"), registry=registry) + metric_name = "test_base_metric\x00\\" + metric = BaseMetric(metric_name, "test_base_metric doc \u4500", ("label1", "label2"), registry=registry) assert registry.is_registered(metric) - assert repr(metric) == "" + assert repr(metric) == "" with pytest.raises(RuntimeError) as exc_info: registry.register(metric) @@ -29,7 +29,7 @@ def test_base_metric(storage_cls): assert str(exc_info.value) == u"Collector {0} already registered.".format(metric.uid) - labels = metric.labels({"label1": "label1_value", "label2": "label2_value"}) + labels = metric.labels({"label1\\x\n\"": "label1_value\\x\n\"", "label2": "label2_value\\x\n\""}) assert isinstance(labels, MetricValue) @@ -37,8 +37,10 @@ def test_base_metric(storage_cls): assert labels.get() == 1 - assert metric.text_export_header == "\n".join(["# HELP test_base_metric test_base_metric doc", - "# TYPE test_base_metric untyped"]) + assert metric.text_export_header == "\n".join(["# HELP test_base_metric\x00\\\\ test_base_metric doc \\\\u4500", + "# TYPE test_base_metric\x00\\\\ untyped"]) + + 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 @pytest.mark.parametrize("storage_cls", [LocalMemoryStorage, UWSGIStorage])