From c710b851c7828c85829f27dba2a6c0dc1cec1a3a Mon Sep 17 00:00:00 2001 From: mhartenbower Date: Wed, 9 Oct 2019 21:08:25 -0500 Subject: [PATCH 1/4] Switch to go-kit logging Fixes #270 Signed-off-by: mhartenbower --- exporter.go | 90 +-- exporter_benchmark_test.go | 3 +- exporter_test.go | 23 +- go.mod | 6 +- go.sum | 24 + main.go | 74 ++- vendor/github.com/alecthomas/template/go.mod | 1 + vendor/github.com/alecthomas/units/go.mod | 1 + vendor/github.com/go-kit/kit/LICENSE | 22 + vendor/github.com/go-kit/kit/log/README.md | 151 +++++ vendor/github.com/go-kit/kit/log/doc.go | 116 ++++ .../github.com/go-kit/kit/log/json_logger.go | 89 +++ vendor/github.com/go-kit/kit/log/level/doc.go | 22 + .../github.com/go-kit/kit/log/level/level.go | 205 ++++++ vendor/github.com/go-kit/kit/log/log.go | 135 ++++ .../go-kit/kit/log/logfmt_logger.go | 62 ++ .../github.com/go-kit/kit/log/nop_logger.go | 8 + vendor/github.com/go-kit/kit/log/stdlib.go | 116 ++++ vendor/github.com/go-kit/kit/log/sync.go | 116 ++++ vendor/github.com/go-kit/kit/log/value.go | 110 ++++ vendor/github.com/go-logfmt/logfmt/.gitignore | 4 + .../github.com/go-logfmt/logfmt/.travis.yml | 16 + .../github.com/go-logfmt/logfmt/CHANGELOG.md | 41 ++ vendor/github.com/go-logfmt/logfmt/LICENSE | 22 + vendor/github.com/go-logfmt/logfmt/README.md | 33 + vendor/github.com/go-logfmt/logfmt/decode.go | 237 +++++++ vendor/github.com/go-logfmt/logfmt/doc.go | 6 + vendor/github.com/go-logfmt/logfmt/encode.go | 322 +++++++++ vendor/github.com/go-logfmt/logfmt/fuzz.go | 126 ++++ vendor/github.com/go-logfmt/logfmt/go.mod | 3 + vendor/github.com/go-logfmt/logfmt/go.sum | 2 + .../github.com/go-logfmt/logfmt/jsonstring.go | 277 ++++++++ .../golang/protobuf/proto/properties.go | 5 +- vendor/github.com/kr/logfmt/.gitignore | 3 + vendor/github.com/kr/logfmt/Readme | 12 + vendor/github.com/kr/logfmt/decode.go | 184 ++++++ vendor/github.com/kr/logfmt/scanner.go | 149 +++++ vendor/github.com/kr/logfmt/unquote.go | 149 +++++ vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 15 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 282 ++++++++ vendor/github.com/pkg/errors/stack.go | 147 +++++ .../prometheus/common/expfmt/text_create.go | 18 +- .../prometheus/common/expfmt/text_parse.go | 13 +- .../prometheus/common/promlog/flag/flag.go | 45 ++ .../prometheus/common/promlog/log.go | 106 +++ vendor/golang.org/x/sys/unix/mkasm_darwin.go | 61 ++ vendor/golang.org/x/sys/unix/mkpost.go | 106 +++ vendor/golang.org/x/sys/unix/mksyscall.go | 407 ++++++++++++ .../x/sys/unix/mksyscall_aix_ppc.go | 415 ++++++++++++ .../x/sys/unix/mksyscall_aix_ppc64.go | 614 ++++++++++++++++++ .../x/sys/unix/mksyscall_solaris.go | 335 ++++++++++ vendor/golang.org/x/sys/unix/mksysnum.go | 190 ++++++ vendor/golang.org/x/sys/unix/types_aix.go | 236 +++++++ vendor/golang.org/x/sys/unix/types_darwin.go | 283 ++++++++ .../golang.org/x/sys/unix/types_dragonfly.go | 263 ++++++++ vendor/golang.org/x/sys/unix/types_freebsd.go | 356 ++++++++++ vendor/golang.org/x/sys/unix/types_netbsd.go | 289 +++++++++ vendor/golang.org/x/sys/unix/types_openbsd.go | 282 ++++++++ vendor/golang.org/x/sys/unix/types_solaris.go | 266 ++++++++ vendor/gopkg.in/yaml.v2/encode.go | 28 + vendor/modules.txt | 33 +- 65 files changed, 7768 insertions(+), 118 deletions(-) create mode 100644 vendor/github.com/alecthomas/template/go.mod create mode 100644 vendor/github.com/alecthomas/units/go.mod create mode 100644 vendor/github.com/go-kit/kit/LICENSE create mode 100644 vendor/github.com/go-kit/kit/log/README.md create mode 100644 vendor/github.com/go-kit/kit/log/doc.go create mode 100644 vendor/github.com/go-kit/kit/log/json_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/level/doc.go create mode 100644 vendor/github.com/go-kit/kit/log/level/level.go create mode 100644 vendor/github.com/go-kit/kit/log/log.go create mode 100644 vendor/github.com/go-kit/kit/log/logfmt_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/nop_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/stdlib.go create mode 100644 vendor/github.com/go-kit/kit/log/sync.go create mode 100644 vendor/github.com/go-kit/kit/log/value.go create mode 100644 vendor/github.com/go-logfmt/logfmt/.gitignore create mode 100644 vendor/github.com/go-logfmt/logfmt/.travis.yml create mode 100644 vendor/github.com/go-logfmt/logfmt/CHANGELOG.md create mode 100644 vendor/github.com/go-logfmt/logfmt/LICENSE create mode 100644 vendor/github.com/go-logfmt/logfmt/README.md create mode 100644 vendor/github.com/go-logfmt/logfmt/decode.go create mode 100644 vendor/github.com/go-logfmt/logfmt/doc.go create mode 100644 vendor/github.com/go-logfmt/logfmt/encode.go create mode 100644 vendor/github.com/go-logfmt/logfmt/fuzz.go create mode 100644 vendor/github.com/go-logfmt/logfmt/go.mod create mode 100644 vendor/github.com/go-logfmt/logfmt/go.sum create mode 100644 vendor/github.com/go-logfmt/logfmt/jsonstring.go create mode 100644 vendor/github.com/kr/logfmt/.gitignore create mode 100644 vendor/github.com/kr/logfmt/Readme create mode 100644 vendor/github.com/kr/logfmt/decode.go create mode 100644 vendor/github.com/kr/logfmt/scanner.go create mode 100644 vendor/github.com/kr/logfmt/unquote.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/prometheus/common/promlog/flag/flag.go create mode 100644 vendor/github.com/prometheus/common/promlog/log.go create mode 100644 vendor/golang.org/x/sys/unix/mkasm_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/mkpost.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/mksyscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/mksysnum.go create mode 100644 vendor/golang.org/x/sys/unix/types_aix.go create mode 100644 vendor/golang.org/x/sys/unix/types_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/types_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/types_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/types_solaris.go diff --git a/exporter.go b/exporter.go index a8dd607..70304e8 100644 --- a/exporter.go +++ b/exporter.go @@ -23,8 +23,9 @@ import ( "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 +33,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 +50,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 +119,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 +154,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 +173,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 +183,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 +198,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 +218,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,7 +228,7 @@ 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() } @@ -235,15 +237,16 @@ func (b *Exporter) handleEvent(event Event) { } 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 +278,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 +294,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 +304,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 +331,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 +371,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 +385,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 +401,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 +413,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 +422,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 +434,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 +449,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 +465,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 +478,7 @@ samples: type StatsDUDPListener struct { conn *net.UDPConn eventHandler eventHandler + logger log.Logger } func (l *StatsDUDPListener) SetEventHandler(eh eventHandler) { @@ -491,7 +495,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 +507,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 +530,7 @@ func (l *StatsDTCPListener) Listen() { if strings.HasSuffix(err.Error(), "use of closed network connection") { return } - log.Fatalf("AcceptTCP failed: %v", err) + panic(fmt.Sprintf("AcceptTCP failed: %s", err)) } go l.handleConn(c) } @@ -542,23 +547,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 +581,7 @@ func (l *StatsDUnixgramListener) Listen() { if strings.HasSuffix(err.Error(), "use of closed network connection") { return } - log.Fatal(err) + panic(err) } l.handlePacket(buf[:n]) } @@ -586,6 +592,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/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..c6e1c26 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) @@ -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) }() @@ -726,7 +727,7 @@ mappings: events := make(chan Events) defer close(events) go func() { - ex := NewExporter(testMapper) + ex := NewExporter(testMapper, log.NewNopLogger()) ex.Listen(events) }() @@ -954,7 +955,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/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..4d4977a 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" @@ -47,13 +51,13 @@ func serveHTTP(listenAddress, metricsEndpoint string) { `)) }) - log.Fatal(http.ListenAndServe(listenAddress, nil)) + panic(http.ListenAndServe(listenAddress, nil)) } func ipPortFromString(addr string) (*net.IPAddr, int) { host, portStr, err := net.SplitHostPort(addr) if err != nil { - log.Fatal("Bad StatsD listening address", addr) + panic(fmt.Sprintf("Bad StatsD listening address: %s", addr)) } if host == "" { @@ -61,12 +65,12 @@ 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) + panic(fmt.Sprintf("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) + panic(fmt.Sprintf("Bad port %s: %s", portStr, err)) } return ip, port @@ -90,39 +94,39 @@ func tcpAddrFromString(addr string) *net.TCPAddr { } } -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,19 +148,21 @@ 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.") + panic("At least one of UDP/TCP/Unixgram listeners must be specified.") } - 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) @@ -168,13 +174,13 @@ func main() { udpListenAddr := udpAddrFromString(*statsdListenUDP) uconn, err := net.ListenUDP("udp", udpListenAddr) if err != nil { - log.Fatal(err) + panic(err) } if *readBuffer != 0 { err = uconn.SetReadBuffer(*readBuffer) if err != nil { - log.Fatal("Error setting UDP read buffer:", err) + panic(fmt.Sprintf("Error setting UDP read buffer: %s", err)) } } @@ -186,25 +192,25 @@ func main() { tcpListenAddr := tcpAddrFromString(*statsdListenTCP) tconn, err := net.ListenTCP("tcp", tcpListenAddr) if err != nil { - log.Fatal(err) + panic(err) } 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) + panic(fmt.Sprintf("Unixgram socket \"%s\" already exists", *statsdListenUnixgram)) } uxgconn, err := net.ListenUnixgram("unixgram", &net.UnixAddr{ Net: "unixgram", Name: *statsdListenUnixgram, }) if err != nil { - log.Fatal(err) + panic(err) } defer uxgconn.Close() @@ -212,11 +218,11 @@ func main() { if *readBuffer != 0 { err = uxgconn.SetReadBuffer(*readBuffer) if err != nil { - log.Fatal("Error setting Unixgram read buffer:", err) + panic(fmt.Sprintf("Error setting Unixgram read buffer: %s", err)) } } - 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 +233,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 +248,21 @@ func main() { if *mappingConfig != "" { err := mapper.InitFromFile(*mappingConfig, *cacheSize) if err != nil { - log.Fatal("Error loading config:", err) + panic(fmt.Sprintf("Error loading config: %s", err)) } if *dumpFSMPath != "" { - err := dumpFSM(mapper, *dumpFSMPath) + err := dumpFSM(mapper, *dumpFSMPath, logger) if err != nil { - log.Fatal("Error dumping FSM:", err) + panic(fmt.Sprintf("Error dumping FSM: %s", err)) } } } 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/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 @@ +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 @@ +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[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` + logRegexpTime = `(?P