FIX: escape all label values correctly

approved
#1

FIX: escape all label values correctly

This fixes #66

Previously if a label name had " in it or newline or \ we would corrupt the prometheus payload

diff --git a/CHANGELOG b/CHANGELOG
index d8f644a..38db271 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+0.4.7 - 08-04-2019
+
+- Fix: collector was not escaping " \ and \n correctly. This could lead
+  to a corrupt payload in some cases.
+
 0.4.6 - 02-04-2019
 
 - Feature: Allow resetting a counter
@@ -11,7 +16,7 @@
 
 0.4.4 - 13-02-2019
 
-- Feature: add support for local metric collection without using HTTP 
+- Feature: add support for local metric collection without using HTTP
 
 0.4.3 - 11-02-2019
 
diff --git a/lib/prometheus_exporter/metric/base.rb b/lib/prometheus_exporter/metric/base.rb
index bd726ec..88f8c23 100644
--- a/lib/prometheus_exporter/metric/base.rb
+++ b/lib/prometheus_exporter/metric/base.rb
@@ -57,6 +57,8 @@ module PrometheusExporter::Metric
       labels = (labels || {}).merge(Base.default_labels)
       if labels && labels.length > 0
         s = labels.map do |key, value|
+          value = value.to_s
+          value = escape_value(value) if needs_escape?(value)
           "#{key}=\"#{value}\""
         end.join(",")
         "{#{s}}"
@@ -70,5 +72,29 @@ module PrometheusExporter::Metric
         #{metric_text}
       TEXT
     end
+
+    private
+
+    def escape_value(str)
+      str.gsub(/[\n"\\]/m) do |m|
+        if m == "\n"
+          "\\n"
+        else
+          "\\#{m}"
+        end
+      end
+    end
+
+    # when we drop Ruby 2.3 we can drop this
+    if "".respond_to? :match?
+      def needs_escape?(str)
+        str.match?(/[\n"\\]/m)
+      end
+    else
+      def needs_escape?(str)
+        !!str.match(/[\n"\\]/m)
+      end
+    end
+
   end
 end
diff --git a/lib/prometheus_exporter/version.rb b/lib/prometheus_exporter/version.rb
index 773a4c4..e411279 100644
--- a/lib/prometheus_exporter/version.rb
+++ b/lib/prometheus_exporter/version.rb
@@ -1,3 +1,3 @@
 module PrometheusExporter
-  VERSION = "0.4.6"
+  VERSION = "0.4.7"
 end
diff --git a/test/metric/counter_test.rb b/test/metric/counter_test.rb
index c7226ff..6fbac2b 100644
--- a/test/metric/counter_test.rb
+++ b/test/metric/counter_test.rb
@@ -81,9 +81,27 @@ module PrometheusExporter::Metric
       assert_equal(counter.to_prometheus_text, text)
     end
 
+    it "can correctly escape label names" do
+      counter.observe(1, sam: "encoding \\ \\")
+      counter.observe(1, sam: "encoding \" \"")
+      counter.observe(1, sam: "encoding \n \n")
+
+      # per spec: label_value can be any sequence of UTF-8 characters, but the backslash (\, double-quote ("}, and line feed (\n) characters have to be escaped as \\, \", and \n, respectively
+
+      text = <<~TEXT
+        # HELP a_counter my amazing counter
+        # TYPE a_counter counter
+        a_counter{sam="encoding \\\\ \\\\"} 1
+        a_counter{sam="encoding \\" \\""} 1
+        a_counter{sam="encoding \\n \\n"} 1
+      TEXT
+
+      assert_equal(counter.to_prometheus_text, text)
+    end
+
     it "can correctly reset to a default value" do
       counter.observe(5, sam: "ham")
-      counter.reset({ sam: "ham" })
+      counter.reset(sam: "ham")
 
       text = <<~TEXT
         # HELP a_counter my amazing counter

GitHub sha: 97994d57

Followed Up #2

FIX: support labels with " and special chars

Approved #3