Repository: prometheus/statsd_exporter
Pull request #283 (2020-01-10 14:09:24)

PR #272 continued
Picking up #272 to get it over the finish line:

* [x] fix the build issues
* [x] convert panics to log+exit (missed this in initial review)

cc @mhartenbower


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4830665..e616781 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,51 +1,44 @@
-## 0.14.0 / unreleased
-
-* [ENHANCEMENT] Add metrics for mapping cache hits ([#280](https://github.com/prometheus/statsd_exporter/pull/280))
-* [CHANGE] Rename existing metric for mapping cache size ([#282](https://github.com/prometheus/statsd_exporter/pull/282))
-
-The metric `statsd_exporter_cache_length` is now called `mapping_cache_length`.
-
-## 0.13.0 / 2019-12-06
+## unreleased / ????-??-??
 
 * [ENHANCEMENT] Support sampling factors for all statsd metric types ([#264](https://github.com/prometheus/statsd_exporter/issues/250))
 * [ENHANCEMENT] Support Librato and InfluxDB labeling formats ([#267](https://github.com/prometheus/statsd_exporter/pull/267))
 
-## 0.12.2 / 2019-07-25
+## v0.12.2 / 2019-07-25
 
 * [BUGFIX] Fix Unix socket handler ([#252](https://github.com/prometheus/statsd_exporter/pull/252))
 * [BUGFIX] Fix panic under high load ([#253](https://github.com/prometheus/statsd_exporter/pull/253))
 
 Thank you to everyone who reported and helped debug these issues!
 
-## 0.12.1 / 2019-07-08
+## v0.12.1 / 2019-07-08
 
 * [BUGFIX] Renew TTL when a metric receives updates ([#246](https://github.com/prometheus/statsd_exporter/pull/246))
 * [CHANGE] Reload on SIGHUP instead of watching the file ([#243](https://github.com/prometheus/statsd_exporter/pull/243))
 
-## 0.11.2 / 2019-06-14
+## v0.11.2 / 2019-06-14
 
 * [BUGFIX] Fix TCP handler ([#235](https://github.com/prometheus/statsd_exporter/pull/235))
 
-## 0.11.1 / 2019-06-14
+## v0.11.1 / 2019-06-14
 
 * [ENHANCEMENT] Batch event processing for improved ingestion performance ([#227](https://github.com/prometheus/statsd_exporter/pull/227))
 * [ENHANCEMENT] Switch Prometheus client to promhttp, freeing the standard HTTP metrics ([#233](https://github.com/prometheus/statsd_exporter/pull/233))
 
 With #233, the exporter no longer exports metrics about its own HTTP status. These were not helpful since you could not get them when scraping fails. This allows mapping to metric names like `http_requests_total` that are useful as application metrics.
 
-## 0.10.6 / 2019-06-07
+## v0.10.6 / 2019-06-07
 
 * [BUGFIX] Fix mapping collision for metrics with different types, but the same name ([#229](https://github.com/prometheus/statsd_exporter/pull/229))
 
-## 0.10.5 / 2019-05-27
+## v0.10.5 / 2019-05-27
 
 * [BUGFIX] Fix "Error: inconsistent label cardinality: expected 0 label values but got N in prometheus.Labels" ([#224](https://github.com/prometheus/statsd_exporter/pull/224))
 
-## 0.10.4 / 2019-05-20
+## v0.10.4 / 2019-05-20
 
 * [BUGFIX] Revert #218 due to a race condition ([#221](https://github.com/prometheus/statsd_exporter/pull/221))
 
-## 0.10.3 / 2019-05-17
+## v0.10.3 / 2019-05-17
 
 * [ENHANCEMENT] Reduce allocations when escaping metric names ([#217](https://github.com/prometheus/statsd_exporter/pull/217))
 * [ENHANCEMENT] Reduce allocations when handling packets ([#218](https://github.com/prometheus/statsd_exporter/pull/218))
@@ -53,7 +46,7 @@ With #233, the exporter no longer exports metrics about its own HTTP status. The
 
 This release is entirely powered by @claytono. Kudos!
 
-## 0.10.2 / 2019-05-17
+## v0.10.2 / 2019-05-17
 
 * [CHANGE] Do not run as root in the Docker container by default ([#202](https://github.com/prometheus/statsd_exporter/pull/202))
 * [FEATURE] Add metric for count of events by action ([#193](https://github.com/prometheus/statsd_exporter/pull/193))
@@ -72,7 +65,7 @@ This release is entirely powered by @claytono. Kudos!
 
 With #192, the count of events rejected because of negative counter increments has moved into the `statsd_exporter_events_error_total` metric, instead of being lumped in with the different kinds of successful events.
 
-## 0.9.0 / 2019-03-11
+## v0.9.0 / 2019-03-11
 
 * [ENHANCEMENT] Update the Prometheus client library to 0.9.2 ([#171](https://github.com/prometheus/statsd_exporter/pull/171))
 * [FEATURE] Metrics can now be expired with a per-mapping TTL ([#164](https://github.com/prometheus/statsd_exporter/pull/164))
@@ -81,12 +74,12 @@ With #192, the count of events rejected because of negative counter increments h
 If you are using summaries, all your quantiles and `_total` will change by a factor of 1000.
 Adjust your queries and dashboards, or consider switching to histograms altogether.
 
-## 0.8.1 / 2018-12-05
+## v0.8.1 / 2018-12-05
 
 * [BUGFIX] Expose the counter for unmapped matches ([#161](https://github.com/prometheus/statsd_exporter/pull/161))
 * [BUGFIX] Unsuccessful backtracking does not clobber captures ([#169](https://github.com/prometheus/statsd_exporter/pull/169), fixes [#168](https://github.com/prometheus/statsd_exporter/issues/168))
 
-## 0.8.0 / 2018-10-12
+## v0.8.0 / 2018-10-12
 
 * [ENHANCEMENT] Speed up glob matching ([#157](https://github.com/prometheus/statsd_exporter/pull/157))
 
@@ -96,7 +89,7 @@ is logged.
 
 This major enhancement was contributed by [Wangchong Zhou](https://github.com/fffonion).
 
-## 0.7.0 / 2018-08-22
+## v0.7.0 / 2018-08-22
 
 This is a breaking release, but the migration is easy: command line flags now
 require two dashes (`--help` instead of `-help`). The previous flag library
@@ -111,12 +104,12 @@ The deprecated `--statsd.listen-address` flag has been removed, use
 * [ENHANCEMENT] Summary quantiles can be configured ([#135](https://github.com/prometheus/statsd_exporter/pulls/135))
 * [BUGFIX] Fix panic if an invalid regular expression is supplied ([#126](https://github.com/prometheus/statsd_exporter/pulls/126))
 
-## 0.6.0 / 2018-01-17
+## v0.6.0 / 2018-01-17
 
 * [ENHANCEMENT] Add a drop action ([#115](https://github.com/prometheus/statsd_exporter/pulls/115))
 * [ENHANCEMENT] Allow templating metric names ([#117](https://github.com/prometheus/statsd_exporter/pulls/117))
 
-## 0.5.0 / 2017-11-16
+## v0.5.0 / 2017-11-16
 
 NOTE: This release breaks backward compatibility. `statsd_exporter` now uses
 a YAML configuration file. You must convert your mappings configuration to
@@ -179,7 +172,7 @@ There is a [tool](https://github.com/bakins/statsd-exporter-convert) available t
 * [BUGFIX] Conflicting label sets no longer crash the exporter and will be
   ignored. Restart to clear the remembered label set. [#72](https://github.com/prometheus/statsd_exporter/pulls/72)
 
-## 0.4.0 / 2017-05-12
+## v0.4.0 / 2017-05-12
 
 * [ENHANCEMENT] Improve mapping configuration parser [#61](https://github.com/prometheus/statsd_exporter/pulls/61)
 * [ENHANCEMENT] Add increment/decrement support to Gauges [#65](https://github.com/prometheus/statsd_exporter/pulls/65)
diff --git a/README.md b/README.md
index b992e8e..5714bd3 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ For DogStatsD-style tags, they're appended as a `|#` delimited section at the
 end of the metric, as so:
 
 ```
-metric.name:0|c|#tagName:val,tag2Name:val2
+metric.name:0|c|#tagName=val,tag2Name=val2
 ```
 
 See [Tags](https://docs.datadoghq.com/developers/dogstatsd/data_types/#tagging)
@@ -200,16 +200,6 @@ could be written as:
 
 ```yaml
 mappings:
-- match: "test\\.(\\w+)\\.(\\w+)\\.counter"
-  match_type: regex
-  name: "${2}_total"
-  labels:
-    provider: "$1"
-```
-
-Be aware about yaml escape rules as a mapping like the following one will not work. 
-```yaml 
-mappings:
 - match: "test\.(\w+)\.(\w+)\.counter"
   match_type: regex
   name: "${2}_total"
@@ -413,7 +403,7 @@ mappings:
 
 Possible values for `match_metric_type` are `gauge`, `counter` and `timer`.
 
-### Mapping cache size and cache replacement policy
+### Mapping cache size and cache replacement polixy
 
 There is a cache used to improve the performance of the metric mapping, that can greatly improvement performance.
 The cache has a default maximum of 1000 unique statsd metric names -> prometheus metrics mappings that it can store.
diff --git a/VERSION b/VERSION
index a803cc2..26acbf0 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.14.0
+0.12.2
diff --git a/exporter.go b/exporter.go
index a8dd607..c70a12e 100644
--- a/exporter.go
+++ b/exporter.go
@@ -18,13 +18,15 @@ import (
 	"fmt"
 	"io"
 	"net"
+	"os"
 	"strconv"
 	"strings"
 	"time"
 	"unicode/utf8"
 
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
 	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/common/log"
 
 	"github.com/prometheus/statsd_exporter/pkg/clock"
 	"github.com/prometheus/statsd_exporter/pkg/mapper"
@@ -32,7 +34,7 @@ import (
 
 const (
 	defaultHelp = "Metric autogenerated by statsd_exporter."
-	regErrF     = "Failed to update metric %q. Error: %s"
+	regErrF     = "Failed to update metric"
 )
 
 // uncheckedCollector wraps a Collector but its Describe method yields no Desc.
@@ -49,6 +51,7 @@ func (u uncheckedCollector) Collect(c chan<- prometheus.Metric) {
 type Exporter struct {
 	mapper   *mapper.MetricMapper
 	registry *registry
+	logger   log.Logger
 }
 
 // Replace invalid characters in the metric name with "_"
@@ -117,7 +120,7 @@ func (b *Exporter) Listen(e <-chan Events) {
 			b.registry.removeStaleMetrics()
 		case events, ok := <-e:
 			if !ok {
-				log.Debug("Channel is closed. Break out of Exporter.Listener.")
+				level.Debug(b.logger).Log("msg", "Channel is closed. Break out of Exporter.Listener.")
 				removeStaleMetricsTicker.Stop()
 				return
 			}
@@ -152,7 +155,7 @@ func (b *Exporter) handleEvent(event Event) {
 	prometheusLabels := event.Labels()
 	if present {
 		if mapping.Name == "" {
-			log.Debugf("The mapping of '%s' for match '%s' generates an empty metric name", event.MetricName(), mapping.Match)
+			level.Debug(b.logger).Log("msg", "The mapping generates an empty metric name", "metric_name", event.MetricName(), "match", mapping.Match)
 			errorEventStats.WithLabelValues("empty_metric_name").Inc()
 			return
 		}
@@ -171,7 +174,7 @@ func (b *Exporter) handleEvent(event Event) {
 		// We don't accept negative values for counters. Incrementing the counter with a negative number
 		// will cause the exporter to panic. Instead we will warn and continue to the next event.
 		if event.Value() < 0.0 {
-			log.Debugf("Counter %q is: '%f' (counter must be non-negative value)", metricName, event.Value())
+			level.Debug(b.logger).Log("msg", "counter must be non-negative value", "metric", metricName, "event_value", event.Value())
 			errorEventStats.WithLabelValues("illegal_negative_counter").Inc()
 			return
 		}
@@ -181,7 +184,7 @@ func (b *Exporter) handleEvent(event Event) {
 			counter.Add(event.Value())
 			eventStats.WithLabelValues("counter").Inc()
 		} else {
-			log.Debugf(regErrF, metricName, err)
+			level.Debug(b.logger).Log("msg", regErrF, "metric", metricName, "error", err)
 			conflictingEventStats.WithLabelValues("counter").Inc()
 		}
 
@@ -196,7 +199,7 @@ func (b *Exporter) handleEvent(event Event) {
 			}
 			eventStats.WithLabelValues("gauge").Inc()
 		} else {
-			log.Debugf(regErrF, metricName, err)
+			level.Debug(b.logger).Log("msg", regErrF, "metric", metricName, "error", err)
 			conflictingEventStats.WithLabelValues("gauge").Inc()
 		}
 
@@ -216,7 +219,7 @@ func (b *Exporter) handleEvent(event Event) {
 				histogram.Observe(event.Value() / 1000) // prometheus presumes seconds, statsd millisecond
 				eventStats.WithLabelValues("timer").Inc()
 			} else {
-				log.Debugf(regErrF, metricName, err)
+				level.Debug(b.logger).Log("msg", regErrF, "metric", metricName, "error", err)
 				conflictingEventStats.WithLabelValues("timer").Inc()
 			}
 
@@ -226,24 +229,26 @@ func (b *Exporter) handleEvent(event Event) {
 				summary.Observe(event.Value() / 1000) // prometheus presumes seconds, statsd millisecond
 				eventStats.WithLabelValues("timer").Inc()
 			} else {
-				log.Debugf(regErrF, metricName, err)
+				level.Debug(b.logger).Log("msg", regErrF, "metric", metricName, "error", err)
 				conflictingEventStats.WithLabelValues("timer").Inc()
 			}
 
 		default:
-			panic(fmt.Sprintf("unknown timer type '%s'", t))
+			level.Error(b.logger).Log("msg", "unknown timer type", "type", t)
+			os.Exit(1)
 		}
 
 	default:
-		log.Debugln("Unsupported event type")
+		level.Debug(b.logger).Log("msg", "Unsupported event type")
 		eventStats.WithLabelValues("illegal").Inc()
 	}
 }
 
-func NewExporter(mapper *mapper.MetricMapper) *Exporter {
+func NewExporter(mapper *mapper.MetricMapper, logger log.Logger) *Exporter {
 	return &Exporter{
 		mapper:   mapper,
 		registry: newRegistry(mapper),
+		logger:   logger,
 	}
 }
 
@@ -275,11 +280,11 @@ func buildEvent(statType, metric string, value float64, relative bool, labels ma
 	}
 }
 
-func parseTag(component, tag string, separator rune, labels map[string]string) {
+func parseTag(component, tag string, separator rune, labels map[string]string, logger log.Logger) {
 	// Entirely empty tag is an error
 	if len(tag) == 0 {
 		tagErrors.Inc()
-		log.Debugf("Empty name tag in component %s", component)
+		level.Debug(logger).Log("msg", "Empty name tag", "component", component)
 		return
 	}
 
@@ -291,7 +296,7 @@ func parseTag(component, tag string, separator rune, labels map[string]string) {
 			if len(k) == 0 || len(v) == 0 {
 				// Empty key or value is an error
 				tagErrors.Inc()
-				log.Debugf("Malformed name tag %s=%s in component %s", k, v, component)
+				level.Debug(logger).Log("msg", "Malformed name tag", "k", k, "v", v, "component", component)
 			} else {
 				labels[escapeMetricName(k)] = v
 			}
@@ -301,23 +306,23 @@ func parseTag(component, tag string, separator rune, labels map[string]string) {
 
 	// Missing separator (no value) is an error
 	tagErrors.Inc()
-	log.Debugf("Malformed name tag %s in component %s", tag, component)
+	level.Debug(logger).Log("msg", "Malformed name tag", "tag", tag, "component", component)
 }
 
-func parseNameTags(component string, labels map[string]string) {
+func parseNameTags(component string, labels map[string]string, logger log.Logger) {
 	lastTagEndIndex := 0
 	for i, c := range component {
 		if c == ',' {
 			tag := component[lastTagEndIndex:i]
 			lastTagEndIndex = i + 1
-			parseTag(component, tag, '=', labels)
+			parseTag(component, tag, '=', labels, logger)
 		}
 	}
 
 	// If we're not off the end of the string, add the last tag
 	if lastTagEndIndex < len(component) {
 		tag := component[lastTagEndIndex:]
-		parseTag(component, tag, '=', labels)
+		parseTag(component, tag, '=', labels, logger)
 	}
 }
 
@@ -328,38 +333,38 @@ func trimLeftHash(s string) string {
 	return s
 }
 
-func parseDogStatsDTags(component string, labels map[string]string) {
+func parseDogStatsDTags(component string, labels map[string]string, logger log.Logger) {
 	lastTagEndIndex := 0
 	for i, c := range component {
 		if c == ',' {
 			tag := component[lastTagEndIndex:i]
 			lastTagEndIndex = i + 1
-			parseTag(component, trimLeftHash(tag), ':', labels)
+			parseTag(component, trimLeftHash(tag), ':', labels, logger)
 		}
 	}
 
 	// If we're not off the end of the string, add the last tag
 	if lastTagEndIndex < len(component) {
 		tag := component[lastTagEndIndex:]
-		parseTag(component, trimLeftHash(tag), ':', labels)
+		parseTag(component, trimLeftHash(tag), ':', labels, logger)
 	}
 }
 
-func parseNameAndTags(name string, labels map[string]string) string {
+func parseNameAndTags(name string, labels map[string]string, logger log.Logger) string {
 	for i, c := range name {
 		// `#` delimits start of tags by Librato
 		// https://www.librato.com/docs/kb/collect/collection_agents/stastd/#stat-level-tags
 		// `,` delimits start of tags by InfluxDB
 		// https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/#introducing-influx-statsd
 		if c == '#' || c == ',' {
-			parseNameTags(name[i+1:], labels)
+			parseNameTags(name[i+1:], labels, logger)
 			return name[:i]
 		}
 	}
 	return name
 }
 
-func lineToEvents(line string) Events {
+func lineToEvents(line string, logger log.Logger) Events {
 	events := Events{}
 	if line == "" {
 		return events
@@ -368,12 +373,12 @@ func lineToEvents(line string) Events {
 	elements := strings.SplitN(line, ":", 2)
 	if len(elements) < 2 || len(elements[0]) == 0 || !utf8.ValidString(line) {
 		sampleErrors.WithLabelValues("malformed_line").Inc()
-		log.Debugln("Bad line from StatsD:", line)
+		level.Debug(logger).Log("msg", "Bad line from StatsD", "line", line)
 		return events
 	}
 
 	labels := map[string]string{}
-	metric := parseNameAndTags(elements[0], labels)
+	metric := parseNameAndTags(elements[0], labels, logger)
 
 	var samples []string
 	if strings.Contains(elements[1], "|#") {
@@ -382,7 +387,7 @@ func lineToEvents(line string) Events {
 		// don't allow mixed tagging styles
 		if len(labels) > 0 {
 			sampleErrors.WithLabelValues("mixed_tagging_styles").Inc()
-			log.Debugln("Bad line (multiple tagging styles) from StatsD:", line)
+			level.Debug(logger).Log("msg", "Bad line (multiple tagging styles) from StatsD", "line", line)
 			return events
 		}
 
@@ -398,7 +403,7 @@ samples:
 		samplingFactor := 1.0
 		if len(components) < 2 || len(components) > 4 {
 			sampleErrors.WithLabelValues("malformed_component").Inc()
-			log.Debugln("Bad component on line:", line)
+			level.Debug(logger).Log("msg", "Bad component", "line", line)
 			continue
 		}
 		valueStr, statType := components[0], components[1]
@@ -410,7 +415,7 @@ samples:
 
 		value, err := strconv.ParseFloat(valueStr, 64)
 		if err != nil {
-			log.Debugf("Bad value %s on line: %s", valueStr, line)
+			level.Debug(logger).Log("msg", "Bad value", "value", valueStr, "line", line)
 			sampleErrors.WithLabelValues("malformed_value").Inc()
 			continue
 		}
@@ -419,7 +424,7 @@ samples:
 		if len(components) >= 3 {
 			for _, component := range components[2:] {
 				if len(component) == 0 {
-					log.Debugln("Empty component on line: ", line)
+					level.Debug(logger).Log("msg", "Empty component", "line", line)
 					sampleErrors.WithLabelValues("malformed_component").Inc()
 					continue samples
 				}
@@ -431,7 +436,7 @@ samples:
 
 					samplingFactor, err = strconv.ParseFloat(component[1:], 64)
 					if err != nil {
-						log.Debugf("Invalid sampling factor %s on line %s", component[1:], line)
+						level.Debug(logger).Log("msg", "Invalid sampling factor", "component", component[1:], "line", line)
 						sampleErrors.WithLabelValues("invalid_sample_factor").Inc()
 					}
 					if samplingFactor == 0 {
@@ -446,9 +451,9 @@ samples:
 						multiplyEvents = int(1 / samplingFactor)
 					}
 				case '#':
-					parseDogStatsDTags(component[1:], labels)
+					parseDogStatsDTags(component[1:], labels, logger)
 				default:
-					log.Debugf("Invalid sampling factor or tag section %s on line %s", components[2], line)
+					level.Debug(logger).Log("msg", "Invalid sampling factor or tag section", "component", components[2], "line", line)
 					sampleErrors.WithLabelValues("invalid_sample_factor").Inc()
 					continue
 				}
@@ -462,7 +467,7 @@ samples:
 		for i := 0; i < multiplyEvents; i++ {
 			event, err := buildEvent(statType, metric, value, relative, labels)
 			if err != nil {
-				log.Debugf("Error building event on line %s: %s", line, err)
+				level.Debug(logger).Log("msg", "Error building event", "line", line, "error", err)
 				sampleErrors.WithLabelValues("illegal_event").Inc()
 				continue
 			}
@@ -475,6 +480,7 @@ samples:
 type StatsDUDPListener struct {
 	conn         *net.UDPConn
 	eventHandler eventHandler
+	logger       log.Logger
 }
 
 func (l *StatsDUDPListener) SetEventHandler(eh eventHandler) {
@@ -491,7 +497,7 @@ func (l *StatsDUDPListener) Listen() {
 			if strings.HasSuffix(err.Error(), "use of closed network connection") {
 				return
 			}
-			log.Error(err)
+			level.Error(l.logger).Log("error", err)
 			return
 		}
 		l.handlePacket(buf[0:n])
@@ -503,13 +509,14 @@ func (l *StatsDUDPListener) handlePacket(packet []byte) {
 	lines := strings.Split(string(packet), "\n")
 	for _, line := range lines {
 		linesReceived.Inc()
-		l.eventHandler.queue(lineToEvents(line))
+		l.eventHandler.queue(lineToEvents(line, l.logger))
 	}
 }
 
 type StatsDTCPListener struct {
 	conn         *net.TCPListener
 	eventHandler eventHandler
+	logger       log.Logger
 }
 
 func (l *StatsDTCPListener) SetEventHandler(eh eventHandler) {
@@ -525,7 +532,8 @@ func (l *StatsDTCPListener) Listen() {
 			if strings.HasSuffix(err.Error(), "use of closed network connection") {
 				return
 			}
-			log.Fatalf("AcceptTCP failed: %v", err)
+			level.Error(l.logger).Log("msg", "AcceptTCP failed", "error", err)
+			os.Exit(1)
 		}
 		go l.handleConn(c)
 	}
@@ -542,23 +550,24 @@ func (l *StatsDTCPListener) handleConn(c *net.TCPConn) {
 		if err != nil {
 			if err != io.EOF {
 				tcpErrors.Inc()
-				log.Debugf("Read %s failed: %v", c.RemoteAddr(), err)
+				level.Debug(l.logger).Log("msg", "Read failed", "addr", c.RemoteAddr(), "error", err)
 			}
 			break
 		}
 		if isPrefix {
 			tcpLineTooLong.Inc()
-			log.Debugf("Read %s failed: line too long", c.RemoteAddr())
+			level.Debug(l.logger).Log("msg", "Read failed: line too long", "addr", c.RemoteAddr())
 			break
 		}
 		linesReceived.Inc()
-		l.eventHandler.queue(lineToEvents(string(line)))
+		l.eventHandler.queue(lineToEvents(string(line), l.logger))
 	}
 }
 
 type StatsDUnixgramListener struct {
 	conn         *net.UnixConn
 	eventHandler eventHandler
+	logger       log.Logger
 }
 
 func (l *StatsDUnixgramListener) SetEventHandler(eh eventHandler) {
@@ -575,7 +584,8 @@ func (l *StatsDUnixgramListener) Listen() {
 			if strings.HasSuffix(err.Error(), "use of closed network connection") {
 				return
 			}
-			log.Fatal(err)
+			level.Error(l.logger).Log(err)
+			os.Exit(1)
 		}
 		l.handlePacket(buf[:n])
 	}
@@ -586,6 +596,6 @@ func (l *StatsDUnixgramListener) handlePacket(packet []byte) {
 	lines := strings.Split(string(packet), "\n")
 	for _, line := range lines {
 		linesReceived.Inc()
-		l.eventHandler.queue(lineToEvents(string(line)))
+		l.eventHandler.queue(lineToEvents(string(line), l.logger))
 	}
 }
diff --git a/go.mod b/go.mod
index 7a5f929..6873c9f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,15 @@
 module github.com/prometheus/statsd_exporter
 
 require (
+	github.com/go-kit/kit v0.9.0
 	github.com/hashicorp/golang-lru v0.5.1
 	github.com/kr/pretty v0.1.0 // indirect
 	github.com/prometheus/client_golang v1.0.0
 	github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
-	github.com/prometheus/common v0.4.1
-	github.com/sirupsen/logrus v1.4.2 // indirect
+	github.com/prometheus/common v0.7.0
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
-	gopkg.in/yaml.v2 v2.2.1
+	gopkg.in/yaml.v2 v2.2.2
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index 916d749..5799ea8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,11 @@
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -9,20 +13,30 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -35,6 +49,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -46,6 +62,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -61,15 +79,19 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -77,3 +99,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/main.go b/main.go
index 01c93e5..7095b06 100644
--- a/main.go
+++ b/main.go
@@ -15,6 +15,7 @@ package main
 
 import (
 	"bufio"
+	"fmt"
 	"net"
 	"net/http"
 	_ "net/http/pprof"
@@ -23,9 +24,12 @@ import (
 	"strconv"
 	"syscall"
 
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"github.com/prometheus/common/log"
+	"github.com/prometheus/common/promlog"
+	"github.com/prometheus/common/promlog/flag"
 	"github.com/prometheus/common/version"
 	"gopkg.in/alecthomas/kingpin.v2"
 
@@ -36,7 +40,7 @@ func init() {
 	prometheus.MustRegister(version.NewCollector("statsd_exporter"))
 }
 
-func serveHTTP(listenAddress, metricsEndpoint string) {
+func serveHTTP(listenAddress, metricsEndpoint string, logger log.Logger) {
 	http.Handle(metricsEndpoint, promhttp.Handler())
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		w.Write([]byte(`<html>
@@ -47,13 +51,14 @@ func serveHTTP(listenAddress, metricsEndpoint string) {
 			</body>
 			</html>`))
 	})
-	log.Fatal(http.ListenAndServe(listenAddress, nil))
+	level.Error(logger).Log("msg", http.ListenAndServe(listenAddress, nil))
+	os.Exit(1)
 }
 
-func ipPortFromString(addr string) (*net.IPAddr, int) {
+func ipPortFromString(addr string) (*net.IPAddr, int, error) {
 	host, portStr, err := net.SplitHostPort(addr)
 	if err != nil {
-		log.Fatal("Bad StatsD listening address", addr)
+		return nil, 0, fmt.Errorf("bad StatsD listening address: %s", addr)
 	}
 
 	if host == "" {
@@ -61,68 +66,74 @@ func ipPortFromString(addr string) (*net.IPAddr, int) {
 	}
 	ip, err := net.ResolveIPAddr("ip", host)
 	if err != nil {
-		log.Fatalf("Unable to resolve %s: %s", host, err)
+		return nil, 0, fmt.Errorf("Unable to resolve %s: %s", host, err)
 	}
 
 	port, err := strconv.Atoi(portStr)
 	if err != nil || port < 0 || port > 65535 {
-		log.Fatalf("Bad port %s: %s", portStr, err)
+		return nil, 0, fmt.Errorf("Bad port %s: %s", portStr, err)
 	}
 
-	return ip, port
+	return ip, port, nil
 }
 
-func udpAddrFromString(addr string) *net.UDPAddr {
-	ip, port := ipPortFromString(addr)
+func udpAddrFromString(addr string) (*net.UDPAddr, error) {
+	ip, port, err := ipPortFromString(addr)
+	if err != nil {
+		return nil, err
+	}
 	return &net.UDPAddr{
 		IP:   ip.IP,
 		Port: port,
 		Zone: ip.Zone,
-	}
+	}, nil
 }
 
-func tcpAddrFromString(addr string) *net.TCPAddr {
-	ip, port := ipPortFromString(addr)
+func tcpAddrFromString(addr string) (*net.TCPAddr, error) {
+	ip, port, err := ipPortFromString(addr)
+	if err != nil {
+		return nil, err
+	}
 	return &net.TCPAddr{
 		IP:   ip.IP,
 		Port: port,
 		Zone: ip.Zone,
-	}
+	}, nil
 }
 
-func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int) {
+func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int, logger log.Logger) {
 
 	signals := make(chan os.Signal, 1)
 	signal.Notify(signals, syscall.SIGHUP)
 
 	for s := range signals {
 		if fileName == "" {
-			log.Warnf("Received %s but no mapping config to reload", s)
+			level.Warn(logger).Log("msg", "Received signal but no mapping config to reload", "signal", s)
 			continue
 		}
-		log.Infof("Received %s, attempting reload", s)
+		level.Info(logger).Log("msg", "Received signal, attempting reload", "signal", s)
 		err := mapper.InitFromFile(fileName, cacheSize)
 		if err != nil {
-			log.Errorln("Error reloading config:", err)
+			level.Info(logger).Log("msg", "Error reloading config", "error", err)
 			configLoads.WithLabelValues("failure").Inc()
 		} else {
-			log.Infoln("Config reloaded successfully")
+			level.Info(logger).Log("msg", "Config reloaded successfully")
 			configLoads.WithLabelValues("success").Inc()
 		}
 	}
 }
 
-func dumpFSM(mapper *mapper.MetricMapper, dumpFilename string) error {
+func dumpFSM(mapper *mapper.MetricMapper, dumpFilename string, logger log.Logger) error {
 	f, err := os.Create(dumpFilename)
 	if err != nil {
 		return err
 	}
-	log.Infoln("Start dumping FSM to", dumpFilename)
+	level.Info(logger).Log("msg", "Start dumping FSM", "file_name", dumpFilename)
 	w := bufio.NewWriter(f)
 	mapper.FSM.DumpFSM(w)
 	w.Flush()
 	f.Close()
-	log.Infoln("Finish dumping FSM")
+	level.Info(logger).Log("msg", "Finish dumping FSM")
 	return nil
 }
 
@@ -144,37 +155,46 @@ func main() {
 		dumpFSMPath          = kingpin.Flag("debug.dump-fsm", "The path to dump internal FSM generated for glob matching as Dot file.").Default("").String()
 	)
 
-	log.AddFlags(kingpin.CommandLine)
+	promlogConfig := &promlog.Config{}
+	flag.AddFlags(kingpin.CommandLine, promlogConfig)
 	kingpin.Version(version.Print("statsd_exporter"))
 	kingpin.HelpFlag.Short('h')
 	kingpin.Parse()
+	logger := promlog.New(promlogConfig)
 
 	if *statsdListenUDP == "" && *statsdListenTCP == "" && *statsdListenUnixgram == "" {
-		log.Fatalln("At least one of UDP/TCP/Unixgram listeners must be specified.")
+		level.Error(logger).Log("At least one of UDP/TCP/Unixgram listeners must be specified.")
+		os.Exit(1)
 	}
 
-	log.Infoln("Starting StatsD -> Prometheus Exporter", version.Info())
-	log.Infoln("Build context", version.BuildContext())
-	log.Infof("Accepting StatsD Traffic: UDP %v, TCP %v, Unixgram %v", *statsdListenUDP, *statsdListenTCP, *statsdListenUnixgram)
-	log.Infoln("Accepting Prometheus Requests on", *listenAddress)
+	level.Info(logger).Log("msg", "Starting StatsD -> Prometheus Exporter", "version", version.Info())
+	level.Info(logger).Log("msg", "Build context", "context", version.BuildContext())
+	level.Info(logger).Log("msg", "Accepting StatsD Traffic", "udp", *statsdListenUDP, "tcp", *statsdListenTCP, "unixgram", *statsdListenUnixgram)
+	level.Info(logger).Log("msg", "Accepting Prometheus Requests", "addr", *listenAddress)
 
-	go serveHTTP(*listenAddress, *metricsEndpoint)
+	go serveHTTP(*listenAddress, *metricsEndpoint, logger)
 
 	events := make(chan Events, *eventQueueSize)
 	defer close(events)
 	eventQueue := newEventQueue(events, *eventFlushThreshold, *eventFlushInterval)
 
 	if *statsdListenUDP != "" {
-		udpListenAddr := udpAddrFromString(*statsdListenUDP)
+		udpListenAddr, err := udpAddrFromString(*statsdListenUDP)
+		if err != nil {
+			level.Error(logger).Log("msg", "invalid UDP listen address", "address", *statsdListenUDP, "error", err)
+			os.Exit(1)
+		}
 		uconn, err := net.ListenUDP("udp", udpListenAddr)
 		if err != nil {
-			log.Fatal(err)
+			level.Error(logger).Log("msg", "failed to start UDP listener", "error", err)
+			os.Exit(1)
 		}
 
 		if *readBuffer != 0 {
 			err = uconn.SetReadBuffer(*readBuffer)
 			if err != nil {
-				log.Fatal("Error setting UDP read buffer:", err)
+				level.Error(logger).Log("msg", "error setting UDP read buffer", "error", err)
+				os.Exit(1)
 			}
 		}
 
@@ -183,28 +203,35 @@ func main() {
 	}
 
 	if *statsdListenTCP != "" {
-		tcpListenAddr := tcpAddrFromString(*statsdListenTCP)
+		tcpListenAddr, err := tcpAddrFromString(*statsdListenTCP)
+		if err != nil {
+			level.Error(logger).Log("msg", "invalid TCP listen address", "address", *statsdListenUDP, "error", err)
+			os.Exit(1)
+		}
 		tconn, err := net.ListenTCP("tcp", tcpListenAddr)
 		if err != nil {
-			log.Fatal(err)
+			level.Error(logger).Log("msg", err)
+			os.Exit(1)
 		}
 		defer tconn.Close()
 
-		tl := &StatsDTCPListener{conn: tconn, eventHandler: eventQueue}
+		tl := &StatsDTCPListener{conn: tconn, eventHandler: eventQueue, logger: logger}
 		go tl.Listen()
 	}
 
 	if *statsdListenUnixgram != "" {
 		var err error
 		if _, err = os.Stat(*statsdListenUnixgram); !os.IsNotExist(err) {
-			log.Fatalf("Unixgram socket \"%s\" already exists", *statsdListenUnixgram)
+			level.Error(logger).Log("msg", "Unixgram socket already exists", "socket_name", *statsdListenUnixgram)
+			os.Exit(1)
 		}
 		uxgconn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{
 			Net:  "unixgram",
 			Name: *statsdListenUnixgram,
 		})
 		if err != nil {
-			log.Fatal(err)
+			level.Error(logger).Log("msg", "failed to listen on Unixgram socket", "error", err)
+			os.Exit(1)
 		}
 
 		defer uxgconn.Close()
@@ -212,11 +239,12 @@ func main() {
 		if *readBuffer != 0 {
 			err = uxgconn.SetReadBuffer(*readBuffer)
 			if err != nil {
-				log.Fatal("Error setting Unixgram read buffer:", err)
+				level.Error(logger).Log("msg", "error setting Unixgram read buffer", "error", err)
+				os.Exit(1)
 			}
 		}
 
-		ul := &StatsDUnixgramListener{conn: uxgconn, eventHandler: eventQueue}
+		ul := &StatsDUnixgramListener{conn: uxgconn, eventHandler: eventQueue, logger: logger}
 		go ul.Listen()
 
 		// if it's an abstract unix domain socket, it won't exist on fs
@@ -227,11 +255,11 @@ func main() {
 			// convert the string to octet
 			perm, err := strconv.ParseInt("0"+string(*statsdUnixSocketMode), 8, 32)
 			if err != nil {
-				log.Warnf("Bad permission %s: %v, ignoring\n", *statsdUnixSocketMode, err)
+				level.Warn(logger).Log("Bad permission %s: %v, ignoring\n", *statsdUnixSocketMode, err)
 			} else {
 				err = os.Chmod(*statsdListenUnixgram, os.FileMode(perm))
 				if err != nil {
-					log.Warnf("Failed to change unixgram socket permission: %v", err)
+					level.Warn(logger).Log("Failed to change unixgram socket permission: %v", err)
 				}
 			}
 		}
@@ -242,21 +270,25 @@ func main() {
 	if *mappingConfig != "" {
 		err := mapper.InitFromFile(*mappingConfig, *cacheSize)
 		if err != nil {
-			log.Fatal("Error loading config:", err)
+			level.Error(logger).Log("msg", "error loading config", "error", err)
+			os.Exit(1)
 		}
 		if *dumpFSMPath != "" {
-			err := dumpFSM(mapper, *dumpFSMPath)
+			err := dumpFSM(mapper, *dumpFSMPath, logger)
 			if err != nil {
-				log.Fatal("Error dumping FSM:", err)
+				level.Error(logger).Log("msg", "error dumping FSM", "error", err)
+				// Failure to dump the FSM is an error (the user asked for it and it
+				// didn't happen) but not fatal (the exporter is fully functional
+				// afterwards).
 			}
 		}
 	} else {
 		mapper.InitCache(*cacheSize)
 	}
 
-	go configReloader(*mappingConfig, mapper, *cacheSize)
+	go configReloader(*mappingConfig, mapper, *cacheSize, logger)
 
-	exporter := NewExporter(mapper)
+	exporter := NewExporter(mapper, logger)
 
 	signals := make(chan os.Signal, 1)
 	signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
diff --git a/pkg/mapper/mapper_cache.go b/pkg/mapper/mapper_cache.go
index e499986..d498df6 100644
--- a/pkg/mapper/mapper_cache.go
+++ b/pkg/mapper/mapper_cache.go
@@ -14,29 +14,18 @@
 package mapper
 
 import (
-	lru "github.com/hashicorp/golang-lru"
+	"fmt"
+	"github.com/hashicorp/golang-lru"
 	"github.com/prometheus/client_golang/prometheus"
 )
 
 var (
 	cacheLength = prometheus.NewGauge(
 		prometheus.GaugeOpts{
-			Name: "mapping_cache_length",
+			Name: "statsd_exporter_cache_length",
 			Help: "The count of unique metrics currently cached.",
 		},
 	)
-	cacheGetsTotal = prometheus.NewCounter(
-		prometheus.CounterOpts{
-			Name: "mapping_cache_gets_total",
-			Help: "The count of total cache gets.",
-		},
-	)
-	cacheHitsTotal = prometheus.NewCounter(
-		prometheus.CounterOpts{
-			Name: "mapping_cache_hits_total",
-			Help: "The count of total cache hits.",
-		},
-	)
 )
 
 type MetricMapperCacheResult struct {
@@ -70,9 +59,7 @@ func NewMetricMapperCache(size int) (*MetricMapperLRUCache, error) {
 }
 
 func (m *MetricMapperLRUCache) Get(metricString string, metricType MetricType) (*MetricMapperCacheResult, bool) {
-	cacheGetsTotal.Inc()
 	if result, ok := m.cache.Get(formatKey(metricString, metricType)); ok {
-		cacheHitsTotal.Inc()
 		return result.(*MetricMapperCacheResult), true
 	} else {
 		return nil, false
@@ -94,7 +81,7 @@ func (m *MetricMapperLRUCache) trackCacheLength() {
 }
 
 func formatKey(metricString string, metricType MetricType) string {
-	return string(metricType) + "." + metricString
+	return fmt.Sprintf("%s.%s", string(metricType), metricString)
 }
 
 func NewMetricMapperNoopCache() *MetricMapperNoopCache {
@@ -116,6 +103,4 @@ func (m *MetricMapperNoopCache) AddMiss(metricString string, metricType MetricTy
 
 func init() {
 	prometheus.MustRegister(cacheLength)
-	prometheus.MustRegister(cacheGetsTotal)
-	prometheus.MustRegister(cacheHitsTotal)
 }
diff --git a/vendor/github.com/alecthomas/template/go.mod b/vendor/github.com/alecthomas/template/go.mod
new file mode 100644
index 0000000..a70670a
--- /dev/null
+++ b/vendor/github.com/alecthomas/template/go.mod
@@ -0,0 +1,1 @@
+module github.com/alecthomas/template
diff --git a/vendor/github.com/alecthomas/units/go.mod b/vendor/github.com/alecthomas/units/go.mod
new file mode 100644
index 0000000..f572173
--- /dev/null
+++ b/vendor/github.com/alecthomas/units/go.mod
@@ -0,0 +1,1 @@
+module github.com/alecthomas/units
diff --git a/vendor/github.com/go-kit/kit/LICENSE b/vendor/github.com/go-kit/kit/LICENSE
new file mode 100644
index 0000000..9d83342
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Peter Bourgon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/go-kit/kit/log/README.md b/vendor/github.com/go-kit/kit/log/README.md
new file mode 100644
index 0000000..a201a3d
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/README.md
@@ -0,0 +1,151 @@
+# package log
+
+`package log` provides a minimal interface for structured logging in services.
+It may be wrapped to encode conventions, enforce type-safety, provide leveled
+logging, and so on. It can be used for both typical application log events,
+and log-structured data streams.
+
+## Structured logging
+
+Structured logging is, basically, conceding to the reality that logs are
+_data_, and warrant some level of schematic rigor. Using a stricter,
+key/value-oriented message format for our logs, containing contextual and
+semantic information, makes it much easier to get insight into the
+operational activity of the systems we build. Consequently, `package log` is
+of the strong belief that "[the benefits of structured logging outweigh the
+minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
+
+Migrating from unstructured to structured logging is probably a lot easier
+than you'd expect.
+
+```go
+// Unstructured
+log.Printf("HTTP server listening on %s", addr)
+
+// Structured
+logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
+```
+
+## Usage
+
+### Typical application logging
+
+```go
+w := log.NewSyncWriter(os.Stderr)
+logger := log.NewLogfmtLogger(w)
+logger.Log("question", "what is the meaning of life?", "answer", 42)
+
+// Output:
+// question="what is the meaning of life?" answer=42
+```
+
+### Contextual Loggers
+
+```go
+func main() {
+	var logger log.Logger
+	logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+	logger = log.With(logger, "instance_id", 123)
+
+	logger.Log("msg", "starting")
+	NewWorker(log.With(logger, "component", "worker")).Run()
+	NewSlacker(log.With(logger, "component", "slacker")).Run()
+}
+
+// Output:
+// instance_id=123 msg=starting
+// instance_id=123 component=worker msg=running
+// instance_id=123 component=slacker msg=running
+```
+
+### Interact with stdlib logger
+
+Redirect stdlib logger to Go kit logger.
+
+```go
+import (
+	"os"
+	stdlog "log"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+func main() {
+	logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
+	stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
+	stdlog.Print("I sure like pie")
+}
+
+// Output:
+// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
+```
+
+Or, if, for legacy reasons, you need to pipe all of your logging through the
+stdlib log package, you can redirect Go kit logger to the stdlib logger.
+
+```go
+logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
+logger.Log("legacy", true, "msg", "at least it's something")
+
+// Output:
+// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
+```
+
+### Timestamps and callers
+
+```go
+var logger log.Logger
+logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
+
+logger.Log("msg", "hello")
+
+// Output:
+// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
+```
+
+## Levels
+
+Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level).
+
+## Supported output formats
+
+- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
+- JSON
+
+## Enhancements
+
+`package log` is centered on the one-method Logger interface.
+
+```go
+type Logger interface {
+	Log(keyvals ...interface{}) error
+}
+```
+
+This interface, and its supporting code like is the product of much iteration
+and evaluation. For more details on the evolution of the Logger interface,
+see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
+a talk by [Chris Hines](https://github.com/ChrisHines).
+Also, please see
+[#63](https://github.com/go-kit/kit/issues/63),
+[#76](https://github.com/go-kit/kit/pull/76),
+[#131](https://github.com/go-kit/kit/issues/131),
+[#157](https://github.com/go-kit/kit/pull/157),
+[#164](https://github.com/go-kit/kit/issues/164), and
+[#252](https://github.com/go-kit/kit/pull/252)
+to review historical conversations about package log and the Logger interface.
+
+Value-add packages and suggestions,
+like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
+are of course welcome. Good proposals should
+
+- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
+- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
+- Be friendly to packages that accept only an unadorned log.Logger.
+
+## Benchmarks & comparisons
+
+There are a few Go logging benchmarks and comparisons that include Go kit's package log.
+
+- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
+- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log
diff --git a/vendor/github.com/go-kit/kit/log/doc.go b/vendor/github.com/go-kit/kit/log/doc.go
new file mode 100644
index 0000000..918c0af
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/doc.go
@@ -0,0 +1,116 @@
+// Package log provides a structured logger.
+//
+// Structured logging produces logs easily consumed later by humans or
+// machines. Humans might be interested in debugging errors, or tracing
+// specific requests. Machines might be interested in counting interesting
+// events, or aggregating information for off-line processing. In both cases,
+// it is important that the log messages are structured and actionable.
+// Package log is designed to encourage both of these best practices.
+//
+// Basic Usage
+//
+// The fundamental interface is Logger. Loggers create log events from
+// key/value data. The Logger interface has a single method, Log, which
+// accepts a sequence of alternating key/value pairs, which this package names
+// keyvals.
+//
+//    type Logger interface {
+//        Log(keyvals ...interface{}) error
+//    }
+//
+// Here is an example of a function using a Logger to create log events.
+//
+//    func RunTask(task Task, logger log.Logger) string {
+//        logger.Log("taskID", task.ID, "event", "starting task")
+//        ...
+//        logger.Log("taskID", task.ID, "event", "task complete")
+//    }
+//
+// The keys in the above example are "taskID" and "event". The values are
+// task.ID, "starting task", and "task complete". Every key is followed
+// immediately by its value.
+//
+// Keys are usually plain strings. Values may be any type that has a sensible
+// encoding in the chosen log format. With structured logging it is a good
+// idea to log simple values without formatting them. This practice allows
+// the chosen logger to encode values in the most appropriate way.
+//
+// Contextual Loggers
+//
+// A contextual logger stores keyvals that it includes in all log events.
+// Building appropriate contextual loggers reduces repetition and aids
+// consistency in the resulting log output. With and WithPrefix add context to
+// a logger. We can use With to improve the RunTask example.
+//
+//    func RunTask(task Task, logger log.Logger) string {
+//        logger = log.With(logger, "taskID", task.ID)
+//        logger.Log("event", "starting task")
+//        ...
+//        taskHelper(task.Cmd, logger)
+//        ...
+//        logger.Log("event", "task complete")
+//    }
+//
+// The improved version emits the same log events as the original for the
+// first and last calls to Log. Passing the contextual logger to taskHelper
+// enables each log event created by taskHelper to include the task.ID even
+// though taskHelper does not have access to that value. Using contextual
+// loggers this way simplifies producing log output that enables tracing the
+// life cycle of individual tasks. (See the Contextual example for the full
+// code of the above snippet.)
+//
+// Dynamic Contextual Values
+//
+// A Valuer function stored in a contextual logger generates a new value each
+// time an event is logged. The Valuer example demonstrates how this feature
+// works.
+//
+// Valuers provide the basis for consistently logging timestamps and source
+// code location. The log package defines several valuers for that purpose.
+// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
+// DefaultCaller. A common logger initialization sequence that ensures all log
+// entries contain a timestamp and source location looks like this:
+//
+//    logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
+//    logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
+//
+// Concurrent Safety
+//
+// Applications with multiple goroutines want each log event written to the
+// same logger to remain separate from other log events. Package log provides
+// two simple solutions for concurrent safe logging.
+//
+// NewSyncWriter wraps an io.Writer and serializes each call to its Write
+// method. Using a SyncWriter has the benefit that the smallest practical
+// portion of the logging logic is performed within a mutex, but it requires
+// the formatting Logger to make only one call to Write per log event.
+//
+// NewSyncLogger wraps any Logger and serializes each call to its Log method.
+// Using a SyncLogger has the benefit that it guarantees each log event is
+// handled atomically within the wrapped logger, but it typically serializes
+// both the formatting and output logic. Use a SyncLogger if the formatting
+// logger may perform multiple writes per log event.
+//
+// Error Handling
+//
+// This package relies on the practice of wrapping or decorating loggers with
+// other loggers to provide composable pieces of functionality. It also means
+// that Logger.Log must return an error because some
+// implementations—especially those that output log data to an io.Writer—may
+// encounter errors that cannot be handled locally. This in turn means that
+// Loggers that wrap other loggers should return errors from the wrapped
+// logger up the stack.
+//
+// Fortunately, the decorator pattern also provides a way to avoid the
+// necessity to check for errors every time an application calls Logger.Log.
+// An application required to panic whenever its Logger encounters
+// an error could initialize its logger as follows.
+//
+//    fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
+//    logger := log.LoggerFunc(func(keyvals ...interface{}) error {
+//        if err := fmtlogger.Log(keyvals...); err != nil {
+//            panic(err)
+//        }
+//        return nil
+//    })
+package log
diff --git a/vendor/github.com/go-kit/kit/log/json_logger.go b/vendor/github.com/go-kit/kit/log/json_logger.go
new file mode 100644
index 0000000..66094b4
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/json_logger.go
@@ -0,0 +1,89 @@
+package log
+
+import (
+	"encoding"
+	"encoding/json"
+	"fmt"
+	"io"
+	"reflect"
+)
+
+type jsonLogger struct {
+	io.Writer
+}
+
+// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
+// single JSON object. Each log event produces no more than one call to
+// w.Write. The passed Writer must be safe for concurrent use by multiple
+// goroutines if the returned Logger will be used concurrently.
+func NewJSONLogger(w io.Writer) Logger {
+	return &jsonLogger{w}
+}
+
+func (l *jsonLogger) Log(keyvals ...interface{}) error {
+	n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
+	m := make(map[string]interface{}, n)
+	for i := 0; i < len(keyvals); i += 2 {
+		k := keyvals[i]
+		var v interface{} = ErrMissingValue
+		if i+1 < len(keyvals) {
+			v = keyvals[i+1]
+		}
+		merge(m, k, v)
+	}
+	return json.NewEncoder(l.Writer).Encode(m)
+}
+
+func merge(dst map[string]interface{}, k, v interface{}) {
+	var key string
+	switch x := k.(type) {
+	case string:
+		key = x
+	case fmt.Stringer:
+		key = safeString(x)
+	default:
+		key = fmt.Sprint(x)
+	}
+
+	// We want json.Marshaler and encoding.TextMarshaller to take priority over
+	// err.Error() and v.String(). But json.Marshall (called later) does that by
+	// default so we force a no-op if it's one of those 2 case.
+	switch x := v.(type) {
+	case json.Marshaler:
+	case encoding.TextMarshaler:
+	case error:
+		v = safeError(x)
+	case fmt.Stringer:
+		v = safeString(x)
+	}
+
+	dst[key] = v
+}
+
+func safeString(str fmt.Stringer) (s string) {
+	defer func() {
+		if panicVal := recover(); panicVal != nil {
+			if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
+				s = "NULL"
+			} else {
+				panic(panicVal)
+			}
+		}
+	}()
+	s = str.String()
+	return
+}
+
+func safeError(err error) (s interface{}) {
+	defer func() {
+		if panicVal := recover(); panicVal != nil {
+			if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
+				s = nil
+			} else {
+				panic(panicVal)
+			}
+		}
+	}()
+	s = err.Error()
+	return
+}
diff --git a/vendor/github.com/go-kit/kit/log/level/doc.go b/vendor/github.com/go-kit/kit/log/level/doc.go
new file mode 100644
index 0000000..505d307
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/level/doc.go
@@ -0,0 +1,22 @@
+// Package level implements leveled logging on top of Go kit's log package. To
+// use the level package, create a logger as per normal in your func main, and
+// wrap it with level.NewFilter.
+//
+//    var logger log.Logger
+//    logger = log.NewLogfmtLogger(os.Stderr)
+//    logger = level.NewFilter(logger, level.AllowInfo()) // <--
+//    logger = log.With(logger, "ts", log.DefaultTimestampUTC)
+//
+// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
+// helper methods to emit leveled log events.
+//
+//    logger.Log("foo", "bar") // as normal, no level
+//    level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
+//    if value > 100 {
+//        level.Error(logger).Log("value", value)
+//    }
+//
+// NewFilter allows precise control over what happens when a log event is
+// emitted without a level key, or if a squelched level is used. Check the
+// Option functions for details.
+package level
diff --git a/vendor/github.com/go-kit/kit/log/level/level.go b/vendor/github.com/go-kit/kit/log/level/level.go
new file mode 100644
index 0000000..fceafc4
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/level/level.go
@@ -0,0 +1,205 @@
+package level
+
+import "github.com/go-kit/kit/log"
+
+// Error returns a logger that includes a Key/ErrorValue pair.
+func Error(logger log.Logger) log.Logger {
+	return log.WithPrefix(logger, Key(), ErrorValue())
+}
+
+// Warn returns a logger that includes a Key/WarnValue pair.
+func Warn(logger log.Logger) log.Logger {
+	return log.WithPrefix(logger, Key(), WarnValue())
+}
+
+// Info returns a logger that includes a Key/InfoValue pair.
+func Info(logger log.Logger) log.Logger {
+	return log.WithPrefix(logger, Key(), InfoValue())
+}
+
+// Debug returns a logger that includes a Key/DebugValue pair.
+func Debug(logger log.Logger) log.Logger {
+	return log.WithPrefix(logger, Key(), DebugValue())
+}
+
+// NewFilter wraps next and implements level filtering. See the commentary on
+// the Option functions for a detailed description of how to configure levels.
+// If no options are provided, all leveled log events created with Debug,
+// Info, Warn or Error helper methods are squelched and non-leveled log
+// events are passed to next unmodified.
+func NewFilter(next log.Logger, options ...Option) log.Logger {
+	l := &logger{
+		next: next,
+	}
+	for _, option := range options {
+		option(l)
+	}
+	return l
+}
+
+type logger struct {
+	next           log.Logger
+	allowed        level
+	squelchNoLevel bool
+	errNotAllowed  error
+	errNoLevel     error
+}
+
+func (l *logger) Log(keyvals ...interface{}) error {
+	var hasLevel, levelAllowed bool
+	for i := 1; i < len(keyvals); i += 2 {
+		if v, ok := keyvals[i].(*levelValue); ok {
+			hasLevel = true
+			levelAllowed = l.allowed&v.level != 0
+			break
+		}
+	}
+	if !hasLevel && l.squelchNoLevel {
+		return l.errNoLevel
+	}
+	if hasLevel && !levelAllowed {
+		return l.errNotAllowed
+	}
+	return l.next.Log(keyvals...)
+}
+
+// Option sets a parameter for the leveled logger.
+type Option func(*logger)
+
+// AllowAll is an alias for AllowDebug.
+func AllowAll() Option {
+	return AllowDebug()
+}
+
+// AllowDebug allows error, warn, info and debug level log events to pass.
+func AllowDebug() Option {
+	return allowed(levelError | levelWarn | levelInfo | levelDebug)
+}
+
+// AllowInfo allows error, warn and info level log events to pass.
+func AllowInfo() Option {
+	return allowed(levelError | levelWarn | levelInfo)
+}
+
+// AllowWarn allows error and warn level log events to pass.
+func AllowWarn() Option {
+	return allowed(levelError | levelWarn)
+}
+
+// AllowError allows only error level log events to pass.
+func AllowError() Option {
+	return allowed(levelError)
+}
+
+// AllowNone allows no leveled log events to pass.
+func AllowNone() Option {
+	return allowed(0)
+}
+
+func allowed(allowed level) Option {
+	return func(l *logger) { l.allowed = allowed }
+}
+
+// ErrNotAllowed sets the error to return from Log when it squelches a log
+// event disallowed by the configured Allow[Level] option. By default,
+// ErrNotAllowed is nil; in this case the log event is squelched with no
+// error.
+func ErrNotAllowed(err error) Option {
+	return func(l *logger) { l.errNotAllowed = err }
+}
+
+// SquelchNoLevel instructs Log to squelch log events with no level, so that
+// they don't proceed through to the wrapped logger. If SquelchNoLevel is set
+// to true and a log event is squelched in this way, the error value
+// configured with ErrNoLevel is returned to the caller.
+func SquelchNoLevel(squelch bool) Option {
+	return func(l *logger) { l.squelchNoLevel = squelch }
+}
+
+// ErrNoLevel sets the error to return from Log when it squelches a log event
+// with no level. By default, ErrNoLevel is nil; in this case the log event is
+// squelched with no error.
+func ErrNoLevel(err error) Option {
+	return func(l *logger) { l.errNoLevel = err }
+}
+
+// NewInjector wraps next and returns a logger that adds a Key/level pair to
+// the beginning of log events that don't already contain a level. In effect,
+// this gives a default level to logs without a level.
+func NewInjector(next log.Logger, level Value) log.Logger {
+	return &injector{
+		next:  next,
+		level: level,
+	}
+}
+
+type injector struct {
+	next  log.Logger
+	level interface{}
+}
+
+func (l *injector) Log(keyvals ...interface{}) error {
+	for i := 1; i < len(keyvals); i += 2 {
+		if _, ok := keyvals[i].(*levelValue); ok {
+			return l.next.Log(keyvals...)
+		}
+	}
+	kvs := make([]interface{}, len(keyvals)+2)
+	kvs[0], kvs[1] = key, l.level
+	copy(kvs[2:], keyvals)
+	return l.next.Log(kvs...)
+}
+
+// Value is the interface that each of the canonical level values implement.
+// It contains unexported methods that prevent types from other packages from
+// implementing it and guaranteeing that NewFilter can distinguish the levels
+// defined in this package from all other values.
+type Value interface {
+	String() string
+	levelVal()
+}
+
+// Key returns the unique key added to log events by the loggers in this
+// package.
+func Key() interface{} { return key }
+
+// ErrorValue returns the unique value added to log events by Error.
+func ErrorValue() Value { return errorValue }
+
+// WarnValue returns the unique value added to log events by Warn.
+func WarnValue() Value { return warnValue }
+
+// InfoValue returns the unique value added to log events by Info.
+func InfoValue() Value { return infoValue }
+
+// DebugValue returns the unique value added to log events by Warn.
+func DebugValue() Value { return debugValue }
+
+var (
+	// key is of type interface{} so that it allocates once during package
+	// initialization and avoids allocating every time the value is added to a
+	// []interface{} later.
+	key interface{} = "level"
+
+	errorValue = &levelValue{level: levelError, name: "error"}
+	warnValue  = &levelValue{level: levelWarn, name: "warn"}
+	infoValue  = &levelValue{level: levelInfo, name: "info"}
+	debugValue = &levelValue{level: levelDebug, name: "debug"}
+)
+
+type level byte
+
+const (
+	levelDebug level = 1 << iota
+	levelInfo
+	levelWarn
+	levelError
+)
+
+type levelValue struct {
+	name string
+	level
+}
+
+func (v *levelValue) String() string { return v.name }
+func (v *levelValue) levelVal()      {}
diff --git a/vendor/github.com/go-kit/kit/log/log.go b/vendor/github.com/go-kit/kit/log/log.go
new file mode 100644
index 0000000..66a9e2f
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/log.go
@@ -0,0 +1,135 @@
+package log
+
+import "errors"
+
+// Logger is the fundamental interface for all log operations. Log creates a
+// log event from keyvals, a variadic sequence of alternating keys and values.
+// Implementations must be safe for concurrent use by multiple goroutines. In
+// particular, any implementation of Logger that appends to keyvals or
+// modifies or retains any of its elements must make a copy first.
+type Logger interface {
+	Log(keyvals ...interface{}) error
+}
+
+// ErrMissingValue is appended to keyvals slices with odd length to substitute
+// the missing value.
+var ErrMissingValue = errors.New("(MISSING)")
+
+// With returns a new contextual logger with keyvals prepended to those passed
+// to calls to Log. If logger is also a contextual logger created by With or
+// WithPrefix, keyvals is appended to the existing context.
+//
+// The returned Logger replaces all value elements (odd indexes) containing a
+// Valuer with their generated value for each call to its Log method.
+func With(logger Logger, keyvals ...interface{}) Logger {
+	if len(keyvals) == 0 {
+		return logger
+	}
+	l := newContext(logger)
+	kvs := append(l.keyvals, keyvals...)
+	if len(kvs)%2 != 0 {
+		kvs = append(kvs, ErrMissingValue)
+	}
+	return &context{
+		logger: l.logger,
+		// Limiting the capacity of the stored keyvals ensures that a new
+		// backing array is created if the slice must grow in Log or With.
+		// Using the extra capacity without copying risks a data race that
+		// would violate the Logger interface contract.
+		keyvals:   kvs[:len(kvs):len(kvs)],
+		hasValuer: l.hasValuer || containsValuer(keyvals),
+	}
+}
+
+// WithPrefix returns a new contextual logger with keyvals prepended to those
+// passed to calls to Log. If logger is also a contextual logger created by
+// With or WithPrefix, keyvals is prepended to the existing context.
+//
+// The returned Logger replaces all value elements (odd indexes) containing a
+// Valuer with their generated value for each call to its Log method.
+func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
+	if len(keyvals) == 0 {
+		return logger
+	}
+	l := newContext(logger)
+	// Limiting the capacity of the stored keyvals ensures that a new
+	// backing array is created if the slice must grow in Log or With.
+	// Using the extra capacity without copying risks a data race that
+	// would violate the Logger interface contract.
+	n := len(l.keyvals) + len(keyvals)
+	if len(keyvals)%2 != 0 {
+		n++
+	}
+	kvs := make([]interface{}, 0, n)
+	kvs = append(kvs, keyvals...)
+	if len(kvs)%2 != 0 {
+		kvs = append(kvs, ErrMissingValue)
+	}
+	kvs = append(kvs, l.keyvals...)
+	return &context{
+		logger:    l.logger,
+		keyvals:   kvs,
+		hasValuer: l.hasValuer || containsValuer(keyvals),
+	}
+}
+
+// context is the Logger implementation returned by With and WithPrefix. It
+// wraps a Logger and holds keyvals that it includes in all log events. Its
+// Log method calls bindValues to generate values for each Valuer in the
+// context keyvals.
+//
+// A context must always have the same number of stack frames between calls to
+// its Log method and the eventual binding of Valuers to their value. This
+// requirement comes from the functional requirement to allow a context to
+// resolve application call site information for a Caller stored in the
+// context. To do this we must be able to predict the number of logging
+// functions on the stack when bindValues is called.
+//
+// Two implementation details provide the needed stack depth consistency.
+//
+//    1. newContext avoids introducing an additional layer when asked to
+//       wrap another context.
+//    2. With and WithPrefix avoid introducing an additional layer by
+//       returning a newly constructed context with a merged keyvals rather
+//       than simply wrapping the existing context.
+type context struct {
+	logger    Logger
+	keyvals   []interface{}
+	hasValuer bool
+}
+
+func newContext(logger Logger) *context {
+	if c, ok := logger.(*context); ok {
+		return c
+	}
+	return &context{logger: logger}
+}
+
+// Log replaces all value elements (odd indexes) containing a Valuer in the
+// stored context with their generated value, appends keyvals, and passes the
+// result to the wrapped Logger.
+func (l *context) Log(keyvals ...interface{}) error {
+	kvs := append(l.keyvals, keyvals...)
+	if len(kvs)%2 != 0 {
+		kvs = append(kvs, ErrMissingValue)
+	}
+	if l.hasValuer {
+		// If no keyvals were appended above then we must copy l.keyvals so
+		// that future log events will reevaluate the stored Valuers.
+		if len(keyvals) == 0 {
+			kvs = append([]interface{}{}, l.keyvals...)
+		}
+		bindValues(kvs[:len(l.keyvals)])
+	}
+	return l.logger.Log(kvs...)
+}
+
+// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
+// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
+// object that calls f.
+type LoggerFunc func(...interface{}) error
+
+// Log implements Logger by calling f(keyvals...).
+func (f LoggerFunc) Log(keyvals ...interface{}) error {
+	return f(keyvals...)
+}
diff --git a/vendor/github.com/go-kit/kit/log/logfmt_logger.go b/vendor/github.com/go-kit/kit/log/logfmt_logger.go
new file mode 100644
index 0000000..a003052
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/logfmt_logger.go
@@ -0,0 +1,62 @@
+package log
+
+import (
+	"bytes"
+	"io"
+	"sync"
+
+	"github.com/go-logfmt/logfmt"
+)
+
+type logfmtEncoder struct {
+	*logfmt.Encoder
+	buf bytes.Buffer
+}
+
+func (l *logfmtEncoder) Reset() {
+	l.Encoder.Reset()
+	l.buf.Reset()
+}
+
+var logfmtEncoderPool = sync.Pool{
+	New: func() interface{} {
+		var enc logfmtEncoder
+		enc.Encoder = logfmt.NewEncoder(&enc.buf)
+		return &enc
+	},
+}
+
+type logfmtLogger struct {
+	w io.Writer
+}
+
+// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
+// logfmt format. Each log event produces no more than one call to w.Write.
+// The passed Writer must be safe for concurrent use by multiple goroutines if
+// the returned Logger will be used concurrently.
+func NewLogfmtLogger(w io.Writer) Logger {
+	return &logfmtLogger{w}
+}
+
+func (l logfmtLogger) Log(keyvals ...interface{}) error {
+	enc := logfmtEncoderPool.Get().(*logfmtEncoder)
+	enc.Reset()
+	defer logfmtEncoderPool.Put(enc)
+
+	if err := enc.EncodeKeyvals(keyvals...); err != nil {
+		return err
+	}
+
+	// Add newline to the end of the buffer
+	if err := enc.EndRecord(); err != nil {
+		return err
+	}
+
+	// The Logger interface requires implementations to be safe for concurrent
+	// use by multiple goroutines. For this implementation that means making
+	// only one call to l.w.Write() for each call to Log.
+	if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/vendor/github.com/go-kit/kit/log/nop_logger.go b/vendor/github.com/go-kit/kit/log/nop_logger.go
new file mode 100644
index 0000000..1047d62
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/nop_logger.go
@@ -0,0 +1,8 @@
+package log
+
+type nopLogger struct{}
+
+// NewNopLogger returns a logger that doesn't do anything.
+func NewNopLogger() Logger { return nopLogger{} }
+
+func (nopLogger) Log(...interface{}) error { return nil }
diff --git a/vendor/github.com/go-kit/kit/log/stdlib.go b/vendor/github.com/go-kit/kit/log/stdlib.go
new file mode 100644
index 0000000..ff96b5d
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/stdlib.go
@@ -0,0 +1,116 @@
+package log
+
+import (
+	"io"
+	"log"
+	"regexp"
+	"strings"
+)
+
+// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
+// designed to be passed to a Go kit logger as the writer, for cases where
+// it's necessary to redirect all Go kit log output to the stdlib logger.
+//
+// If you have any choice in the matter, you shouldn't use this. Prefer to
+// redirect the stdlib log to the Go kit logger via NewStdlibAdapter.
+type StdlibWriter struct{}
+
+// Write implements io.Writer.
+func (w StdlibWriter) Write(p []byte) (int, error) {
+	log.Print(strings.TrimSpace(string(p)))
+	return len(p), nil
+}
+
+// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
+// logger's SetOutput. It will extract date/timestamps, filenames, and
+// messages, and place them under relevant keys.
+type StdlibAdapter struct {
+	Logger
+	timestampKey string
+	fileKey      string
+	messageKey   string
+}
+
+// StdlibAdapterOption sets a parameter for the StdlibAdapter.
+type StdlibAdapterOption func(*StdlibAdapter)
+
+// TimestampKey sets the key for the timestamp field. By default, it's "ts".
+func TimestampKey(key string) StdlibAdapterOption {
+	return func(a *StdlibAdapter) { a.timestampKey = key }
+}
+
+// FileKey sets the key for the file and line field. By default, it's "caller".
+func FileKey(key string) StdlibAdapterOption {
+	return func(a *StdlibAdapter) { a.fileKey = key }
+}
+
+// MessageKey sets the key for the actual log message. By default, it's "msg".
+func MessageKey(key string) StdlibAdapterOption {
+	return func(a *StdlibAdapter) { a.messageKey = key }
+}
+
+// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
+// logger. It's designed to be passed to log.SetOutput.
+func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
+	a := StdlibAdapter{
+		Logger:       logger,
+		timestampKey: "ts",
+		fileKey:      "caller",
+		messageKey:   "msg",
+	}
+	for _, option := range options {
+		option(&a)
+	}
+	return a
+}
+
+func (a StdlibAdapter) Write(p []byte) (int, error) {
+	result := subexps(p)
+	keyvals := []interface{}{}
+	var timestamp string
+	if date, ok := result["date"]; ok && date != "" {
+		timestamp = date
+	}
+	if time, ok := result["time"]; ok && time != "" {
+		if timestamp != "" {
+			timestamp += " "
+		}
+		timestamp += time
+	}
+	if timestamp != "" {
+		keyvals = append(keyvals, a.timestampKey, timestamp)
+	}
+	if file, ok := result["file"]; ok && file != "" {
+		keyvals = append(keyvals, a.fileKey, file)
+	}
+	if msg, ok := result["msg"]; ok {
+		keyvals = append(keyvals, a.messageKey, msg)
+	}
+	if err := a.Logger.Log(keyvals...); err != nil {
+		return 0, err
+	}
+	return len(p), nil
+}
+
+const (
+	logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
+	logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
+	logRegexpFile = `(?P<file>.+?:[0-9]+)?`
+	logRegexpMsg  = `(: )?(?P<msg>.*)`
+)
+
+var (
+	logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
+)
+
+func subexps(line []byte) map[string]string {
+	m := logRegexp.FindSubmatch(line)
+	if len(m) < len(logRegexp.SubexpNames()) {
+		return map[string]string{}
+	}
+	result := map[string]string{}
+	for i, name := range logRegexp.SubexpNames() {
+		result[name] = string(m[i])
+	}
+	return result
+}
diff --git a/vendor/github.com/go-kit/kit/log/sync.go b/vendor/github.com/go-kit/kit/log/sync.go
new file mode 100644
index 0000000..c07cdfa
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/sync.go
@@ -0,0 +1,116 @@
+package log
+
+import (
+	"io"
+	"sync"
+	"sync/atomic"
+)
+
+// SwapLogger wraps another logger that may be safely replaced while other
+// goroutines use the SwapLogger concurrently. The zero value for a SwapLogger
+// will discard all log events without error.
+//
+// SwapLogger serves well as a package global logger that can be changed by
+// importers.
+type SwapLogger struct {
+	logger atomic.Value
+}
+
+type loggerStruct struct {
+	Logger
+}
+
+// Log implements the Logger interface by forwarding keyvals to the currently
+// wrapped logger. It does not log anything if the wrapped logger is nil.
+func (l *SwapLogger) Log(keyvals ...interface{}) error {
+	s, ok := l.logger.Load().(loggerStruct)
+	if !ok || s.Logger == nil {
+		return nil
+	}
+	return s.Log(keyvals...)
+}
+
+// Swap replaces the currently wrapped logger with logger. Swap may be called
+// concurrently with calls to Log from other goroutines.
+func (l *SwapLogger) Swap(logger Logger) {
+	l.logger.Store(loggerStruct{logger})
+}
+
+// NewSyncWriter returns a new writer that is safe for concurrent use by
+// multiple goroutines. Writes to the returned writer are passed on to w. If
+// another write is already in progress, the calling goroutine blocks until
+// the writer is available.
+//
+// If w implements the following interface, so does the returned writer.
+//
+//    interface {
+//        Fd() uintptr
+//    }
+func NewSyncWriter(w io.Writer) io.Writer {
+	switch w := w.(type) {
+	case fdWriter:
+		return &fdSyncWriter{fdWriter: w}
+	default:
+		return &syncWriter{Writer: w}
+	}
+}
+
+// syncWriter synchronizes concurrent writes to an io.Writer.
+type syncWriter struct {
+	sync.Mutex
+	io.Writer
+}
+
+// Write writes p to the underlying io.Writer. If another write is already in
+// progress, the calling goroutine blocks until the syncWriter is available.
+func (w *syncWriter) Write(p []byte) (n int, err error) {
+	w.Lock()
+	n, err = w.Writer.Write(p)
+	w.Unlock()
+	return n, err
+}
+
+// fdWriter is an io.Writer that also has an Fd method. The most common
+// example of an fdWriter is an *os.File.
+type fdWriter interface {
+	io.Writer
+	Fd() uintptr
+}
+
+// fdSyncWriter synchronizes concurrent writes to an fdWriter.
+type fdSyncWriter struct {
+	sync.Mutex
+	fdWriter
+}
+
+// Write writes p to the underlying io.Writer. If another write is already in
+// progress, the calling goroutine blocks until the fdSyncWriter is available.
+func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
+	w.Lock()
+	n, err = w.fdWriter.Write(p)
+	w.Unlock()
+	return n, err
+}
+
+// syncLogger provides concurrent safe logging for another Logger.
+type syncLogger struct {
+	mu     sync.Mutex
+	logger Logger
+}
+
+// NewSyncLogger returns a logger that synchronizes concurrent use of the
+// wrapped logger. When multiple goroutines use the SyncLogger concurrently
+// only one goroutine will be allowed to log to the wrapped logger at a time.
+// The other goroutines will block until the logger is available.
+func NewSyncLogger(logger Logger) Logger {
+	return &syncLogger{logger: logger}
+}
+
+// Log logs keyvals to the underlying Logger. If another log is already in
+// progress, the calling goroutine blocks until the syncLogger is available.
+func (l *syncLogger) Log(keyvals ...interface{}) error {
+	l.mu.Lock()
+	err := l.logger.Log(keyvals...)
+	l.mu.Unlock()
+	return err
+}
diff --git a/vendor/github.com/go-kit/kit/log/value.go b/vendor/github.com/go-kit/kit/log/value.go
new file mode 100644
index 0000000..1486f14
--- /dev/null
+++ b/vendor/github.com/go-kit/kit/log/value.go
@@ -0,0 +1,110 @@
+package log
+
+import (
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// A Valuer generates a log value. When passed to With or WithPrefix in a
+// value element (odd indexes), it represents a dynamic value which is re-
+// evaluated with each log event.
+type Valuer func() interface{}
+
+// bindValues replaces all value elements (odd indexes) containing a Valuer
+// with their generated value.
+func bindValues(keyvals []interface{}) {
+	for i := 1; i < len(keyvals); i += 2 {
+		if v, ok := keyvals[i].(Valuer); ok {
+			keyvals[i] = v()
+		}
+	}
+}
+
+// containsValuer returns true if any of the value elements (odd indexes)
+// contain a Valuer.
+func containsValuer(keyvals []interface{}) bool {
+	for i := 1; i < len(keyvals); i += 2 {
+		if _, ok := keyvals[i].(Valuer); ok {
+			return true
+		}
+	}
+	return false
+}
+
+// Timestamp returns a timestamp Valuer. It invokes the t function to get the
+// time; unless you are doing something tricky, pass time.Now.
+//
+// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
+// are TimestampFormats that use the RFC3339Nano format.
+func Timestamp(t func() time.Time) Valuer {
+	return func() interface{} { return t() }
+}
+
+// TimestampFormat returns a timestamp Valuer with a custom time format. It
+// invokes the t function to get the time to format; unless you are doing
+// something tricky, pass time.Now. The layout string is passed to
+// Time.Format.
+//
+// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
+// are TimestampFormats that use the RFC3339Nano format.
+func TimestampFormat(t func() time.Time, layout string) Valuer {
+	return func() interface{} {
+		return timeFormat{
+			time:   t(),
+			layout: layout,
+		}
+	}
+}
+
+// A timeFormat represents an instant in time and a layout used when
+// marshaling to a text format.
+type timeFormat struct {
+	time   time.Time
+	layout string
+}
+
+func (tf timeFormat) String() string {
+	return tf.time.Format(tf.layout)
+}
+
+// MarshalText implements encoding.TextMarshaller.
+func (tf timeFormat) MarshalText() (text []byte, err error) {
+	// The following code adapted from the standard library time.Time.Format
+	// method. Using the same undocumented magic constant to extend the size
+	// of the buffer as seen there.
+	b := make([]byte, 0, len(tf.layout)+10)
+	b = tf.time.AppendFormat(b, tf.layout)
+	return b, nil
+}
+
+// Caller returns a Valuer that returns a file and line from a specified depth
+// in the callstack. Users will probably want to use DefaultCaller.
+func Caller(depth int) Valuer {
+	return func() interface{} {
+		_, file, line, _ := runtime.Caller(depth)
+		idx := strings.LastIndexByte(file, '/')
+		// using idx+1 below handles both of following cases:
+		// idx == -1 because no "/" was found, or
+		// idx >= 0 and we want to start at the character after the found "/".
+		return file[idx+1:] + ":" + strconv.Itoa(line)
+	}
+}
+
+var (
+	// DefaultTimestamp is a Valuer that returns the current wallclock time,
+	// respecting time zones, when bound.
+	DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
+
+	// DefaultTimestampUTC is a Valuer that returns the current time in UTC
+	// when bound.
+	DefaultTimestampUTC = TimestampFormat(
+		func() time.Time { return time.Now().UTC() },
+		time.RFC3339Nano,
+	)
+
+	// DefaultCaller is a Valuer that returns the file and line where the Log
+	// method was invoked. It can only be used with log.With.
+	DefaultCaller = Caller(3)
+)
diff --git a/vendor/github.com/go-logfmt/logfmt/.gitignore b/vendor/github.com/go-logfmt/logfmt/.gitignore
new file mode 100644
index 0000000..320e53e
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/.gitignore
@@ -0,0 +1,4 @@
+_testdata/
+_testdata2/
+logfmt-fuzz.zip
+logfmt.test.exe
diff --git a/vendor/github.com/go-logfmt/logfmt/.travis.yml b/vendor/github.com/go-logfmt/logfmt/.travis.yml
new file mode 100644
index 0000000..25976da
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/.travis.yml
@@ -0,0 +1,16 @@
+language: go
+sudo: false
+go:
+  - "1.7.x"
+  - "1.8.x"
+  - "1.9.x"
+  - "1.10.x"
+  - "1.11.x"
+  - "tip"
+
+before_install:
+  - go get github.com/mattn/goveralls
+  - go get golang.org/x/tools/cmd/cover
+
+script:
+  - goveralls -service=travis-ci
diff --git a/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md b/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
new file mode 100644
index 0000000..3455b8e
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
@@ -0,0 +1,41 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [0.4.0] - 2018-11-21
+
+### Added
+- Go module support by [@ChrisHines]
+- CHANGELOG by [@ChrisHines]
+
+### Changed
+- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
+- On panic while printing, attempt to print panic value by [@bboreham]
+
+## [0.3.0] - 2016-11-15
+### Added
+- Pool buffers for quoted strings and byte slices by [@nussjustin]
+### Fixed
+- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
+
+## [0.2.0] - 2016-05-08
+### Added
+- Encoder.EncodeKeyvals by [@ChrisHines]
+
+## [0.1.0] - 2016-03-28
+### Added
+- Encoder by [@ChrisHines]
+- Decoder by [@ChrisHines]
+- MarshalKeyvals by [@ChrisHines]
+
+[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
+[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
+[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
+[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
+
+[@ChrisHines]: https://github.com/ChrisHines
+[@bboreham]: https://github.com/bboreham
+[@judwhite]: https://github.com/judwhite
+[@nussjustin]: https://github.com/nussjustin
diff --git a/vendor/github.com/go-logfmt/logfmt/LICENSE b/vendor/github.com/go-logfmt/logfmt/LICENSE
new file mode 100644
index 0000000..c026508
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 go-logfmt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/go-logfmt/logfmt/README.md b/vendor/github.com/go-logfmt/logfmt/README.md
new file mode 100644
index 0000000..3a8f10b
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/README.md
@@ -0,0 +1,33 @@
+[![GoDoc](https://godoc.org/github.com/go-logfmt/logfmt?status.svg)](https://godoc.org/github.com/go-logfmt/logfmt)
+[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt)
+[![TravisCI](https://travis-ci.org/go-logfmt/logfmt.svg?branch=master)](https://travis-ci.org/go-logfmt/logfmt)
+[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master)
+
+# logfmt
+
+Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
+format](https://brandur.org/logfmt). It provides an API similar to
+[encoding/json](http://golang.org/pkg/encoding/json/) and
+[encoding/xml](http://golang.org/pkg/encoding/xml/).
+
+The logfmt format was first documented by Brandur Leach in [this
+article](https://brandur.org/logfmt). The format has not been formally
+standardized. The most authoritative public specification to date has been the
+documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt)
+written by Blake Mizerany and Keith Rarick.
+
+## Goals
+
+This project attempts to conform as closely as possible to the prior art, while
+also removing ambiguity where necessary to provide well behaved encoder and
+decoder implementations.
+
+## Non-goals
+
+This project does not attempt to formally standardize the logfmt format. In the
+event that logfmt is standardized this project would take conforming to the
+standard as a goal.
+
+## Versioning
+
+Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'.
diff --git a/vendor/github.com/go-logfmt/logfmt/decode.go b/vendor/github.com/go-logfmt/logfmt/decode.go
new file mode 100644
index 0000000..04e0eff
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/decode.go
@@ -0,0 +1,237 @@
+package logfmt
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"unicode/utf8"
+)
+
+// A Decoder reads and decodes logfmt records from an input stream.
+type Decoder struct {
+	pos     int
+	key     []byte
+	value   []byte
+	lineNum int
+	s       *bufio.Scanner
+	err     error
+}
+
+// NewDecoder returns a new decoder that reads from r.
+//
+// The decoder introduces its own buffering and may read data from r beyond
+// the logfmt records requested.
+func NewDecoder(r io.Reader) *Decoder {
+	dec := &Decoder{
+		s: bufio.NewScanner(r),
+	}
+	return dec
+}
+
+// ScanRecord advances the Decoder to the next record, which can then be
+// parsed with the ScanKeyval method. It returns false when decoding stops,
+// either by reaching the end of the input or an error. After ScanRecord
+// returns false, the Err method will return any error that occurred during
+// decoding, except that if it was io.EOF, Err will return nil.
+func (dec *Decoder) ScanRecord() bool {
+	if dec.err != nil {
+		return false
+	}
+	if !dec.s.Scan() {
+		dec.err = dec.s.Err()
+		return false
+	}
+	dec.lineNum++
+	dec.pos = 0
+	return true
+}
+
+// ScanKeyval advances the Decoder to the next key/value pair of the current
+// record, which can then be retrieved with the Key and Value methods. It
+// returns false when decoding stops, either by reaching the end of the
+// current record or an error.
+func (dec *Decoder) ScanKeyval() bool {
+	dec.key, dec.value = nil, nil
+	if dec.err != nil {
+		return false
+	}
+
+	line := dec.s.Bytes()
+
+	// garbage
+	for p, c := range line[dec.pos:] {
+		if c > ' ' {
+			dec.pos += p
+			goto key
+		}
+	}
+	dec.pos = len(line)
+	return false
+
+key:
+	const invalidKeyError = "invalid key"
+
+	start, multibyte := dec.pos, false
+	for p, c := range line[dec.pos:] {
+		switch {
+		case c == '=':
+			dec.pos += p
+			if dec.pos > start {
+				dec.key = line[start:dec.pos]
+				if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
+					dec.syntaxError(invalidKeyError)
+					return false
+				}
+			}
+			if dec.key == nil {
+				dec.unexpectedByte(c)
+				return false
+			}
+			goto equal
+		case c == '"':
+			dec.pos += p
+			dec.unexpectedByte(c)
+			return false
+		case c <= ' ':
+			dec.pos += p
+			if dec.pos > start {
+				dec.key = line[start:dec.pos]
+				if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
+					dec.syntaxError(invalidKeyError)
+					return false
+				}
+			}
+			return true
+		case c >= utf8.RuneSelf:
+			multibyte = true
+		}
+	}
+	dec.pos = len(line)
+	if dec.pos > start {
+		dec.key = line[start:dec.pos]
+		if multibyte && bytes.IndexRune(dec.key, utf8.RuneError) != -1 {
+			dec.syntaxError(invalidKeyError)
+			return false
+		}
+	}
+	return true
+
+equal:
+	dec.pos++
+	if dec.pos >= len(line) {
+		return true
+	}
+	switch c := line[dec.pos]; {
+	case c <= ' ':
+		return true
+	case c == '"':
+		goto qvalue
+	}
+
+	// value
+	start = dec.pos
+	for p, c := range line[dec.pos:] {
+		switch {
+		case c == '=' || c == '"':
+			dec.pos += p
+			dec.unexpectedByte(c)
+			return false
+		case c <= ' ':
+			dec.pos += p
+			if dec.pos > start {
+				dec.value = line[start:dec.pos]
+			}
+			return true
+		}
+	}
+	dec.pos = len(line)
+	if dec.pos > start {
+		dec.value = line[start:dec.pos]
+	}
+	return true
+
+qvalue:
+	const (
+		untermQuote  = "unterminated quoted value"
+		invalidQuote = "invalid quoted value"
+	)
+
+	hasEsc, esc := false, false
+	start = dec.pos
+	for p, c := range line[dec.pos+1:] {
+		switch {
+		case esc:
+			esc = false
+		case c == '\\':
+			hasEsc, esc = true, true
+		case c == '"':
+			dec.pos += p + 2
+			if hasEsc {
+				v, ok := unquoteBytes(line[start:dec.pos])
+				if !ok {
+					dec.syntaxError(invalidQuote)
+					return false
+				}
+				dec.value = v
+			} else {
+				start++
+				end := dec.pos - 1
+				if end > start {
+					dec.value = line[start:end]
+				}
+			}
+			return true
+		}
+	}
+	dec.pos = len(line)
+	dec.syntaxError(untermQuote)
+	return false
+}
+
+// Key returns the most recent key found by a call to ScanKeyval. The returned
+// slice may point to internal buffers and is only valid until the next call
+// to ScanRecord.  It does no allocation.
+func (dec *Decoder) Key() []byte {
+	return dec.key
+}
+
+// Value returns the most recent value found by a call to ScanKeyval. The
+// returned slice may point to internal buffers and is only valid until the
+// next call to ScanRecord.  It does no allocation when the value has no
+// escape sequences.
+func (dec *Decoder) Value() []byte {
+	return dec.value
+}
+
+// Err returns the first non-EOF error that was encountered by the Scanner.
+func (dec *Decoder) Err() error {
+	return dec.err
+}
+
+func (dec *Decoder) syntaxError(msg string) {
+	dec.err = &SyntaxError{
+		Msg:  msg,
+		Line: dec.lineNum,
+		Pos:  dec.pos + 1,
+	}
+}
+
+func (dec *Decoder) unexpectedByte(c byte) {
+	dec.err = &SyntaxError{
+		Msg:  fmt.Sprintf("unexpected %q", c),
+		Line: dec.lineNum,
+		Pos:  dec.pos + 1,
+	}
+}
+
+// A SyntaxError represents a syntax error in the logfmt input stream.
+type SyntaxError struct {
+	Msg  string
+	Line int
+	Pos  int
+}
+
+func (e *SyntaxError) Error() string {
+	return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg)
+}
diff --git a/vendor/github.com/go-logfmt/logfmt/doc.go b/vendor/github.com/go-logfmt/logfmt/doc.go
new file mode 100644
index 0000000..378e9ad
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/doc.go
@@ -0,0 +1,6 @@
+// Package logfmt implements utilities to marshal and unmarshal data in the
+// logfmt format. The logfmt format records key/value pairs in a way that
+// balances readability for humans and simplicity of computer parsing. It is
+// most commonly used as a more human friendly alternative to JSON for
+// structured logging.
+package logfmt
diff --git a/vendor/github.com/go-logfmt/logfmt/encode.go b/vendor/github.com/go-logfmt/logfmt/encode.go
new file mode 100644
index 0000000..4ea9d23
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/encode.go
@@ -0,0 +1,322 @@
+package logfmt
+
+import (
+	"bytes"
+	"encoding"
+	"errors"
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+	"unicode/utf8"
+)
+
+// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence
+// of alternating keys and values.
+func MarshalKeyvals(keyvals ...interface{}) ([]byte, error) {
+	buf := &bytes.Buffer{}
+	if err := NewEncoder(buf).EncodeKeyvals(keyvals...); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+// An Encoder writes logfmt data to an output stream.
+type Encoder struct {
+	w       io.Writer
+	scratch bytes.Buffer
+	needSep bool
+}
+
+// NewEncoder returns a new encoder that writes to w.
+func NewEncoder(w io.Writer) *Encoder {
+	return &Encoder{
+		w: w,
+	}
+}
+
+var (
+	space   = []byte(" ")
+	equals  = []byte("=")
+	newline = []byte("\n")
+	null    = []byte("null")
+)
+
+// EncodeKeyval writes the logfmt encoding of key and value to the stream. A
+// single space is written before the second and subsequent keys in a record.
+// Nothing is written if a non-nil error is returned.
+func (enc *Encoder) EncodeKeyval(key, value interface{}) error {
+	enc.scratch.Reset()
+	if enc.needSep {
+		if _, err := enc.scratch.Write(space); err != nil {
+			return err
+		}
+	}
+	if err := writeKey(&enc.scratch, key); err != nil {
+		return err
+	}
+	if _, err := enc.scratch.Write(equals); err != nil {
+		return err
+	}
+	if err := writeValue(&enc.scratch, value); err != nil {
+		return err
+	}
+	_, err := enc.w.Write(enc.scratch.Bytes())
+	enc.needSep = true
+	return err
+}
+
+// EncodeKeyvals writes the logfmt encoding of keyvals to the stream. Keyvals
+// is a variadic sequence of alternating keys and values. Keys of unsupported
+// type are skipped along with their corresponding value. Values of
+// unsupported type or that cause a MarshalerError are replaced by their error
+// but do not cause EncodeKeyvals to return an error. If a non-nil error is
+// returned some key/value pairs may not have be written.
+func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error {
+	if len(keyvals) == 0 {
+		return nil
+	}
+	if len(keyvals)%2 == 1 {
+		keyvals = append(keyvals, nil)
+	}
+	for i := 0; i < len(keyvals); i += 2 {
+		k, v := keyvals[i], keyvals[i+1]
+		err := enc.EncodeKeyval(k, v)
+		if err == ErrUnsupportedKeyType {
+			continue
+		}
+		if _, ok := err.(*MarshalerError); ok || err == ErrUnsupportedValueType {
+			v = err
+			err = enc.EncodeKeyval(k, v)
+		}
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// MarshalerError represents an error encountered while marshaling a value.
+type MarshalerError struct {
+	Type reflect.Type
+	Err  error
+}
+
+func (e *MarshalerError) Error() string {
+	return "error marshaling value of type " + e.Type.String() + ": " + e.Err.Error()
+}
+
+// ErrNilKey is returned by Marshal functions and Encoder methods if a key is
+// a nil interface or pointer value.
+var ErrNilKey = errors.New("nil key")
+
+// ErrInvalidKey is returned by Marshal functions and Encoder methods if, after
+// dropping invalid runes, a key is empty.
+var ErrInvalidKey = errors.New("invalid key")
+
+// ErrUnsupportedKeyType is returned by Encoder methods if a key has an
+// unsupported type.
+var ErrUnsupportedKeyType = errors.New("unsupported key type")
+
+// ErrUnsupportedValueType is returned by Encoder methods if a value has an
+// unsupported type.
+var ErrUnsupportedValueType = errors.New("unsupported value type")
+
+func writeKey(w io.Writer, key interface{}) error {
+	if key == nil {
+		return ErrNilKey
+	}
+
+	switch k := key.(type) {
+	case string:
+		return writeStringKey(w, k)
+	case []byte:
+		if k == nil {
+			return ErrNilKey
+		}
+		return writeBytesKey(w, k)
+	case encoding.TextMarshaler:
+		kb, err := safeMarshal(k)
+		if err != nil {
+			return err
+		}
+		if kb == nil {
+			return ErrNilKey
+		}
+		return writeBytesKey(w, kb)
+	case fmt.Stringer:
+		ks, ok := safeString(k)
+		if !ok {
+			return ErrNilKey
+		}
+		return writeStringKey(w, ks)
+	default:
+		rkey := reflect.ValueOf(key)
+		switch rkey.Kind() {
+		case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
+			return ErrUnsupportedKeyType
+		case reflect.Ptr:
+			if rkey.IsNil() {
+				return ErrNilKey
+			}
+			return writeKey(w, rkey.Elem().Interface())
+		}
+		return writeStringKey(w, fmt.Sprint(k))
+	}
+}
+
+// keyRuneFilter returns r for all valid key runes, and -1 for all invalid key
+// runes. When used as the mapping function for strings.Map and bytes.Map
+// functions it causes them to remove invalid key runes from strings or byte
+// slices respectively.
+func keyRuneFilter(r rune) rune {
+	if r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError {
+		return -1
+	}
+	return r
+}
+
+func writeStringKey(w io.Writer, key string) error {
+	k := strings.Map(keyRuneFilter, key)
+	if k == "" {
+		return ErrInvalidKey
+	}
+	_, err := io.WriteString(w, k)
+	return err
+}
+
+func writeBytesKey(w io.Writer, key []byte) error {
+	k := bytes.Map(keyRuneFilter, key)
+	if len(k) == 0 {
+		return ErrInvalidKey
+	}
+	_, err := w.Write(k)
+	return err
+}
+
+func writeValue(w io.Writer, value interface{}) error {
+	switch v := value.(type) {
+	case nil:
+		return writeBytesValue(w, null)
+	case string:
+		return writeStringValue(w, v, true)
+	case []byte:
+		return writeBytesValue(w, v)
+	case encoding.TextMarshaler:
+		vb, err := safeMarshal(v)
+		if err != nil {
+			return err
+		}
+		if vb == nil {
+			vb = null
+		}
+		return writeBytesValue(w, vb)
+	case error:
+		se, ok := safeError(v)
+		return writeStringValue(w, se, ok)
+	case fmt.Stringer:
+		ss, ok := safeString(v)
+		return writeStringValue(w, ss, ok)
+	default:
+		rvalue := reflect.ValueOf(value)
+		switch rvalue.Kind() {
+		case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
+			return ErrUnsupportedValueType
+		case reflect.Ptr:
+			if rvalue.IsNil() {
+				return writeBytesValue(w, null)
+			}
+			return writeValue(w, rvalue.Elem().Interface())
+		}
+		return writeStringValue(w, fmt.Sprint(v), true)
+	}
+}
+
+func needsQuotedValueRune(r rune) bool {
+	return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError
+}
+
+func writeStringValue(w io.Writer, value string, ok bool) error {
+	var err error
+	if ok && value == "null" {
+		_, err = io.WriteString(w, `"null"`)
+	} else if strings.IndexFunc(value, needsQuotedValueRune) != -1 {
+		_, err = writeQuotedString(w, value)
+	} else {
+		_, err = io.WriteString(w, value)
+	}
+	return err
+}
+
+func writeBytesValue(w io.Writer, value []byte) error {
+	var err error
+	if bytes.IndexFunc(value, needsQuotedValueRune) != -1 {
+		_, err = writeQuotedBytes(w, value)
+	} else {
+		_, err = w.Write(value)
+	}
+	return err
+}
+
+// EndRecord writes a newline character to the stream and resets the encoder
+// to the beginning of a new record.
+func (enc *Encoder) EndRecord() error {
+	_, err := enc.w.Write(newline)
+	if err == nil {
+		enc.needSep = false
+	}
+	return err
+}
+
+// Reset resets the encoder to the beginning of a new record.
+func (enc *Encoder) Reset() {
+	enc.needSep = false
+}
+
+func safeError(err error) (s string, ok bool) {
+	defer func() {
+		if panicVal := recover(); panicVal != nil {
+			if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
+				s, ok = "null", false
+			} else {
+				s, ok = fmt.Sprintf("PANIC:%v", panicVal), false
+			}
+		}
+	}()
+	s, ok = err.Error(), true
+	return
+}
+
+func safeString(str fmt.Stringer) (s string, ok bool) {
+	defer func() {
+		if panicVal := recover(); panicVal != nil {
+			if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
+				s, ok = "null", false
+			} else {
+				s, ok = fmt.Sprintf("PANIC:%v", panicVal), true
+			}
+		}
+	}()
+	s, ok = str.String(), true
+	return
+}
+
+func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) {
+	defer func() {
+		if panicVal := recover(); panicVal != nil {
+			if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() {
+				b, err = nil, nil
+			} else {
+				b, err = nil, fmt.Errorf("panic when marshalling: %s", panicVal)
+			}
+		}
+	}()
+	b, err = tm.MarshalText()
+	if err != nil {
+		return nil, &MarshalerError{
+			Type: reflect.TypeOf(tm),
+			Err:  err,
+		}
+	}
+	return
+}
diff --git a/vendor/github.com/go-logfmt/logfmt/fuzz.go b/vendor/github.com/go-logfmt/logfmt/fuzz.go
new file mode 100644
index 0000000..6553b35
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/fuzz.go
@@ -0,0 +1,126 @@
+// +build gofuzz
+
+package logfmt
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"reflect"
+
+	kr "github.com/kr/logfmt"
+)
+
+// Fuzz checks reserialized data matches
+func Fuzz(data []byte) int {
+	parsed, err := parse(data)
+	if err != nil {
+		return 0
+	}
+	var w1 bytes.Buffer
+	if err = write(parsed, &w1); err != nil {
+		panic(err)
+	}
+	parsed, err = parse(w1.Bytes())
+	if err != nil {
+		panic(err)
+	}
+	var w2 bytes.Buffer
+	if err = write(parsed, &w2); err != nil {
+		panic(err)
+	}
+	if !bytes.Equal(w1.Bytes(), w2.Bytes()) {
+		panic(fmt.Sprintf("reserialized data does not match:\n%q\n%q\n", w1.Bytes(), w2.Bytes()))
+	}
+	return 1
+}
+
+// FuzzVsKR checks go-logfmt/logfmt against kr/logfmt
+func FuzzVsKR(data []byte) int {
+	parsed, err := parse(data)
+	parsedKR, errKR := parseKR(data)
+
+	// github.com/go-logfmt/logfmt is a stricter parser. It returns errors for
+	// more inputs than github.com/kr/logfmt. Ignore any inputs that have a
+	// stict error.
+	if err != nil {
+		return 0
+	}
+
+	// Fail if the more forgiving parser finds an error not found by the
+	// stricter parser.
+	if errKR != nil {
+		panic(fmt.Sprintf("unmatched error: %v", errKR))
+	}
+
+	if !reflect.DeepEqual(parsed, parsedKR) {
+		panic(fmt.Sprintf("parsers disagree:\n%+v\n%+v\n", parsed, parsedKR))
+	}
+	return 1
+}
+
+type kv struct {
+	k, v []byte
+}
+
+func parse(data []byte) ([][]kv, error) {
+	var got [][]kv
+	dec := NewDecoder(bytes.NewReader(data))
+	for dec.ScanRecord() {
+		var kvs []kv
+		for dec.ScanKeyval() {
+			kvs = append(kvs, kv{dec.Key(), dec.Value()})
+		}
+		got = append(got, kvs)
+	}
+	return got, dec.Err()
+}
+
+func parseKR(data []byte) ([][]kv, error) {
+	var (
+		s   = bufio.NewScanner(bytes.NewReader(data))
+		err error
+		h   saveHandler
+		got [][]kv
+	)
+	for err == nil && s.Scan() {
+		h.kvs = nil
+		err = kr.Unmarshal(s.Bytes(), &h)
+		got = append(got, h.kvs)
+	}
+	if err == nil {
+		err = s.Err()
+	}
+	return got, err
+}
+
+type saveHandler struct {
+	kvs []kv
+}
+
+func (h *saveHandler) HandleLogfmt(key, val []byte) error {
+	if len(key) == 0 {
+		key = nil
+	}
+	if len(val) == 0 {
+		val = nil
+	}
+	h.kvs = append(h.kvs, kv{key, val})
+	return nil
+}
+
+func write(recs [][]kv, w io.Writer) error {
+	enc := NewEncoder(w)
+	for _, rec := range recs {
+		for _, f := range rec {
+			if err := enc.EncodeKeyval(f.k, f.v); err != nil {
+				return err
+			}
+		}
+		if err := enc.EndRecord(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/vendor/github.com/go-logfmt/logfmt/go.mod b/vendor/github.com/go-logfmt/logfmt/go.mod
new file mode 100644
index 0000000..63d50f0
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/go.mod
@@ -0,0 +1,3 @@
+module github.com/go-logfmt/logfmt
+
+require github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
diff --git a/vendor/github.com/go-logfmt/logfmt/go.sum b/vendor/github.com/go-logfmt/logfmt/go.sum
new file mode 100644
index 0000000..d0cdc16
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/go.sum
@@ -0,0 +1,2 @@
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
diff --git a/vendor/github.com/go-logfmt/logfmt/jsonstring.go b/vendor/github.com/go-logfmt/logfmt/jsonstring.go
new file mode 100644
index 0000000..030ac85
--- /dev/null
+++ b/vendor/github.com/go-logfmt/logfmt/jsonstring.go
@@ -0,0 +1,277 @@
+package logfmt
+
+import (
+	"bytes"
+	"io"
+	"strconv"
+	"sync"
+	"unicode"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+// Taken from Go's encoding/json and modified for use here.
+
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var hex = "0123456789abcdef"
+
+var bufferPool = sync.Pool{
+	New: func() interface{} {
+		return &bytes.Buffer{}
+	},
+}
+
+func getBuffer() *bytes.Buffer {
+	return bufferPool.Get().(*bytes.Buffer)
+}
+
+func poolBuffer(buf *bytes.Buffer) {
+	buf.Reset()
+	bufferPool.Put(buf)
+}
+
+// NOTE: keep in sync with writeQuotedBytes below.
+func writeQuotedString(w io.Writer, s string) (int, error) {
+	buf := getBuffer()
+	buf.WriteByte('"')
+	start := 0
+	for i := 0; i < len(s); {
+		if b := s[i]; b < utf8.RuneSelf {
+			if 0x20 <= b && b != '\\' && b != '"' {
+				i++
+				continue
+			}
+			if start < i {
+				buf.WriteString(s[start:i])
+			}
+			switch b {
+			case '\\', '"':
+				buf.WriteByte('\\')
+				buf.WriteByte(b)
+			case '\n':
+				buf.WriteByte('\\')
+				buf.WriteByte('n')
+			case '\r':
+				buf.WriteByte('\\')
+				buf.WriteByte('r')
+			case '\t':
+				buf.WriteByte('\\')
+				buf.WriteByte('t')
+			default:
+				// This encodes bytes < 0x20 except for \n, \r, and \t.
+				buf.WriteString(`\u00`)
+				buf.WriteByte(hex[b>>4])
+				buf.WriteByte(hex[b&0xF])
+			}
+			i++
+			start = i
+			continue
+		}
+		c, size := utf8.DecodeRuneInString(s[i:])
+		if c == utf8.RuneError {
+			if start < i {
+				buf.WriteString(s[start:i])
+			}
+			buf.WriteString(`\ufffd`)
+			i += size
+			start = i
+			continue
+		}
+		i += size
+	}
+	if start < len(s) {
+		buf.WriteString(s[start:])
+	}
+	buf.WriteByte('"')
+	n, err := w.Write(buf.Bytes())
+	poolBuffer(buf)
+	return n, err
+}
+
+// NOTE: keep in sync with writeQuoteString above.
+func writeQuotedBytes(w io.Writer, s []byte) (int, error) {
+	buf := getBuffer()
+	buf.WriteByte('"')
+	start := 0
+	for i := 0; i < len(s); {
+		if b := s[i]; b < utf8.RuneSelf {
+			if 0x20 <= b && b != '\\' && b != '"' {
+				i++
+				continue
+			}
+			if start < i {
+				buf.Write(s[start:i])
+			}
+			switch b {
+			case '\\', '"':
+				buf.WriteByte('\\')
+				buf.WriteByte(b)
+			case '\n':
+				buf.WriteByte('\\')
+				buf.WriteByte('n')
+			case '\r':
+				buf.WriteByte('\\')
+				buf.WriteByte('r')
+			case '\t':
+				buf.WriteByte('\\')
+				buf.WriteByte('t')
+			default:
+				// This encodes bytes < 0x20 except for \n, \r, and \t.
+				buf.WriteString(`\u00`)
+				buf.WriteByte(hex[b>>4])
+				buf.WriteByte(hex[b&0xF])
+			}
+			i++
+			start = i
+			continue
+		}
+		c, size := utf8.DecodeRune(s[i:])
+		if c == utf8.RuneError {
+			if start < i {
+				buf.Write(s[start:i])
+			}
+			buf.WriteString(`\ufffd`)
+			i += size
+			start = i
+			continue
+		}
+		i += size
+	}
+	if start < len(s) {
+		buf.Write(s[start:])
+	}
+	buf.WriteByte('"')
+	n, err := w.Write(buf.Bytes())
+	poolBuffer(buf)
+	return n, err
+}
+
+// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
+// or it returns -1.
+func getu4(s []byte) rune {
+	if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
+		return -1
+	}
+	r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
+	if err != nil {
+		return -1
+	}
+	return rune(r)
+}
+
+func unquoteBytes(s []byte) (t []byte, ok bool) {
+	if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
+		return
+	}
+	s = s[1 : len(s)-1]
+
+	// Check for unusual characters. If there are none,
+	// then no unquoting is needed, so return a slice of the
+	// original bytes.
+	r := 0
+	for r < len(s) {
+		c := s[r]
+		if c == '\\' || c == '"' || c < ' ' {
+			break
+		}
+		if c < utf8.RuneSelf {
+			r++
+			continue
+		}
+		rr, size := utf8.DecodeRune(s[r:])
+		if rr == utf8.RuneError {
+			break
+		}
+		r += size
+	}
+	if r == len(s) {
+		return s, true
+	}
+
+	b := make([]byte, len(s)+2*utf8.UTFMax)
+	w := copy(b, s[0:r])
+	for r < len(s) {
+		// Out of room?  Can only happen if s is full of
+		// malformed UTF-8 and we're replacing each
+		// byte with RuneError.
+		if w >= len(b)-2*utf8.UTFMax {
+			nb := make([]byte, (len(b)+utf8.UTFMax)*2)
+			copy(nb, b[0:w])
+			b = nb
+		}
+		switch c := s[r]; {
+		case c == '\\':
+			r++
+			if r >= len(s) {
+				return
+			}
+			switch s[r] {
+			default:
+				return
+			case '"', '\\', '/', '\'':
+				b[w] = s[r]
+				r++
+				w++
+			case 'b':
+				b[w] = '\b'
+				r++
+				w++
+			case 'f':
+				b[w] = '\f'
+				r++
+				w++
+			case 'n':
+				b[w] = '\n'
+				r++
+				w++
+			case 'r':
+				b[w] = '\r'
+				r++
+				w++
+			case 't':
+				b[w] = '\t'
+				r++
+				w++
+			case 'u':
+				r--
+				rr := getu4(s[r:])
+				if rr < 0 {
+					return
+				}
+				r += 6
+				if utf16.IsSurrogate(rr) {
+					rr1 := getu4(s[r:])
+					if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
+						// A valid pair; consume.
+						r += 6
+						w += utf8.EncodeRune(b[w:], dec)
+						break
+					}
+					// Invalid surrogate; fall back to replacement rune.
+					rr = unicode.ReplacementChar
+				}
+				w += utf8.EncodeRune(b[w:], rr)
+			}
+
+		// Quote, control characters are invalid.
+		case c == '"', c < ' ':
+			return
+
+		// ASCII
+		case c < utf8.RuneSelf:
+			b[w] = c
+			r++
+			w++
+
+		// Coerce to well-formed UTF-8.
+		default:
+			rr, size := utf8.DecodeRune(s[r:])
+			r += size
+			w += utf8.EncodeRune(b[w:], rr)
+		}
+	}
+	return b[0:w], true
+}
diff --git a/vendor/github.com/golang/protobuf/proto/properties.go b/vendor/github.com/golang/protobuf/proto/properties.go
index 79668ff..a4b8c0c 100644
--- a/vendor/github.com/golang/protobuf/proto/properties.go
+++ b/vendor/github.com/golang/protobuf/proto/properties.go
@@ -38,7 +38,6 @@ package proto
 import (
 	"fmt"
 	"log"
-	"os"
 	"reflect"
 	"sort"
 	"strconv"
@@ -194,7 +193,7 @@ func (p *Properties) Parse(s string) {
 	// "bytes,49,opt,name=foo,def=hello!"
 	fields := strings.Split(s, ",") // breaks def=, but handled below.
 	if len(fields) < 2 {
-		fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
+		log.Printf("proto: tag has too few fields: %q", s)
 		return
 	}
 
@@ -214,7 +213,7 @@ func (p *Properties) Parse(s string) {
 		p.WireType = WireBytes
 		// no numeric converter for non-numeric types
 	default:
-		fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
+		log.Printf("proto: tag has unknown wire type: %q", s)
 		return
 	}
 
diff --git a/vendor/github.com/kr/logfmt/.gitignore b/vendor/github.com/kr/logfmt/.gitignore
new file mode 100644
index 0000000..8e524f6
--- /dev/null
+++ b/vendor/github.com/kr/logfmt/.gitignore
@@ -0,0 +1,3 @@
+*.test
+*.swp
+*.prof
diff --git a/vendor/github.com/kr/logfmt/Readme b/vendor/github.com/kr/logfmt/Readme
new file mode 100644
index 0000000..1865a11
--- /dev/null
+++ b/vendor/github.com/kr/logfmt/Readme
@@ -0,0 +1,12 @@
+Go package for parsing (and, eventually, generating)
+log lines in the logfmt style.
+
+See http://godoc.org/github.com/kr/logfmt for format, and other documentation and examples.
+
+Copyright (C) 2013 Keith Rarick, Blake Mizerany
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/kr/logfmt/decode.go b/vendor/github.com/kr/logfmt/decode.go
new file mode 100644
index 0000000..1397fb7
--- /dev/null
+++ b/vendor/github.com/kr/logfmt/decode.go
@@ -0,0 +1,184 @@
+// Package implements the decoding of logfmt key-value pairs.
+//
+// Example logfmt message:
+//
+//	foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf
+//
+// Example result in JSON:
+//
+//	{ "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": true, "%^asdf": true }
+//
+// EBNFish:
+//
+// 	ident_byte = any byte greater than ' ', excluding '=' and '"'
+// 	string_byte = any byte excluding '"' and '\'
+// 	garbage = !ident_byte
+// 	ident = ident_byte, { ident byte }
+// 	key = ident
+// 	value = ident | '"', { string_byte | '\', '"' }, '"'
+// 	pair = key, '=', value | key, '=' | key
+// 	message = { garbage, pair }, garbage
+package logfmt
+
+import (
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Handler is the interface implemented by objects that accept logfmt
+// key-value pairs. HandleLogfmt must copy the logfmt data if it
+// wishes to retain the data after returning.
+type Handler interface {
+	HandleLogfmt(key, val []byte) error
+}
+
+// The HandlerFunc type is an adapter to allow the use of ordinary functions as
+// logfmt handlers. If f is a function with the appropriate signature,
+// HandlerFunc(f) is a Handler object that calls f.
+type HandlerFunc func(key, val []byte) error
+
+func (f HandlerFunc) HandleLogfmt(key, val []byte) error {
+	return f(key, val)
+}
+
+// Unmarshal parses the logfmt encoding data and stores the result in the value
+// pointed to by v. If v is an Handler, HandleLogfmt will be called for each
+// key-value pair.
+//
+// If v is not a Handler, it will pass v to NewStructHandler and use the
+// returned StructHandler for decoding.
+func Unmarshal(data []byte, v interface{}) (err error) {
+	h, ok := v.(Handler)
+	if !ok {
+		h, err = NewStructHandler(v)
+		if err != nil {
+			return err
+		}
+	}
+	return gotoScanner(data, h)
+}
+
+// StructHandler unmarshals logfmt into a struct. It matches incoming keys to
+// the the struct's fields (either the struct field name or its tag, preferring
+// an exact match but also accepting a case-insensitive match.
+//
+// Field types supported by StructHandler are:
+//
+// 	all numeric types (e.g. float32, int, etc.)
+// 	[]byte
+// 	string
+// 	bool - true if key is present, false otherwise (the value is ignored).
+//	time.Duration - uses time.ParseDuration
+//
+// If a field is a pointer to an above type, and a matching key is not present
+// in the logfmt data, the pointer will be untouched.
+//
+// If v is not a pointer to an Handler or struct, Unmarshal will return an
+// error.
+type StructHandler struct {
+	rv reflect.Value
+}
+
+func NewStructHandler(v interface{}) (Handler, error) {
+	rv := reflect.ValueOf(v)
+	if rv.Kind() != reflect.Ptr || rv.IsNil() {
+		return nil, &InvalidUnmarshalError{reflect.TypeOf(v)}
+	}
+	return &StructHandler{rv: rv}, nil
+}
+
+func (h *StructHandler) HandleLogfmt(key, val []byte) error {
+	el := h.rv.Elem()
+	skey := string(key)
+	for i := 0; i < el.NumField(); i++ {
+		fv := el.Field(i)
+		ft := el.Type().Field(i)
+		switch {
+		case ft.Name == skey:
+		case ft.Tag.Get("logfmt") == skey:
+		case strings.EqualFold(ft.Name, skey):
+		default:
+			continue
+		}
+		if fv.Kind() == reflect.Ptr {
+			if fv.IsNil() {
+				t := fv.Type().Elem()
+				v := reflect.New(t)
+				fv.Set(v)
+				fv = v
+			}
+			fv = fv.Elem()
+		}
+		switch fv.Interface().(type) {
+		case time.Duration:
+			d, err := time.ParseDuration(string(val))
+			if err != nil {
+				return &UnmarshalTypeError{string(val), fv.Type()}
+			}
+			fv.Set(reflect.ValueOf(d))
+		case string:
+			fv.SetString(string(val))
+		case []byte:
+			b := make([]byte, len(val))
+			copy(b, val)
+			fv.SetBytes(b)
+		case bool:
+			fv.SetBool(true)
+		default:
+			switch {
+			case reflect.Int <= fv.Kind() && fv.Kind() <= reflect.Int64:
+				v, err := strconv.ParseInt(string(val), 10, 64)
+				if err != nil {
+					return err
+				}
+				fv.SetInt(v)
+			case reflect.Uint32 <= fv.Kind() && fv.Kind() <= reflect.Uint64:
+				v, err := strconv.ParseUint(string(val), 10, 64)
+				if err != nil {
+					return err
+				}
+				fv.SetUint(v)
+			case reflect.Float32 <= fv.Kind() && fv.Kind() <= reflect.Float64:
+				v, err := strconv.ParseFloat(string(val), 10)
+				if err != nil {
+					return err
+				}
+				fv.SetFloat(v)
+			default:
+				return &UnmarshalTypeError{string(val), fv.Type()}
+			}
+		}
+
+	}
+	return nil
+}
+
+// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
+// (The argument to Unmarshal must be a non-nil pointer.)
+type InvalidUnmarshalError struct {
+	Type reflect.Type
+}
+
+func (e *InvalidUnmarshalError) Error() string {
+	if e.Type == nil {
+		return "logfmt: Unmarshal(nil)"
+	}
+
+	if e.Type.Kind() != reflect.Ptr {
+		return "logfmt: Unmarshal(non-pointer " + e.Type.String() + ")"
+	}
+	return "logfmt: Unmarshal(nil " + e.Type.String() + ")"
+}
+
+// An UnmarshalTypeError describes a logfmt value that was
+// not appropriate for a value of a specific Go type.
+type UnmarshalTypeError struct {
+	Value string       // the logfmt value
+	Type  reflect.Type // type of Go value it could not be assigned to
+}
+
+func (e *UnmarshalTypeError) Error() string {
+	return "logfmt: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
+}
diff --git a/vendor/github.com/kr/logfmt/scanner.go b/vendor/github.com/kr/logfmt/scanner.go
new file mode 100644
index 0000000..095221f
--- /dev/null
+++ b/vendor/github.com/kr/logfmt/scanner.go
@@ -0,0 +1,149 @@
+package logfmt
+
+import (
+	"errors"
+	"fmt"
+)
+
+var ErrUnterminatedString = errors.New("logfmt: unterminated string")
+
+func gotoScanner(data []byte, h Handler) (err error) {
+	saveError := func(e error) {
+		if err == nil {
+			err = e
+		}
+	}
+
+	var c byte
+	var i int
+	var m int
+	var key []byte
+	var val []byte
+	var ok bool
+	var esc bool
+
+garbage:
+	if i == len(data) {
+		return
+	}
+
+	c = data[i]
+	switch {
+	case c > ' ' && c != '"' && c != '=':
+		key, val = nil, nil
+		m = i
+		i++
+		goto key
+	default:
+		i++
+		goto garbage
+	}
+
+key:
+	if i >= len(data) {
+		if m >= 0 {
+			key = data[m:i]
+			saveError(h.HandleLogfmt(key, nil))
+		}
+		return
+	}
+
+	c = data[i]
+	switch {
+	case c > ' ' && c != '"' && c != '=':
+		i++
+		goto key
+	case c == '=':
+		key = data[m:i]
+		i++
+		goto equal
+	default:
+		key = data[m:i]
+		i++
+		saveError(h.HandleLogfmt(key, nil))
+		goto garbage
+	}
+
+equal:
+	if i >= len(data) {
+		if m >= 0 {
+			i--
+			key = data[m:i]
+			saveError(h.HandleLogfmt(key, nil))
+		}
+		return
+	}
+
+	c = data[i]
+	switch {
+	case c > ' ' && c != '"' && c != '=':
+		m = i
+		i++
+		goto ivalue
+	case c == '"':
+		m = i
+		i++
+		esc = false
+		goto qvalue
+	default:
+		if key != nil {
+			saveError(h.HandleLogfmt(key, val))
+		}
+		i++
+		goto garbage
+	}
+
+ivalue:
+	if i >= len(data) {
+		if m >= 0 {
+			val = data[m:i]
+			saveError(h.HandleLogfmt(key, val))
+		}
+		return
+	}
+
+	c = data[i]
+	switch {
+	case c > ' ' && c != '"' && c != '=':
+		i++
+		goto ivalue
+	default:
+		val = data[m:i]
+		saveError(h.HandleLogfmt(key, val))
+		i++
+		goto garbage
+	}
+
+qvalue:
+	if i >= len(data) {
+		if m >= 0 {
+			saveError(ErrUnterminatedString)
+		}
+		return
+	}
+
+	c = data[i]
+	switch c {
+	case '\\':
+		i += 2
+		esc = true
+		goto qvalue
+	case '"':
+		i++
+		val = data[m:i]
+		if esc {
+			val, ok = unquoteBytes(val)
+			if !ok {
+				saveError(fmt.Errorf("logfmt: error unquoting bytes %q", string(val)))
+				goto garbage
+			}
+		} else {
+			val = val[1 : len(val)-1]
+		}
+		saveError(h.HandleLogfmt(key, val))
+		goto garbage
+	default:
+		i++
+		goto qvalue
+	}
+}
diff --git a/vendor/github.com/kr/logfmt/unquote.go b/vendor/github.com/kr/logfmt/unquote.go
new file mode 100644
index 0000000..fb088a4
--- /dev/null
+++ b/vendor/github.com/kr/logfmt/unquote.go
@@ -0,0 +1,149 @@
+package logfmt
+
+import (
+	"strconv"
+	"unicode"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+// Taken from Go's encoding/json
+
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
+// or it returns -1.
+func getu4(s []byte) rune {
+	if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
+		return -1
+	}
+	r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
+	if err != nil {
+		return -1
+	}
+	return rune(r)
+}
+
+// unquote converts a quoted JSON string literal s into an actual string t.
+// The rules are different than for Go, so cannot use strconv.Unquote.
+func unquote(s []byte) (t string, ok bool) {
+	s, ok = unquoteBytes(s)
+	t = string(s)
+	return
+}
+
+func unquoteBytes(s []byte) (t []byte, ok bool) {
+	if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
+		return
+	}
+	s = s[1 : len(s)-1]
+
+	// Check for unusual characters. If there are none,
+	// then no unquoting is needed, so return a slice of the
+	// original bytes.
+	r := 0
+	for r < len(s) {
+		c := s[r]
+		if c == '\\' || c == '"' || c < ' ' {
+			break
+		}
+		if c < utf8.RuneSelf {
+			r++
+			continue
+		}
+		rr, size := utf8.DecodeRune(s[r:])
+		if rr == utf8.RuneError && size == 1 {
+			break
+		}
+		r += size
+	}
+	if r == len(s) {
+		return s, true
+	}
+
+	b := make([]byte, len(s)+2*utf8.UTFMax)
+	w := copy(b, s[0:r])
+	for r < len(s) {
+		// Out of room?  Can only happen if s is full of
+		// malformed UTF-8 and we're replacing each
+		// byte with RuneError.
+		if w >= len(b)-2*utf8.UTFMax {
+			nb := make([]byte, (len(b)+utf8.UTFMax)*2)
+			copy(nb, b[0:w])
+			b = nb
+		}
+		switch c := s[r]; {
+		case c == '\\':
+			r++
+			if r >= len(s) {
+				return
+			}
+			switch s[r] {
+			default:
+				return
+			case '"', '\\', '/', '\'':
+				b[w] = s[r]
+				r++
+				w++
+			case 'b':
+				b[w] = '\b'
+				r++
+				w++
+			case 'f':
+				b[w] = '\f'
+				r++
+				w++
+			case 'n':
+				b[w] = '\n'
+				r++
+				w++
+			case 'r':
+				b[w] = '\r'
+				r++
+				w++
+			case 't':
+				b[w] = '\t'
+				r++
+				w++
+			case 'u':
+				r--
+				rr := getu4(s[r:])
+				if rr < 0 {
+					return
+				}
+				r += 6
+				if utf16.IsSurrogate(rr) {
+					rr1 := getu4(s[r:])
+					if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
+						// A valid pair; consume.
+						r += 6
+						w += utf8.EncodeRune(b[w:], dec)
+						break
+					}
+					// Invalid surrogate; fall back to replacement rune.
+					rr = unicode.ReplacementChar
+				}
+				w += utf8.EncodeRune(b[w:], rr)
+			}
+
+		// Quote, control characters are invalid.
+		case c == '"', c < ' ':
+			return
+
+		// ASCII
+		case c < utf8.RuneSelf:
+			b[w] = c
+			r++
+			w++
+
+		// Coerce to well-formed UTF-8.
+		default:
+			rr, size := utf8.DecodeRune(s[r:])
+			r += size
+			w += utf8.EncodeRune(b[w:], rr)
+		}
+	}
+	return b[0:w], true
+}
diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/github.com/pkg/errors/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml
new file mode 100644
index 0000000..d4b9266
--- /dev/null
+++ b/vendor/github.com/pkg/errors/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+go_import_path: github.com/pkg/errors
+go:
+  - 1.4.x
+  - 1.5.x
+  - 1.6.x
+  - 1.7.x
+  - 1.8.x
+  - 1.9.x
+  - 1.10.x
+  - 1.11.x
+  - tip
+
+script:
+  - go test -v ./...
diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE
new file mode 100644
index 0000000..835ba3e
--- /dev/null
+++ b/vendor/github.com/pkg/errors/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2015, Dave Cheney <dave@cheney.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md
new file mode 100644
index 0000000..6483ba2
--- /dev/null
+++ b/vendor/github.com/pkg/errors/README.md
@@ -0,0 +1,52 @@
+# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
+
+Package errors provides simple error handling primitives.
+
+`go get github.com/pkg/errors`
+
+The traditional error handling idiom in Go is roughly akin to
+```go
+if err != nil {
+        return err
+}
+```
+which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
+
+## Adding context to an error
+
+The errors.Wrap function returns a new error that adds context to the original error. For example
+```go
+_, err := ioutil.ReadAll(r)
+if err != nil {
+        return errors.Wrap(err, "read failed")
+}
+```
+## Retrieving the cause of an error
+
+Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
+```go
+type causer interface {
+        Cause() error
+}
+```
+`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
+```go
+switch err := errors.Cause(err).(type) {
+case *MyError:
+        // handle specifically
+default:
+        // unknown error
+}
+```
+
+[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
+
+## Contributing
+
+We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
+
+Before proposing a change, please discuss your change by raising an issue.
+
+## License
+
+BSD-2-Clause
diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml
new file mode 100644
index 0000000..a932ead
--- /dev/null
+++ b/vendor/github.com/pkg/errors/appveyor.yml
@@ -0,0 +1,32 @@
+version: build-{build}.{branch}
+
+clone_folder: C:\gopath\src\github.com\pkg\errors
+shallow_clone: true # for startup speed
+
+environment:
+  GOPATH: C:\gopath
+
+platform:
+  - x64
+
+# http://www.appveyor.com/docs/installed-software
+install:
+  # some helpful output for debugging builds
+  - go version
+  - go env
+  # pre-installed MinGW at C:\MinGW is 32bit only
+  # but MSYS2 at C:\msys64 has mingw64
+  - set PATH=C:\msys64\mingw64\bin;%PATH%
+  - gcc --version
+  - g++ --version
+
+build_script:
+  - go install -v ./...
+
+test_script:
+  - set PATH=C:\gopath\bin;%PATH%
+  - go test -v ./...
+
+#artifacts:
+#  - path: '%GOPATH%\bin\*.exe'
+deploy: off
diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go
new file mode 100644
index 0000000..7421f32
--- /dev/null
+++ b/vendor/github.com/pkg/errors/errors.go
@@ -0,0 +1,282 @@
+// Package errors provides simple error handling primitives.
+//
+// The traditional error handling idiom in Go is roughly akin to
+//
+//     if err != nil {
+//             return err
+//     }
+//
+// which when applied recursively up the call stack results in error reports
+// without context or debugging information. The errors package allows
+// programmers to add context to the failure path in their code in a way
+// that does not destroy the original value of the error.
+//
+// Adding context to an error
+//
+// The errors.Wrap function returns a new error that adds context to the
+// original error by recording a stack trace at the point Wrap is called,
+// together with the supplied message. For example
+//
+//     _, err := ioutil.ReadAll(r)
+//     if err != nil {
+//             return errors.Wrap(err, "read failed")
+//     }
+//
+// If additional control is required, the errors.WithStack and
+// errors.WithMessage functions destructure errors.Wrap into its component
+// operations: annotating an error with a stack trace and with a message,
+// respectively.
+//
+// Retrieving the cause of an error
+//
+// Using errors.Wrap constructs a stack of errors, adding context to the
+// preceding error. Depending on the nature of the error it may be necessary
+// to reverse the operation of errors.Wrap to retrieve the original error
+// for inspection. Any error value which implements this interface
+//
+//     type causer interface {
+//             Cause() error
+//     }
+//
+// can be inspected by errors.Cause. errors.Cause will recursively retrieve
+// the topmost error that does not implement causer, which is assumed to be
+// the original cause. For example:
+//
+//     switch err := errors.Cause(err).(type) {
+//     case *MyError:
+//             // handle specifically
+//     default:
+//             // unknown error
+//     }
+//
+// Although the causer interface is not exported by this package, it is
+// considered a part of its stable public interface.
+//
+// Formatted printing of errors
+//
+// All error values returned from this package implement fmt.Formatter and can
+// be formatted by the fmt package. The following verbs are supported:
+//
+//     %s    print the error. If the error has a Cause it will be
+//           printed recursively.
+//     %v    see %s
+//     %+v   extended format. Each Frame of the error's StackTrace will
+//           be printed in detail.
+//
+// Retrieving the stack trace of an error or wrapper
+//
+// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
+// invoked. This information can be retrieved with the following interface:
+//
+//     type stackTracer interface {
+//             StackTrace() errors.StackTrace
+//     }
+//
+// The returned errors.StackTrace type is defined as
+//
+//     type StackTrace []Frame
+//
+// The Frame type represents a call site in the stack trace. Frame supports
+// the fmt.Formatter interface that can be used for printing information about
+// the stack trace of this error. For example:
+//
+//     if err, ok := err.(stackTracer); ok {
+//             for _, f := range err.StackTrace() {
+//                     fmt.Printf("%+s:%d", f)
+//             }
+//     }
+//
+// Although the stackTracer interface is not exported by this package, it is
+// considered a part of its stable public interface.
+//
+// See the documentation for Frame.Format for more details.
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+// New returns an error with the supplied message.
+// New also records the stack trace at the point it was called.
+func New(message string) error {
+	return &fundamental{
+		msg:   message,
+		stack: callers(),
+	}
+}
+
+// Errorf formats according to a format specifier and returns the string
+// as a value that satisfies error.
+// Errorf also records the stack trace at the point it was called.
+func Errorf(format string, args ...interface{}) error {
+	return &fundamental{
+		msg:   fmt.Sprintf(format, args...),
+		stack: callers(),
+	}
+}
+
+// fundamental is an error that has a message and a stack, but no caller.
+type fundamental struct {
+	msg string
+	*stack
+}
+
+func (f *fundamental) Error() string { return f.msg }
+
+func (f *fundamental) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			io.WriteString(s, f.msg)
+			f.stack.Format(s, verb)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, f.msg)
+	case 'q':
+		fmt.Fprintf(s, "%q", f.msg)
+	}
+}
+
+// WithStack annotates err with a stack trace at the point WithStack was called.
+// If err is nil, WithStack returns nil.
+func WithStack(err error) error {
+	if err == nil {
+		return nil
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+type withStack struct {
+	error
+	*stack
+}
+
+func (w *withStack) Cause() error { return w.error }
+
+func (w *withStack) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			w.stack.Format(s, verb)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+// Wrap returns an error annotating err with a stack trace
+// at the point Wrap is called, and the supplied message.
+// If err is nil, Wrap returns nil.
+func Wrap(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+	err = &withMessage{
+		cause: err,
+		msg:   message,
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+// Wrapf returns an error annotating err with a stack trace
+// at the point Wrapf is called, and the format specifier.
+// If err is nil, Wrapf returns nil.
+func Wrapf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	err = &withMessage{
+		cause: err,
+		msg:   fmt.Sprintf(format, args...),
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+// WithMessage annotates err with a new message.
+// If err is nil, WithMessage returns nil.
+func WithMessage(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+	return &withMessage{
+		cause: err,
+		msg:   message,
+	}
+}
+
+// WithMessagef annotates err with the format specifier.
+// If err is nil, WithMessagef returns nil.
+func WithMessagef(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	return &withMessage{
+		cause: err,
+		msg:   fmt.Sprintf(format, args...),
+	}
+}
+
+type withMessage struct {
+	cause error
+	msg   string
+}
+
+func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
+func (w *withMessage) Cause() error  { return w.cause }
+
+func (w *withMessage) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v\n", w.Cause())
+			io.WriteString(s, w.msg)
+			return
+		}
+		fallthrough
+	case 's', 'q':
+		io.WriteString(s, w.Error())
+	}
+}
+
+// Cause returns the underlying cause of the error, if possible.
+// An error value has a cause if it implements the following
+// interface:
+//
+//     type causer interface {
+//            Cause() error
+//     }
+//
+// If the error does not implement Cause, the original error will
+// be returned. If the error is nil, nil will be returned without further
+// investigation.
+func Cause(err error) error {
+	type causer interface {
+		Cause() error
+	}
+
+	for err != nil {
+		cause, ok := err.(causer)
+		if !ok {
+			break
+		}
+		err = cause.Cause()
+	}
+	return err
+}
diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go
new file mode 100644
index 0000000..2874a04
--- /dev/null
+++ b/vendor/github.com/pkg/errors/stack.go
@@ -0,0 +1,147 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+	"path"
+	"runtime"
+	"strings"
+)
+
+// Frame represents a program counter inside a stack frame.
+type Frame uintptr
+
+// pc returns the program counter for this frame;
+// multiple frames may have the same PC value.
+func (f Frame) pc() uintptr { return uintptr(f) - 1 }
+
+// file returns the full path to the file that contains the
+// function for this Frame's pc.
+func (f Frame) file() string {
+	fn := runtime.FuncForPC(f.pc())
+	if fn == nil {
+		return "unknown"
+	}
+	file, _ := fn.FileLine(f.pc())
+	return file
+}
+
+// line returns the line number of source code of the
+// function for this Frame's pc.
+func (f Frame) line() int {
+	fn := runtime.FuncForPC(f.pc())
+	if fn == nil {
+		return 0
+	}
+	_, line := fn.FileLine(f.pc())
+	return line
+}
+
+// Format formats the frame according to the fmt.Formatter interface.
+//
+//    %s    source file
+//    %d    source line
+//    %n    function name
+//    %v    equivalent to %s:%d
+//
+// Format accepts flags that alter the printing of some verbs, as follows:
+//
+//    %+s   function name and path of source file relative to the compile time
+//          GOPATH separated by \n\t (<funcname>\n\t<path>)
+//    %+v   equivalent to %+s:%d
+func (f Frame) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 's':
+		switch {
+		case s.Flag('+'):
+			pc := f.pc()
+			fn := runtime.FuncForPC(pc)
+			if fn == nil {
+				io.WriteString(s, "unknown")
+			} else {
+				file, _ := fn.FileLine(pc)
+				fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
+			}
+		default:
+			io.WriteString(s, path.Base(f.file()))
+		}
+	case 'd':
+		fmt.Fprintf(s, "%d", f.line())
+	case 'n':
+		name := runtime.FuncForPC(f.pc()).Name()
+		io.WriteString(s, funcname(name))
+	case 'v':
+		f.Format(s, 's')
+		io.WriteString(s, ":")
+		f.Format(s, 'd')
+	}
+}
+
+// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
+type StackTrace []Frame
+
+// Format formats the stack of Frames according to the fmt.Formatter interface.
+//
+//    %s	lists source files for each Frame in the stack
+//    %v	lists the source file and line number for each Frame in the stack
+//
+// Format accepts flags that alter the printing of some verbs, as follows:
+//
+//    %+v   Prints filename, function, and line number for each Frame in the stack.
+func (st StackTrace) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		switch {
+		case s.Flag('+'):
+			for _, f := range st {
+				fmt.Fprintf(s, "\n%+v", f)
+			}
+		case s.Flag('#'):
+			fmt.Fprintf(s, "%#v", []Frame(st))
+		default:
+			fmt.Fprintf(s, "%v", []Frame(st))
+		}
+	case 's':
+		fmt.Fprintf(s, "%s", []Frame(st))
+	}
+}
+
+// stack represents a stack of program counters.
+type stack []uintptr
+
+func (s *stack) Format(st fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		switch {
+		case st.Flag('+'):
+			for _, pc := range *s {
+				f := Frame(pc)
+				fmt.Fprintf(st, "\n%+v", f)
+			}
+		}
+	}
+}
+
+func (s *stack) StackTrace() StackTrace {
+	f := make([]Frame, len(*s))
+	for i := 0; i < len(f); i++ {
+		f[i] = Frame((*s)[i])
+	}
+	return f
+}
+
+func callers() *stack {
+	const depth = 32
+	var pcs [depth]uintptr
+	n := runtime.Callers(3, pcs[:])
+	var st stack = pcs[0:n]
+	return &st
+}
+
+// funcname removes the path prefix component of a function's name reported by func.Name().
+func funcname(name string) string {
+	i := strings.LastIndex(name, "/")
+	name = name[i+1:]
+	i = strings.Index(name, ".")
+	return name[i+1:]
+}
diff --git a/vendor/github.com/prometheus/common/expfmt/text_create.go b/vendor/github.com/prometheus/common/expfmt/text_create.go
index 8e473d0..0327865 100644
--- a/vendor/github.com/prometheus/common/expfmt/text_create.go
+++ b/vendor/github.com/prometheus/common/expfmt/text_create.go
@@ -14,9 +14,10 @@
 package expfmt
 
 import (
-	"bytes"
+	"bufio"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"math"
 	"strconv"
 	"strings"
@@ -27,7 +28,7 @@ import (
 	dto "github.com/prometheus/client_model/go"
 )
 
-// enhancedWriter has all the enhanced write functions needed here. bytes.Buffer
+// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
 // implements it.
 type enhancedWriter interface {
 	io.Writer
@@ -37,14 +38,13 @@ type enhancedWriter interface {
 }
 
 const (
-	initialBufSize    = 512
 	initialNumBufSize = 24
 )
 
 var (
 	bufPool = sync.Pool{
 		New: func() interface{} {
-			return bytes.NewBuffer(make([]byte, 0, initialBufSize))
+			return bufio.NewWriter(ioutil.Discard)
 		},
 	}
 	numBufPool = sync.Pool{
@@ -75,16 +75,14 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e
 	}
 
 	// Try the interface upgrade. If it doesn't work, we'll use a
-	// bytes.Buffer from the sync.Pool and write out its content to out in a
-	// single go in the end.
+	// bufio.Writer from the sync.Pool.
 	w, ok := out.(enhancedWriter)
 	if !ok {
-		b := bufPool.Get().(*bytes.Buffer)
-		b.Reset()
+		b := bufPool.Get().(*bufio.Writer)
+		b.Reset(out)
 		w = b
 		defer func() {
-			bWritten, bErr := out.Write(b.Bytes())
-			written = bWritten
+			bErr := b.Flush()
 			if err == nil {
 				err = bErr
 			}
diff --git a/vendor/github.com/prometheus/common/expfmt/text_parse.go b/vendor/github.com/prometheus/common/expfmt/text_parse.go
index ec3d86b..342e594 100644
--- a/vendor/github.com/prometheus/common/expfmt/text_parse.go
+++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go
@@ -325,7 +325,7 @@ func (p *TextParser) startLabelValue() stateFn {
 	// - Other labels have to be added to currentLabels for signature calculation.
 	if p.currentMF.GetType() == dto.MetricType_SUMMARY {
 		if p.currentLabelPair.GetName() == model.QuantileLabel {
-			if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
+			if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
 				// Create a more helpful error message.
 				p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
 				return nil
@@ -337,7 +337,7 @@ func (p *TextParser) startLabelValue() stateFn {
 	// Similar special treatment of histograms.
 	if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
 		if p.currentLabelPair.GetName() == model.BucketLabel {
-			if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
+			if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
 				// Create a more helpful error message.
 				p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
 				return nil
@@ -392,7 +392,7 @@ func (p *TextParser) readingValue() stateFn {
 	if p.readTokenUntilWhitespace(); p.err != nil {
 		return nil // Unexpected end of input.
 	}
-	value, err := strconv.ParseFloat(p.currentToken.String(), 64)
+	value, err := parseFloat(p.currentToken.String())
 	if err != nil {
 		// Create a more helpful error message.
 		p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
@@ -755,3 +755,10 @@ func histogramMetricName(name string) string {
 		return name
 	}
 }
+
+func parseFloat(s string) (float64, error) {
+	if strings.ContainsAny(s, "pP_") {
+		return 0, fmt.Errorf("unsupported character in float")
+	}
+	return strconv.ParseFloat(s, 64)
+}
diff --git a/vendor/github.com/prometheus/common/promlog/flag/flag.go b/vendor/github.com/prometheus/common/promlog/flag/flag.go
new file mode 100644
index 0000000..ec55008
--- /dev/null
+++ b/vendor/github.com/prometheus/common/promlog/flag/flag.go
@@ -0,0 +1,45 @@
+// Copyright 2017 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package flag
+
+import (
+	"github.com/prometheus/common/promlog"
+	kingpin "gopkg.in/alecthomas/kingpin.v2"
+)
+
+// LevelFlagName is the canonical flag name to configure the allowed log level
+// within Prometheus projects.
+const LevelFlagName = "log.level"
+
+// LevelFlagHelp is the help description for the log.level flag.
+const LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]"
+
+// FormatFlagName is the canonical flag name to configure the log format
+// within Prometheus projects.
+const FormatFlagName = "log.format"
+
+// FormatFlagHelp is the help description for the log.format flag.
+const FormatFlagHelp = "Output format of log messages. One of: [logfmt, json]"
+
+// AddFlags adds the flags used by this package to the Kingpin application.
+// To use the default Kingpin application, call AddFlags(kingpin.CommandLine)
+func AddFlags(a *kingpin.Application, config *promlog.Config) {
+	config.Level = &promlog.AllowedLevel{}
+	a.Flag(LevelFlagName, LevelFlagHelp).
+		Default("info").SetValue(config.Level)
+
+	config.Format = &promlog.AllowedFormat{}
+	a.Flag(FormatFlagName, FormatFlagHelp).
+		Default("logfmt").SetValue(config.Format)
+}
diff --git a/vendor/github.com/prometheus/common/promlog/log.go b/vendor/github.com/prometheus/common/promlog/log.go
new file mode 100644
index 0000000..45fcbc4
--- /dev/null
+++ b/vendor/github.com/prometheus/common/promlog/log.go
@@ -0,0 +1,106 @@
+// Copyright 2017 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package promlog defines standardised ways to initialize Go kit loggers
+// across Prometheus components.
+// It should typically only ever be imported by main packages.
+package promlog
+
+import (
+	"os"
+	"time"
+
+	"github.com/go-kit/kit/log"
+	"github.com/go-kit/kit/log/level"
+	"github.com/pkg/errors"
+)
+
+var (
+	// This timestamp format differs from RFC3339Nano by using .000 instead
+	// of .999999999 which changes the timestamp from 9 variable to 3 fixed
+	// decimals (.130 instead of .130987456).
+	timestampFormat = log.TimestampFormat(
+		func() time.Time { return time.Now().UTC() },
+		"2006-01-02T15:04:05.000Z07:00",
+	)
+)
+
+// AllowedLevel is a settable identifier for the minimum level a log entry
+// must be have.
+type AllowedLevel struct {
+	s string
+	o level.Option
+}
+
+func (l *AllowedLevel) String() string {
+	return l.s
+}
+
+// Set updates the value of the allowed level.
+func (l *AllowedLevel) Set(s string) error {
+	switch s {
+	case "debug":
+		l.o = level.AllowDebug()
+	case "info":
+		l.o = level.AllowInfo()
+	case "warn":
+		l.o = level.AllowWarn()
+	case "error":
+		l.o = level.AllowError()
+	default:
+		return errors.Errorf("unrecognized log level %q", s)
+	}
+	l.s = s
+	return nil
+}
+
+// AllowedFormat is a settable identifier for the output format that the logger can have.
+type AllowedFormat struct {
+	s string
+}
+
+func (f *AllowedFormat) String() string {
+	return f.s
+}
+
+// Set updates the value of the allowed format.
+func (f *AllowedFormat) Set(s string) error {
+	switch s {
+	case "logfmt", "json":
+		f.s = s
+	default:
+		return errors.Errorf("unrecognized log format %q", s)
+	}
+	return nil
+}
+
+// Config is a struct containing configurable settings for the logger
+type Config struct {
+	Level  *AllowedLevel
+	Format *AllowedFormat
+}
+
+// New returns a new leveled oklog logger. Each logged line will be annotated
+// with a timestamp. The output always goes to stderr.
+func New(config *Config) log.Logger {
+	var l log.Logger
+	if config.Format.s == "logfmt" {
+		l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
+	} else {
+		l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
+	}
+
+	l = level.NewFilter(l, config.Level.o)
+	l = log.With(l, "ts", timestampFormat, "caller", log.DefaultCaller)
+	return l
+}
diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go
index a14435e..0ee738e 100644
--- a/vendor/gopkg.in/yaml.v2/encode.go
+++ b/vendor/gopkg.in/yaml.v2/encode.go
@@ -13,6 +13,19 @@ import (
 	"unicode/utf8"
 )
 
+// jsonNumber is the interface of the encoding/json.Number datatype.
+// Repeating the interface here avoids a dependency on encoding/json, and also
+// supports other libraries like jsoniter, which use a similar datatype with
+// the same interface. Detecting this interface is useful when dealing with
+// structures containing json.Number, which is a string under the hood. The
+// encoder should prefer the use of Int64(), Float64() and string(), in that
+// order, when encoding this type.
+type jsonNumber interface {
+	Float64() (float64, error)
+	Int64() (int64, error)
+	String() string
+}
+
 type encoder struct {
 	emitter yaml_emitter_t
 	event   yaml_event_t
@@ -89,6 +102,21 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
 	}
 	iface := in.Interface()
 	switch m := iface.(type) {
+	case jsonNumber:
+		integer, err := m.Int64()
+		if err == nil {
+			// In this case the json.Number is a valid int64
+			in = reflect.ValueOf(integer)
+			break
+		}
+		float, err := m.Float64()
+		if err == nil {
+			// In this case the json.Number is a valid float64
+			in = reflect.ValueOf(float)
+			break
+		}
+		// fallback case - no number could be obtained
+		in = reflect.ValueOf(m.String())
 	case time.Time, *time.Time:
 		// Although time.Time implements TextMarshaler,
 		// we don't want to treat it as a string for YAML
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 4212770..692314d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,30 +1,41 @@
-# github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
+# github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
 github.com/alecthomas/template
 github.com/alecthomas/template/parse
-# github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
+# github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4
 github.com/alecthomas/units
 # github.com/beorn7/perks v1.0.0
 github.com/beorn7/perks/quantile
-# github.com/golang/protobuf v1.3.1
+# github.com/go-kit/kit v0.9.0
+github.com/go-kit/kit/log
+github.com/go-kit/kit/log/level
+# github.com/go-logfmt/logfmt v0.4.0
+github.com/go-logfmt/logfmt
+# github.com/golang/protobuf v1.3.2
 github.com/golang/protobuf/proto
 # github.com/hashicorp/golang-lru v0.5.1
 github.com/hashicorp/golang-lru
 github.com/hashicorp/golang-lru/simplelru
 # github.com/konsorten/go-windows-terminal-sequences v1.0.1
 github.com/konsorten/go-windows-terminal-sequences
+# github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
+github.com/kr/logfmt
 # github.com/matttproud/golang_protobuf_extensions v1.0.1
 github.com/matttproud/golang_protobuf_extensions/pbutil
+# github.com/pkg/errors v0.8.1
+github.com/pkg/errors
 # github.com/prometheus/client_golang v1.0.0
 github.com/prometheus/client_golang/prometheus
 github.com/prometheus/client_golang/prometheus/internal
 github.com/prometheus/client_golang/prometheus/promhttp
 # github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
 github.com/prometheus/client_model/go
-# github.com/prometheus/common v0.4.1
+# github.com/prometheus/common v0.7.0
 github.com/prometheus/common/expfmt
 github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
 github.com/prometheus/common/log
 github.com/prometheus/common/model
+github.com/prometheus/common/promlog
+github.com/prometheus/common/promlog/flag
 github.com/prometheus/common/version
 # github.com/prometheus/procfs v0.0.2
 github.com/prometheus/procfs
@@ -38,5 +49,5 @@ golang.org/x/sys/windows/registry
 golang.org/x/sys/windows/svc/eventlog
 # gopkg.in/alecthomas/kingpin.v2 v2.2.6
 gopkg.in/alecthomas/kingpin.v2
-# gopkg.in/yaml.v2 v2.2.1
+# gopkg.in/yaml.v2 v2.2.2
 gopkg.in/yaml.v2


Test patch:
diff --git a/bridge_test.go b/bridge_test.go
index e703c37..c8862df 100644
--- a/bridge_test.go
+++ b/bridge_test.go
@@ -16,6 +16,8 @@ package main
 import (
 	"reflect"
 	"testing"
+
+	"github.com/go-kit/kit/log"
 )
 
 func TestHandlePacket(t *testing.T) {
@@ -413,7 +415,7 @@ func TestHandlePacket(t *testing.T) {
 		},
 	}
 
-	for k, l := range []statsDPacketHandler{&StatsDUDPListener{}, &mockStatsDTCPListener{}} {
+	for k, l := range []statsDPacketHandler{&StatsDUDPListener{nil, nil, log.NewNopLogger()}, &mockStatsDTCPListener{StatsDTCPListener{nil, nil, log.NewNopLogger()}, log.NewNopLogger()}} {
 		events := make(chan Events, 32)
 		l.SetEventHandler(&unbufferedEventHandler{c: events})
 		for i, scenario := range scenarios {
diff --git a/exporter_benchmark_test.go b/exporter_benchmark_test.go
index 7978ce3..1c86479 100644
--- a/exporter_benchmark_test.go
+++ b/exporter_benchmark_test.go
@@ -17,6 +17,7 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/go-kit/kit/log"
 	"github.com/prometheus/statsd_exporter/pkg/mapper"
 )
 
@@ -138,7 +139,7 @@ mappings:
 		b.Fatalf("Config load error: %s %s", config, err)
 	}
 
-	ex := NewExporter(testMapper)
+	ex := NewExporter(testMapper, log.NewNopLogger())
 	for i := 0; i < b.N; i++ {
 		ec := make(chan Events, 1000)
 		go func() {
diff --git a/exporter_test.go b/exporter_test.go
index 262bd0d..ffc1d27 100644
--- a/exporter_test.go
+++ b/exporter_test.go
@@ -19,6 +19,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/go-kit/kit/log"
 	"github.com/prometheus/client_golang/prometheus"
 	dto "github.com/prometheus/client_model/go"
 
@@ -58,7 +59,7 @@ func TestNegativeCounter(t *testing.T) {
 	testMapper := mapper.MetricMapper{}
 	testMapper.InitCache(0)
 
-	ex := NewExporter(&testMapper)
+	ex := NewExporter(&testMapper, log.NewNopLogger())
 	ex.Listen(events)
 
 	updated := getTelemetryCounterValue(errorCounter)
@@ -139,7 +140,7 @@ mappings:
 		t.Fatalf("Config load error: %s %s", config, err)
 	}
 
-	ex := NewExporter(testMapper)
+	ex := NewExporter(testMapper, log.NewNopLogger())
 	ex.Listen(events)
 
 	metrics, err := prometheus.DefaultGatherer.Gather()
@@ -197,7 +198,7 @@ mappings:
 		t.Fatalf("Config load error: %s %s", config, err)
 	}
 
-	ex := NewExporter(testMapper)
+	ex := NewExporter(testMapper, log.NewNopLogger())
 	ex.Listen(events)
 
 	metrics, err := prometheus.DefaultGatherer.Gather()
@@ -412,7 +413,7 @@ mappings:
 				events <- s.in
 				close(events)
 			}()
-			ex := NewExporter(testMapper)
+			ex := NewExporter(testMapper, log.NewNopLogger())
 			ex.Listen(events)
 
 			metrics, err := prometheus.DefaultGatherer.Gather()
@@ -467,7 +468,7 @@ mappings:
 	errorCounter := errorEventStats.WithLabelValues("empty_metric_name")
 	prev := getTelemetryCounterValue(errorCounter)
 
-	ex := NewExporter(testMapper)
+	ex := NewExporter(testMapper, log.NewNopLogger())
 	ex.Listen(events)
 
 	updated := getTelemetryCounterValue(errorCounter)
@@ -492,7 +493,7 @@ func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
 	ueh := &unbufferedEventHandler{c: events}
 
 	go func() {
-		for _, l := range []statsDPacketHandler{&StatsDUDPListener{}, &mockStatsDTCPListener{}} {
+		for _, l := range []statsDPacketHandler{&StatsDUDPListener{nil, nil, log.NewNopLogger()}, &mockStatsDTCPListener{StatsDTCPListener{nil, nil, log.NewNopLogger()}, log.NewNopLogger()}} {
 			l.SetEventHandler(ueh)
 			l.handlePacket([]byte("bar:200|c|#tag:value\nbar:200|c|#tag:\xc3\x28invalid"))
 		}
@@ -502,7 +503,7 @@ func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
 	testMapper := mapper.MetricMapper{}
 	testMapper.InitCache(0)
 
-	ex := NewExporter(&testMapper)
+	ex := NewExporter(&testMapper, log.NewNopLogger())
 	ex.Listen(events)
 }
 
@@ -516,7 +517,7 @@ func TestSummaryWithQuantilesEmptyMapping(t *testing.T) {
 		testMapper := mapper.MetricMapper{}
 		testMapper.InitCache(0)
 
-		ex := NewExporter(&testMapper)
+		ex := NewExporter(&testMapper, log.NewNopLogger())
 		ex.Listen(events)
 	}()
 
@@ -560,7 +561,7 @@ func TestHistogramUnits(t *testing.T) {
 	go func() {
 		testMapper := mapper.MetricMapper{}
 		testMapper.InitCache(0)
-		ex := NewExporter(&testMapper)
+		ex := NewExporter(&testMapper, log.NewNopLogger())
 		ex.mapper.Defaults.TimerType = mapper.TimerTypeHistogram
 		ex.Listen(events)
 	}()
@@ -599,7 +600,7 @@ func TestCounterIncrement(t *testing.T) {
 	go func() {
 		testMapper := mapper.MetricMapper{}
 		testMapper.InitCache(0)
-		ex := NewExporter(&testMapper)
+		ex := NewExporter(&testMapper, log.NewNopLogger())
 		ex.Listen(events)
 	}()
 
@@ -647,6 +648,7 @@ type statsDPacketHandler interface {
 
 type mockStatsDTCPListener struct {
 	StatsDTCPListener
+	log.Logger
 }
 
 func (ml *mockStatsDTCPListener) handlePacket(packet []byte) {
@@ -726,7 +728,7 @@ mappings:
 	events := make(chan Events)
 	defer close(events)
 	go func() {
-		ex := NewExporter(testMapper)
+		ex := NewExporter(testMapper, log.NewNopLogger())
 		ex.Listen(events)
 	}()
 
@@ -954,7 +956,7 @@ func BenchmarkParseDogStatsDTags(b *testing.B) {
 		b.Run(name, func(b *testing.B) {
 			for n := 0; n < b.N; n++ {
 				labels := map[string]string{}
-				parseDogStatsDTags(tags, labels)
+				parseDogStatsDTags(tags, labels, log.NewNopLogger())
 			}
 		})
 	}
diff --git a/pkg/mapper/mapper_test.go b/pkg/mapper/mapper_test.go
index 93649c6..92bdd5f 100644
--- a/pkg/mapper/mapper_test.go
+++ b/pkg/mapper/mapper_test.go
@@ -842,31 +842,6 @@ mappings:
 			configBad:      true,
 			expectedAction: ActionTypeDrop,
 		},
-		{
-			// valid yaml example
-			config: `---
-mappings:
-- match: "test\\.(\\w+)\\.(\\w+)\\.counter"
-  match_type: regex
-  name: "${2}_total"
-  labels:
-    provider: "$1"
-`,
-			configBad:      false,
-			expectedAction: ActionTypeMap,
-		},
-		{
-			// invalid yaml example
-			config: `---
-mappings:
-- match: "test\.(\w+)\.(\w+)\.counter"
-  match_type: regex
-  name: "${2}_total"
-  labels:
-    provider: "$1"
-`,
-			configBad: true,
-		},
 	}
 
 	for i, scenario := range scenarios {