<|start_filename|>state/state.go<|end_filename|>
package state

// State is a metric state interface
type State interface {
	// Set sets metric state
	Set(metric string, n int, labels ...map[string]string)
}


<|start_filename|>state/memory_test.go<|end_filename|>
package state

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewMemory(t *testing.T) {
	memory := NewMemory()

	metric1 := "metric1"
	metric2 := "metric2"
	metricState1 := 10
	metricState2 := 33
	metricState12 := 42

	memory.Set(metric1, metricState1)
	memory.Set(metric2, metricState2)

	metrics := memory.Metrics()

	assert.Equal(t, metricState1, metrics[metric1])
	assert.Equal(t, metricState2, metrics[metric2])

	memory.Set(metric1, metricState12)
	assert.Equal(t, metricState12, metrics[metric1])
}


<|start_filename|>client/noop.go<|end_filename|>
package client

import (
	"net/http"
	"sync"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/timer"
)

// Noop is Client implementation that does literally nothing
type Noop struct {
	sync.Mutex

	httpMetricCallback bucket.HTTPMetricNameAlterCallback
}

// NewNoop builds and returns new Noop instance
func NewNoop() *Noop {
	return &Noop{}
}

// BuildTimer builds timer to track metric timings
func (c *Noop) BuildTimer() timer.Timer {
	return &timer.Memory{}
}

// Close closes underlying client connection if any
func (c *Noop) Close() error {
	return nil
}

// TrackRequest tracks HTTP Request stats
func (c *Noop) TrackRequest(r *http.Request, t timer.Timer, success bool) Client {
	return c
}

// TrackOperation tracks custom operation
func (c *Noop) TrackOperation(section string, operation *bucket.MetricOperation, t timer.Timer, success bool) Client {
	return c
}

// TrackOperationN tracks custom operation with n diff
func (c *Noop) TrackOperationN(section string, operation *bucket.MetricOperation, t timer.Timer, n int, success bool) Client {
	return c
}

// TrackMetric tracks custom metric, w/out ok/fail additional sections
func (c *Noop) TrackMetric(section string, operation *bucket.MetricOperation) Client {
	return c
}

// TrackMetricN tracks custom metric with n diff, w/out ok/fail additional sections
func (c *Noop) TrackMetricN(section string, operation *bucket.MetricOperation, n int) Client {
	return c
}

// TrackState tracks metric absolute value
func (c *Noop) TrackState(section string, operation *bucket.MetricOperation, value int) Client {
	return c
}

// SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
func (c *Noop) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client {
	c.Lock()
	defer c.Unlock()

	c.httpMetricCallback = callback
	return c
}

// GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request
func (c *Noop) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback {
	c.Lock()
	defer c.Unlock()

	return c.httpMetricCallback
}

// SetHTTPRequestSection sets metric section for HTTP Request metrics
func (c *Noop) SetHTTPRequestSection(section string) Client {
	return c
}

// ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"
func (c *Noop) ResetHTTPRequestSection() Client {
	return c
}

// Handler returns metrics endpoint for prometheus backend
func (c *Noop) Handler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
	})
}


<|start_filename|>incrementer/prometheus.go<|end_filename|>
package incrementer

import (
	"sync"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/prometheus/client_golang/prometheus"
)

// Prometheus struct is Incrementer interface implementation that writes all metrics to Prometheus
type Prometheus struct {
	sync.Mutex

	counter        CounterVec
	counterFactory CounterFactory
}

// CounterVec interface for counter vectors in prometheus backend
type CounterVec interface {
	GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error)
	GetMetricWith(labels prometheus.Labels) (prometheus.Counter, error)
	WithLabelValues(lvs ...string) prometheus.Counter
	With(labels prometheus.Labels) prometheus.Counter
}

// PrometheusCounterFactory implements CounterFactory interface
type PrometheusCounterFactory struct{}

// NewPrometheusCounterFactory returns new PrometheusCounterFactory instance
func NewPrometheusCounterFactory() *PrometheusCounterFactory {
	return &PrometheusCounterFactory{}
}

// Create method returns new CounterVec instance with metric and labelKeys attributes
func (f *PrometheusCounterFactory) Create(metric string, labelKeys []string) CounterVec {
	p := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: metric,
			Help: " ",
		},
		labelKeys,
	)
	prometheus.Register(p)

	return p
}

// PrometheusIncrementerFactory implements Factory interface
type PrometheusIncrementerFactory struct{}

// NewPrometheusIncrementerFactory returns new NewPrometheusIncrementerFactory instance
func NewPrometheusIncrementerFactory() *PrometheusIncrementerFactory {
	return &PrometheusIncrementerFactory{}
}

// Create method returns new Prometheus incrementer instance
func (p *PrometheusIncrementerFactory) Create() Incrementer {
	return NewPrometheus(NewPrometheusCounterFactory())
}

// NewPrometheus creates new prometheus incrementer instance
func NewPrometheus(counterFactory CounterFactory) *Prometheus {
	return &Prometheus{counter: nil, counterFactory: counterFactory}
}

// Increment increments metric in prometheus
func (i *Prometheus) Increment(metric string, labels ...map[string]string) {
	var labelNames []string
	var labelValues []string

	if labels != nil {
		for k, v := range labels[0] {
			labelNames = append(labelNames, k)
			labelValues = append(labelValues, v)
		}
	}

	i.Lock()
	defer i.Unlock()

	if i.counter == nil {
		i.counter = i.counterFactory.Create(metric, labelNames)
	}

	i.counter.WithLabelValues(labelValues...).Inc()
}

// IncrementN increments metric by n in prometheus
func (i *Prometheus) IncrementN(metric string, n int, labels ...map[string]string) {
	var labelNames []string
	var labelValues []string

	if labels != nil {
		for k, v := range labels[0] {
			labelNames = append(labelNames, k)
			labelValues = append(labelValues, v)
		}
	}

	i.Lock()
	defer i.Unlock()

	if i.counter == nil {
		i.counter = i.counterFactory.Create(metric, labelNames)
	}

	i.counter.WithLabelValues(labelValues...).Add(float64(n))
}

// IncrementAll increments all metrics for given bucket in prometheus
func (i *Prometheus) IncrementAll(b bucket.Bucket) {

}

// IncrementAllN increments all metrics for given bucket in prometheus
func (i *Prometheus) IncrementAllN(b bucket.Bucket, n int) {
	incrementAllN(i, b, n)
}


<|start_filename|>client/statsd.go<|end_filename|>
package client

import (
	"net/http"
	"sync"
	"time"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/incrementer"
	"github.com/hellofresh/stats-go/log"
	"github.com/hellofresh/stats-go/state"
	"github.com/hellofresh/stats-go/timer"
	"gopkg.in/alexcesaro/statsd.v2"
)

// StatsD is Client implementation for statsd
type StatsD struct {
	sync.Mutex
	client             *statsd.Client
	httpMetricCallback bucket.HTTPMetricNameAlterCallback
	httpRequestSection string
	unicode            bool
}

// NewStatsD builds and returns new StatsD instance
func NewStatsD(addr string, prefix string, unicode bool) (*StatsD, error) {
	var options []statsd.Option

	if prefix != "" {
		options = append(options, statsd.Prefix(prefix))
	}

	if addr != "" {
		options = append(options, statsd.Address(addr))
	}

	log.Log("Trying to connect to statsd instance", map[string]interface{}{
		"addr":   addr,
		"prefix": prefix,
	}, nil)

	statsdClient, err := statsd.New(options...)
	if err != nil {
		log.Log("An error occurred while connecting to StatsD", map[string]interface{}{
			"addr":   addr,
			"prefix": prefix,
		}, err)
		return nil, err
	}

	client := &StatsD{client: statsdClient, unicode: unicode}
	client.ResetHTTPRequestSection()

	return client, nil
}

// BuildTimer builds timer to track metric timings
func (c *StatsD) BuildTimer() timer.Timer {
	return &timer.Memory{}
}

// Close statsd connection
func (c *StatsD) Close() error {
	c.client.Close()
	return nil
}

// TrackRequest tracks HTTP Request stats
func (c *StatsD) TrackRequest(r *http.Request, t timer.Timer, success bool) Client {
	b := bucket.NewHTTPRequest(c.httpRequestSection, r, success, c.httpMetricCallback, c.unicode)
	i := incrementer.NewStatsD(c.client)

	if nil != t {
		c.client.Timing(b.Metric(), int(t.Finish()/time.Millisecond))
	}
	i.IncrementAll(b)

	return c
}

// TrackOperation tracks custom operation
func (c *StatsD) TrackOperation(section string, operation *bucket.MetricOperation, t timer.Timer, success bool) Client {
	b := bucket.NewPlain(section, operation, success, c.unicode)
	i := incrementer.NewStatsD(c.client)

	if nil != t {
		c.client.Timing(b.MetricWithSuffix(), int(t.Finish()/time.Millisecond))
	}
	i.IncrementAll(b)

	return c
}

// TrackOperationN tracks custom operation with n diff
func (c *StatsD) TrackOperationN(section string, operation *bucket.MetricOperation, t timer.Timer, n int, success bool) Client {
	b := bucket.NewPlain(section, operation, success, c.unicode)
	i := incrementer.NewStatsD(c.client)

	if nil != t {
		c.client.Timing(b.MetricWithSuffix(), int(t.Finish()/time.Millisecond))
	}
	i.IncrementAllN(b, n)

	return c
}

// TrackMetric tracks custom metric, w/out ok/fail additional sections
func (c *StatsD) TrackMetric(section string, operation *bucket.MetricOperation) Client {
	b := bucket.NewPlain(section, operation, true, c.unicode)
	i := incrementer.NewStatsD(c.client)

	i.Increment(b.Metric())
	i.Increment(b.MetricTotal())

	return c
}

// TrackMetricN tracks custom metric with n diff, w/out ok/fail additional sections
func (c *StatsD) TrackMetricN(section string, operation *bucket.MetricOperation, n int) Client {
	b := bucket.NewPlain(section, operation, true, c.unicode)
	i := incrementer.NewStatsD(c.client)

	i.IncrementN(b.Metric(), n)
	i.IncrementN(b.MetricTotal(), n)

	return c
}

// TrackState tracks metric absolute value
func (c *StatsD) TrackState(section string, operation *bucket.MetricOperation, value int) Client {
	b := bucket.NewPlain(section, operation, true, c.unicode)
	s := state.NewStatsD(c.client)

	s.Set(b.Metric(), value)

	return c
}

// SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
func (c *StatsD) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client {
	c.Lock()
	defer c.Unlock()

	c.httpMetricCallback = callback
	return c
}

// GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request
func (c *StatsD) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback {
	c.Lock()
	defer c.Unlock()

	return c.httpMetricCallback
}

// SetHTTPRequestSection sets metric section for HTTP Request metrics
func (c *StatsD) SetHTTPRequestSection(section string) Client {
	c.Lock()
	defer c.Unlock()

	c.httpRequestSection = section
	return c
}

// ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"
func (c *StatsD) ResetHTTPRequestSection() Client {
	return c.SetHTTPRequestSection(bucket.SectionRequest)
}

// Handler returns metrics endpoint for prometheus backend
func (c *StatsD) Handler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
	})
}


<|start_filename|>state/log_test.go<|end_filename|>
package state

import (
	"testing"

	"github.com/hellofresh/stats-go/log"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLog_Set(t *testing.T) {
	var (
		logMessages []string
		logFields   []map[string]interface{}
	)
	log.SetHandler(func(msg string, fields map[string]interface{}, err error) {
		logMessages = append(logMessages, msg)
		logFields = append(logFields, fields)
	})

	metric1 := "metric1"
	metric2 := "metric2"
	metricState1 := 10
	metricState2 := 33

	logger := &Log{}
	logger.Set(metric1, metricState1)
	logger.Set(metric2, metricState2)

	require.Equal(t, 2, len(logMessages))
	assert.Equal(t, "Stats state set", logMessages[0])
	assert.Equal(t, metric1, logFields[0]["bucket"])
	assert.Equal(t, metricState1, logFields[0]["state"])

	assert.Equal(t, "Stats state set", logMessages[1])
	assert.Equal(t, metric2, logFields[1]["bucket"])
	assert.Equal(t, metricState2, logFields[1]["state"])
}


<|start_filename|>client/prometheus.go<|end_filename|>
package client

import (
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/incrementer"
	"github.com/hellofresh/stats-go/state"
	"github.com/hellofresh/stats-go/timer"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Prometheus is Client implementation for prometheus
type Prometheus struct {
	sync.Mutex

	unicode            bool
	httpMetricCallback bucket.HTTPMetricNameAlterCallback
	httpRequestSection string

	namespace  string
	incFactory incrementer.Factory
	stFactory  state.Factory

	increments map[string]incrementer.Incrementer
	states     map[string]state.State
	histograms map[string]*prometheus.HistogramVec
}

// NewPrometheus builds and returns new Prometheus instance
func NewPrometheus(namespace string, incFactory incrementer.Factory, stFactory state.Factory) *Prometheus {
	client := &Prometheus{
		namespace:  namespace,
		incFactory: incFactory,
		stFactory:  stFactory,
		increments: make(map[string]incrementer.Incrementer),
		states:     make(map[string]state.State),
		histograms: make(map[string]*prometheus.HistogramVec),
	}
	return client
}

// BuildTimer builds timer to track metric timings
func (c *Prometheus) BuildTimer() timer.Timer {
	return &timer.Memory{}
}

// Close closes underlying client connection if any
func (c *Prometheus) Close() error {
	return nil
}

// prepareMetric adds namespace to metric
func (c *Prometheus) prepareMetric(metric string) string {
	return c.namespace + "_" + metric
}

// getIncrementer calls incrementer factory if incrementer was not created before
func (c *Prometheus) getIncrementer(name string) incrementer.Incrementer {
	c.Lock()
	defer c.Unlock()

	increment, ok := c.increments[name]
	if !ok {
		increment = c.incFactory.Create()
		c.increments[name] = increment
	}

	return increment
}

// getState calls state factory if state objects was not created before
func (c *Prometheus) getState(name string) state.State {
	c.Lock()
	defer c.Unlock()

	st, ok := c.states[name]
	if !ok {
		st = c.stFactory.Create()
		c.states[name] = st
	}

	return st
}

// getHistogram creates new histogram instance from prometheus library if it was not created before or gets existing
func (c *Prometheus) getHistogram(name string, labels ...map[string]string) *prometheus.HistogramVec {
	var keys []string
	var values []string

	for key, value := range labels[0] {
		keys = append(keys, key)
		values = append(values, value)
	}

	c.Lock()
	defer c.Unlock()

	if _, ok := c.histograms[name]; !ok {
		c.histograms[name] = prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Name: name + "_seconds",
			Help: " ",
		}, keys)
		prometheus.Register(c.histograms[name])
	}
	return c.histograms[name]
}

// TrackRequest tracks HTTP Request stats
func (c *Prometheus) TrackRequest(r *http.Request, t timer.Timer, success bool) Client {
	b := bucket.NewHTTPRequest(c.httpRequestSection, r, success, c.httpMetricCallback, c.unicode)
	metric := b.Metric()
	metricTotal := b.MetricTotal()

	metric = strings.Replace(metric, "-.", "", -1)
	metric = strings.Replace(metric, ".-", "", -1)
	metric = strings.Replace(metric, "-", "", -1)
	metric = strings.Replace(metric, ".", "_", -1)

	metricTotal = strings.Replace(metricTotal, "-.", "", -1)
	metricTotal = strings.Replace(metricTotal, ".-", "", -1)
	metricTotal = strings.Replace(metricTotal, "-", "", -1)
	metricTotal = strings.Replace(metricTotal, ".", "_", -1)

	metricInc := c.getIncrementer(metric)
	metricTotalInc := c.getIncrementer(metricTotal)

	labels := map[string]string{"success": strconv.FormatBool(success), "action": r.Method}

	metric = c.prepareMetric(metric)
	metricTotal = c.prepareMetric(metricTotal)
	metricInc.Increment(metric, labels)
	metricTotalInc.Increment(metricTotal, labels)

	return c
}

// TrackOperation tracks custom operation
func (c *Prometheus) TrackOperation(section string, operation *bucket.MetricOperation, t timer.Timer, success bool) Client {
	b := bucket.NewPrometheus(section, operation, success, c.unicode)

	if operation.Labels == nil {
		operation.Labels = map[string]string{"success": strconv.FormatBool(success)}
	} else {
		operation.Labels["success"] = strconv.FormatBool(success)
	}

	c.TrackMetric(section, operation)

	if nil != t {
		var values []string

		h := c.getHistogram(c.prepareMetric(b.Metric()), operation.Labels)
		for _, value := range operation.Labels {
			values = append(values, value)
		}
		h.WithLabelValues(values...).Observe(float64(t.Finish() / time.Millisecond))
	}

	return c
}

// TrackOperationN tracks custom operation with n diff
func (c *Prometheus) TrackOperationN(section string, operation *bucket.MetricOperation, t timer.Timer, n int, success bool) Client {
	b := bucket.NewPrometheus(section, operation, success, c.unicode)

	if operation.Labels == nil {
		operation.Labels = map[string]string{"success": strconv.FormatBool(success)}
	} else {
		operation.Labels["success"] = strconv.FormatBool(success)
	}

	c.TrackMetricN(section, operation, n)

	if nil != t {
		var values []string

		h := c.getHistogram(c.prepareMetric(b.Metric()), operation.Labels)
		for _, value := range operation.Labels {
			values = append(values, value)
		}
		h.WithLabelValues(values...).Observe(float64(t.Finish() / time.Millisecond))
	}

	return c
}

// TrackMetric tracks custom metric, w/out ok/fail additional sections
func (c *Prometheus) TrackMetric(section string, operation *bucket.MetricOperation) Client {
	b := bucket.NewPrometheus(section, operation, true, c.unicode)
	metric := b.Metric()
	metricTotal := b.MetricTotal()

	metricInc := c.getIncrementer(metric)
	metricTotalInc := c.getIncrementer(metricTotal)

	metric = c.prepareMetric(metric)
	metricTotal = c.prepareMetric(metricTotal)
	metricInc.Increment(metric, operation.Labels)
	metricTotalInc.Increment(metricTotal, operation.Labels)

	return c
}

// TrackMetricN tracks custom metric with n diff, w/out ok/fail additional sections
func (c *Prometheus) TrackMetricN(section string, operation *bucket.MetricOperation, n int) Client {
	b := bucket.NewPrometheus(section, operation, true, c.unicode)
	metric := b.Metric()
	metricTotal := b.MetricTotal()

	metricInc := c.getIncrementer(metric)
	metricTotalInc := c.getIncrementer(metricTotal)

	metric = c.prepareMetric(metric)
	metricTotal = c.prepareMetric(metricTotal)
	metricInc.IncrementN(metric, n, operation.Labels)
	metricTotalInc.IncrementN(metricTotal, n, operation.Labels)

	return c
}

// TrackState tracks metric absolute value
func (c *Prometheus) TrackState(section string, operation *bucket.MetricOperation, value int) Client {
	b := bucket.NewPrometheus(section, operation, true, c.unicode)
	metric := b.Metric()

	st := c.getState(metric)

	metric = c.prepareMetric(metric)
	st.Set(metric, value, operation.Labels)

	return c
}

// SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
func (c *Prometheus) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client {
	c.Lock()
	defer c.Unlock()

	c.httpMetricCallback = callback
	return c
}

// GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request
func (c *Prometheus) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback {
	return c.httpMetricCallback
}

// SetHTTPRequestSection sets metric section for HTTP Request metrics
func (c *Prometheus) SetHTTPRequestSection(section string) Client {
	c.Lock()
	defer c.Unlock()

	c.httpRequestSection = section
	return c
}

// ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"
func (c *Prometheus) ResetHTTPRequestSection() Client {
	return c.SetHTTPRequestSection(bucket.SectionRequest)
}

// Handler returns metrics endpoint for prometheus backend
func (c *Prometheus) Handler() http.Handler {
	return promhttp.Handler()
}


<|start_filename|>client/client.go<|end_filename|>
package client

import (
	"net/http"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/timer"
)

// Client is an interface for different methods of gathering stats
type Client interface {
	// BuildTimer builds timer to track metric timings
	BuildTimer() timer.Timer

	// Close closes underlying client connection if any
	Close() error

	// TrackRequest tracks HTTP Request stats
	TrackRequest(r *http.Request, t timer.Timer, success bool) Client

	// TrackOperation tracks custom operation
	TrackOperation(section string, operation *bucket.MetricOperation, t timer.Timer, success bool) Client

	// TrackOperationN tracks custom operation with n diff
	TrackOperationN(section string, operation *bucket.MetricOperation, t timer.Timer, n int, success bool) Client

	// TrackMetric tracks custom metric, w/out ok/fail additional sections
	TrackMetric(section string, operation *bucket.MetricOperation) Client

	// TrackMetricN tracks custom metric with n diff, w/out ok/fail additional sections
	TrackMetricN(section string, operation *bucket.MetricOperation, n int) Client

	// TrackState tracks metric absolute value
	TrackState(section string, operation *bucket.MetricOperation, value int) Client

	// SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
	SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client

	// GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request
	GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback

	// SetHTTPRequestSection sets metric section for HTTP Request metrics
	SetHTTPRequestSection(section string) Client

	// ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"
	ResetHTTPRequestSection() Client

	// Handler returns metrics endpoint for prometheus backend
	Handler() http.Handler
}


<|start_filename|>client/memory.go<|end_filename|>
package client

import (
	"net/http"
	"sync"
	"time"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/incrementer"
	"github.com/hellofresh/stats-go/state"
	"github.com/hellofresh/stats-go/timer"
)

// Metric is a type for storing single duration metric
type Metric struct {
	Bucket  string
	Elapsed time.Duration
}

// Memory is Client implementation for tests
type Memory struct {
	sync.Mutex
	httpMetricCallback bucket.HTTPMetricNameAlterCallback
	httpRequestSection string
	unicode            bool

	TimerMetrics []Metric
	CountMetrics map[string]int
	StateMetrics map[string]int
}

// NewMemory builds and returns new Memory instance
func NewMemory(unicode bool) *Memory {
	client := &Memory{unicode: unicode}
	client.ResetHTTPRequestSection()
	client.resetMetrics()

	return client
}

func (c *Memory) resetMetrics() {
	c.TimerMetrics = []Metric{}
	c.CountMetrics = map[string]int{}
	c.StateMetrics = map[string]int{}
}

// BuildTimer builds timer to track metric timings
func (c *Memory) BuildTimer() timer.Timer {
	return &timer.Memory{}
}

// Close resets all collected stats
func (c *Memory) Close() error {
	c.resetMetrics()
	return nil
}

// TrackRequest tracks HTTP Request stats
func (c *Memory) TrackRequest(r *http.Request, t timer.Timer, success bool) Client {
	b := bucket.NewHTTPRequest(c.httpRequestSection, r, success, c.httpMetricCallback, c.unicode)
	i := incrementer.NewMemory()

	if nil != t {
		c.TimerMetrics = append(c.TimerMetrics, Metric{Bucket: b.Metric(), Elapsed: t.Finish()})
	}

	i.IncrementAll(b)
	for metric, value := range i.Metrics() {
		c.CountMetrics[metric] += value
	}

	return c
}

// TrackOperation tracks custom operation
func (c *Memory) TrackOperation(section string, operation *bucket.MetricOperation, t timer.Timer, success bool) Client {
	b := bucket.NewPlain(section, operation, success, true)
	i := incrementer.NewMemory()

	if nil != t {
		c.TimerMetrics = append(c.TimerMetrics, Metric{Bucket: b.MetricWithSuffix(), Elapsed: t.Finish()})
	}

	i.IncrementAll(b)
	for metric, value := range i.Metrics() {
		c.CountMetrics[metric] += value
	}

	return c
}

// TrackOperationN tracks custom operation with n diff
func (c *Memory) TrackOperationN(section string, operation *bucket.MetricOperation, t timer.Timer, n int, success bool) Client {
	b := bucket.NewPlain(section, operation, success, true)
	i := incrementer.NewMemory()

	if nil != t {
		c.TimerMetrics = append(c.TimerMetrics, Metric{Bucket: b.MetricWithSuffix(), Elapsed: t.Finish()})
	}

	i.IncrementAllN(b, n)
	for metric, value := range i.Metrics() {
		c.CountMetrics[metric] += value
	}

	return c
}

// TrackMetric tracks custom metric, w/out ok/fail additional sections
func (c *Memory) TrackMetric(section string, operation *bucket.MetricOperation) Client {
	b := bucket.NewPlain(section, operation, true, true)
	i := incrementer.NewMemory()

	i.Increment(b.Metric())
	i.Increment(b.MetricTotal())
	for metric, value := range i.Metrics() {
		c.CountMetrics[metric] += value
	}

	return c
}

// TrackMetricN tracks custom metric with n diff, w/out ok/fail additional sections
func (c *Memory) TrackMetricN(section string, operation *bucket.MetricOperation, n int) Client {
	b := bucket.NewPlain(section, operation, true, true)
	i := incrementer.NewMemory()

	i.IncrementN(b.Metric(), n)
	i.IncrementN(b.MetricTotal(), n)
	for metric, value := range i.Metrics() {
		c.CountMetrics[metric] += value
	}

	return c
}

// TrackState tracks metric absolute value
func (c *Memory) TrackState(section string, operation *bucket.MetricOperation, value int) Client {
	b := bucket.NewPlain(section, operation, true, true)
	s := state.NewMemory()

	s.Set(b.Metric(), value)
	for metric, value := range s.Metrics() {
		c.StateMetrics[metric] = value
	}

	return c
}

// SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
func (c *Memory) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client {
	c.Lock()
	defer c.Unlock()

	c.httpMetricCallback = callback
	return c
}

// GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request
func (c *Memory) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback {
	c.Lock()
	defer c.Unlock()

	return c.httpMetricCallback
}

// SetHTTPRequestSection sets metric section for HTTP Request metrics
func (c *Memory) SetHTTPRequestSection(section string) Client {
	c.Lock()
	defer c.Unlock()

	c.httpRequestSection = section
	return c
}

// ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"
func (c *Memory) ResetHTTPRequestSection() Client {
	return c.SetHTTPRequestSection(bucket.SectionRequest)
}

// Handler returns metrics endpoint for prometheus backend
func (c *Memory) Handler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
	})
}


<|start_filename|>client.go<|end_filename|>
package stats

import (
	"errors"
	"net/url"
	"strconv"
	"strings"

	"github.com/hellofresh/stats-go/client"
	"github.com/hellofresh/stats-go/incrementer"
	"github.com/hellofresh/stats-go/state"
)

const (
	// StatsD is a dsn scheme value for statsd client
	statsD = "statsd"
	// prometheus is a dsn scheme value for prometheus client
	prometheus = "prometheus"
	// Log is a dsn scheme value for log client
	log = "log"
	// Memory is a dsn scheme value for memory client
	memory = "memory"
	// Noop is a dsn scheme value for noop client
	noop = "noop"
)

// ErrUnknownClient is an error returned when trying to create stats client of unknown type
var ErrUnknownClient = errors.New("unknown stats client type")

// NewClient creates and builds new stats client instance by given dsn
func NewClient(dsn string) (client.Client, error) {
	dsnURL, err := url.Parse(dsn)
	if err != nil {
		return nil, err
	}

	// do not care about parse error, as default value is set to false that is fine for us
	unicode, _ := strconv.ParseBool(dsnURL.Query().Get("unicode"))

	switch dsnURL.Scheme {
	case statsD:
		return client.NewStatsD(dsnURL.Host, strings.Trim(dsnURL.Path, "/"), unicode)
	case prometheus:
		return client.NewPrometheus(dsnURL.Host, incrementer.NewPrometheusIncrementerFactory(), state.NewPrometheusStateFactory()), nil
	case log:
		return client.NewLog(unicode), nil
	case memory:
		return client.NewMemory(unicode), nil
	case noop:
		return client.NewNoop(), nil
	}

	return nil, ErrUnknownClient
}


<|start_filename|>timer/memory.go<|end_filename|>
package timer

import "time"

// Memory struct is Timer interface implementation that stores results in memory for further usage
type Memory struct {
	timerStart time.Time
}

// StartAt starts timer at a given time
func (t *Memory) StartAt(s time.Time) Timer {
	t.timerStart = s
	return t
}

// Start starts timer
func (t *Memory) Start() Timer {
	t.timerStart = time.Now()
	return t
}

// Finish returns elapsed duration
func (t *Memory) Finish() time.Duration {
	return time.Now().Sub(t.timerStart)
}


<|start_filename|>bucket/section_test_callback.go<|end_filename|>
package bucket

import (
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/hellofresh/stats-go/log"
)

const (
	sectionsDelimiter   = ":"
	logSuspiciousMetric = "Second level ID auto-discover found suspicious metric"

	// SectionTestTrue is a name for "stats.TestAlwaysTrue" test callback function
	SectionTestTrue = "true"
	// SectionTestIsNumeric is a name for "stats.TestIsNumeric" test callback function
	SectionTestIsNumeric = "numeric"
	// SectionTestIsNotEmpty is a name for "stats.TestIsNotEmpty" test callback function
	SectionTestIsNotEmpty = "not_empty"
)

var (
	// ErrInvalidFormat error indicates that sections string has invalid format
	ErrInvalidFormat = errors.New("invalid sections format")

	// ErrUnknownSectionTest error indicates that section has unknown test callback name
	ErrUnknownSectionTest = errors.New("unknown section test")

	// ErrLooksLikeID error indicates that second level ID auto-discover found suspicious metric
	ErrLooksLikeID = errors.New("metric looks like ID")
)

// PathSection type represents single path section string
type PathSection string

// SectionTestCallback type represents section test callback function
type SectionTestCallback func(PathSection) bool

// SectionTestDefinition type represents section test callback definition
type SectionTestDefinition struct {
	Name     string
	Callback SectionTestCallback
}

// SectionsTestsMap type represents section test callbacks definitions map
type SectionsTestsMap map[PathSection]SectionTestDefinition

// String returns pretty formatted string representation of SectionsTestsMap
func (m SectionsTestsMap) String() string {
	var sections []string
	for k, v := range m {
		sections = append(sections, fmt.Sprintf("%s: %s", k, v.Name))
	}

	sort.Strings(sections)
	return fmt.Sprintf("[%s]", strings.Join(sections, ", "))
}

// TestAlwaysTrue section test callback function that gives true result to any section
func TestAlwaysTrue(PathSection) bool {
	return true
}

// TestIsNumeric section test callback function that gives true result if section is numeric
func TestIsNumeric(s PathSection) bool {
	_, err := strconv.Atoi(string(s))
	return err == nil
}

// TestIsNotEmpty section test callback function that gives true result if section is not empty placeholder ("-")
func TestIsNotEmpty(s PathSection) bool {
	return string(s) != MetricEmptyPlaceholder
}

var (
	sectionsTestSync     sync.Mutex
	sectionsTestRegistry = map[string]SectionTestCallback{
		SectionTestTrue:       TestAlwaysTrue,
		SectionTestIsNumeric:  TestIsNumeric,
		SectionTestIsNotEmpty: TestIsNotEmpty,
	}
)

// SecondLevelIDConfig configuration struct for second level ID callback
type SecondLevelIDConfig struct {
	HasIDAtSecondLevel    SectionsTestsMap
	AutoDiscoverThreshold uint
	AutoDiscoverWhiteList []string

	autoDiscoverStorage  *metricStorage
	autoDiscoverWhiteMap map[string]bool
}

// NewHasIDAtSecondLevelCallback returns HttpMetricNameAlterCallback implementation that checks for IDs
// on the second level of HTTP Request path
func NewHasIDAtSecondLevelCallback(config *SecondLevelIDConfig) HTTPMetricNameAlterCallback {
	if config.AutoDiscoverThreshold > 0 {
		config.autoDiscoverStorage = newMetricStorage(config.AutoDiscoverThreshold)

		// convert array to map for easier search
		config.autoDiscoverWhiteMap = make(map[string]bool, len(config.AutoDiscoverWhiteList))
		for _, val := range config.AutoDiscoverWhiteList {
			config.autoDiscoverWhiteMap[val] = true
		}
	}

	return func(operation *MetricOperation, r *http.Request) *MetricOperation {
		firstFragment := "/"
		for _, fragment := range strings.Split(r.URL.Path, "/") {
			if fragment != "" {
				firstFragment = fragment
				break
			}
		}

		if testFunction, ok := config.HasIDAtSecondLevel[PathSection(firstFragment)]; ok {
			if testFunction.Callback(PathSection(operation.operations[2])) {
				operation.operations[2] = MetricIDPlaceholder
			}
		} else if config.AutoDiscoverThreshold > 0 {
			if _, ok := config.autoDiscoverWhiteMap[firstFragment]; !ok {
				if config.autoDiscoverStorage.LooksLikeID(firstFragment, operation.operations[2]) {
					log.Log("Second level ID auto-discover found suspicious metric", map[string]interface{}{
						"method":    r.Method,
						"path":      r.URL.Path,
						"operation": operation,
					}, ErrLooksLikeID)

					operation.operations[2] = MetricIDPlaceholder
				}
			}
		}

		return operation
	}
}

// RegisterSectionTest registers new section test callback function with its name
func RegisterSectionTest(name string, callback SectionTestCallback) {
	sectionsTestSync.Lock()
	defer sectionsTestSync.Unlock()

	sectionsTestRegistry[name] = callback
}

// GetSectionTestCallback returns section test callback function by name
func GetSectionTestCallback(name string) SectionTestCallback {
	sectionsTestSync.Lock()
	defer sectionsTestSync.Unlock()

	return sectionsTestRegistry[name]
}

// ParseSectionsTestsMap parses string into SectionsTestsMap.
// In most cases string comes as config to the application e.g. from env.
// Valid string formats are:
// 1. <section-0>:<test-callback-name-0>:<section-1>:<test-callback-name-1>:<section-2>:<test-callback-name-2>
// 2. <section-0>:<test-callback-name-0>\n<section-1>:<test-callback-name-1>\n<section-2>:<test-callback-name-2>
// 3. <section-0>:<test-callback-name-0>:<section-1>:<test-callback-name-1>\n<section-2>:<test-callback-name-2>
func ParseSectionsTestsMap(s string) (SectionsTestsMap, error) {
	result := make(SectionsTestsMap)
	var parts []string

	for _, line := range strings.Split(s, "\n") {
		if strings.TrimSpace(line) != "" {
			for _, part := range strings.Split(strings.TrimSpace(line), sectionsDelimiter) {
				if strings.TrimSpace(part) != "" {
					parts = append(parts, part)
				}
			}
		}
	}
	if len(parts)%2 != 0 {
		return nil, ErrInvalidFormat
	}

	for i := 0; i < len(parts); i += 2 {
		pathSection := PathSection(parts[i])
		sectionTestName := parts[i+1]

		sectionTestCallback := GetSectionTestCallback(sectionTestName)
		if sectionTestCallback == nil {
			return nil, ErrUnknownSectionTest
		}
		result[pathSection] = SectionTestDefinition{sectionTestName, sectionTestCallback}
	}

	return result, nil
}


<|start_filename|>state/log.go<|end_filename|>
package state

import (
	"github.com/hellofresh/stats-go/log"
)

// Log struct is State interface implementation that writes all states to log
type Log struct{}

// Set sets metric state
func (s *Log) Set(metric string, n int, labels ...map[string]string) {
	log.Log("Stats state set", map[string]interface{}{
		"bucket": metric,
		"state":  n,
	}, nil)
}


<|start_filename|>bucket/bucket_test.go<|end_filename|>
package bucket

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSanitizeMetricName(t *testing.T) {
	assert.Equal(t, "-", SanitizeMetricName("", false))

	assert.Equal(t, "-u-iunikod", SanitizeMetricName("юникод", true))
	assert.Equal(t, "-u-Apollon", SanitizeMetricName("Ἀπόλλων", true))
	assert.Equal(t, "-u-acougue", SanitizeMetricName("açougue", true))

	assert.Equal(t, "юникод", SanitizeMetricName("юникод", false))
	assert.Equal(t, "Ἀπόλλων", SanitizeMetricName("Ἀπόλλων", false))
	assert.Equal(t, "açougue", SanitizeMetricName("açougue", false))

	assert.Equal(t, "metric", SanitizeMetricName("metric", true))
	assert.Equal(t, "metric_with_dots", SanitizeMetricName("metric.with.dots", true))
	assert.Equal(t, "metric__with__underscores", SanitizeMetricName("metric_with_underscores", true))
	assert.Equal(t, "metric_with_dots__and__underscores", SanitizeMetricName("metric.with.dots_and_underscores", true))

	assert.Equal(t, "-u-iunikod_metrika", SanitizeMetricName("юникод.метрика", true))
}


<|start_filename|>hooks/logrus.go<|end_filename|>
package hooks

import (
	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/client"
	"github.com/sirupsen/logrus"
)

// LogrusHook is logrus hook for gathering stats on logged error
type LogrusHook struct {
	statsClient client.Client
	section     string
}

// NewLogrusHook creates a stats logger.
func NewLogrusHook(statsClient client.Client, section string) *LogrusHook {
	return &LogrusHook{statsClient: statsClient, section: section}
}

// Levels is logrus.Hook method implementation
func (h *LogrusHook) Levels() []logrus.Level {
	return []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}
}

// Fire is logrus.Hook method implementation
func (h *LogrusHook) Fire(e *logrus.Entry) error {
	m := bucket.NewMetricOperation(e.Level.String())
	h.statsClient.TrackMetric(h.section, m)
	return nil
}


<|start_filename|>context/context.go<|end_filename|>
package context

import (
	"context"

	"github.com/hellofresh/stats-go/client"
)

type statsKeyType int

const statsKey statsKeyType = iota

// New returns a context that has a stats Client
func New(ctx context.Context, client client.Client) context.Context {
	return context.WithValue(ctx, statsKey, client)
}

// WithContext returns a stats Client with as much context as possible
func WithContext(ctx context.Context) client.Client {
	ctxStats, ok := ctx.Value(statsKey).(client.Client)
	if !ok {
		return client.NewNoop()
	}

	return ctxStats
}


<|start_filename|>incrementer/prometheus_test.go<|end_filename|>
package incrementer

import (
	"testing"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
)

type CounterMock struct {
	prometheus.Metric
	prometheus.Collector
}

func (c *CounterMock) Set(float64) {}

func (c *CounterMock) Inc() {}

func (c *CounterMock) Add(float64) {}

type CounterVecMock struct {
	withLabelValuesCalls int
	values               []string
}

func (m *CounterVecMock) GetMetricWithLabelValues(lvs ...string) (prometheus.Counter, error) {
	return nil, nil
}

func (m *CounterVecMock) GetMetricWith(labels prometheus.Labels) (prometheus.Counter, error) {
	return nil, nil
}

func (m *CounterVecMock) WithLabelValues(lvs ...string) prometheus.Counter {
	m.withLabelValuesCalls++
	m.values = lvs
	return &CounterMock{}
}

func (m *CounterVecMock) With(labels prometheus.Labels) prometheus.Counter {
	return nil
}

type CounterFactoryMock struct {
	mock CounterVecMock
}

func (m *CounterFactoryMock) Create(metric string, labelKeys []string) CounterVec {
	m.mock = CounterVecMock{values: labelKeys}
	return &m.mock
}

func TestPrometheus_Increment(t *testing.T) {
	b := bucket.NewPrometheus("section", bucket.NewMetricOperation("o1", "o2", "o3"), true, true)
	m := &CounterFactoryMock{}
	i := NewPrometheus(m)

	i.Increment(b.Metric())
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 0, len(m.mock.values))
}

func TestPrometheus_IncrementWithLabels(t *testing.T) {
	b := bucket.NewPrometheus("section", bucket.NewMetricOperation("o1", "o2", "o3"), true, true)
	m := &CounterFactoryMock{}
	i := NewPrometheus(m)

	i.Increment(b.Metric(), map[string]string{"key1": "value1", "key2": "value2"})
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 2, len(m.mock.values))
}

func TestPrometheus_IncrementN(t *testing.T) {
	b := bucket.NewPrometheus("section", bucket.NewMetricOperation("o1", "o2", "o3"), true, true)
	m := &CounterFactoryMock{}
	i := NewPrometheus(m)

	i.IncrementN(b.Metric(), 123)
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 0, len(m.mock.values))
}

func TestPrometheus_IncrementNWithLabels(t *testing.T) {
	b := bucket.NewPrometheus("section", bucket.NewMetricOperation("o1", "o2", "o3"), true, true)
	m := &CounterFactoryMock{}
	i := NewPrometheus(m)

	i.Increment(b.Metric(), map[string]string{"key1": "value1", "key2": "value2"})
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 2, len(m.mock.values))
}


<|start_filename|>client/prometheus_test.go<|end_filename|>
package client

import (
	"testing"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/incrementer"
	"github.com/hellofresh/stats-go/state"
	"github.com/hellofresh/stats-go/timer"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
)

// Mock incrementer object
type mockIncrementer struct {
	incrementMethodCalled  int
	incrementMethodMetrics []string
	incrementMethodLabels  []map[string]string

	incrementNMethodCalled  int
	incrementNMethodMetrics []string
	incrementNMethodLabels  []map[string]string

	incrementALlMethodCalled  int
	incrementALlNMethodCalled int
}

func (i *mockIncrementer) Increment(metric string, labels ...map[string]string) {
	i.incrementMethodCalled++
	i.incrementMethodMetrics = append(i.incrementMethodMetrics, metric)
	if labels[0] != nil {
		i.incrementMethodLabels = append(i.incrementMethodLabels, labels[0])
	}
}

func (i *mockIncrementer) IncrementN(metric string, n int, labels ...map[string]string) {
	i.incrementNMethodCalled++
	i.incrementNMethodMetrics = append(i.incrementNMethodMetrics, metric)
	if labels[0] != nil {
		i.incrementNMethodLabels = append(i.incrementNMethodLabels, labels[0])
	}
}

func (i *mockIncrementer) IncrementAll(b bucket.Bucket) {
	i.incrementALlMethodCalled++
}

func (i *mockIncrementer) IncrementAllN(b bucket.Bucket, n int) {
	i.incrementALlNMethodCalled++
}

// Mock IncrementFactory object
type mockIncrementerFactory struct {
	inc *mockIncrementer

	createMethodCalled int
}

func newMockIncrementerFactory() *mockIncrementerFactory {
	return &mockIncrementerFactory{createMethodCalled: 0}
}

func (m *mockIncrementerFactory) Create() incrementer.Incrementer {
	m.createMethodCalled++
	if m.inc == nil {
		m.inc = &mockIncrementer{
			incrementMethodCalled:  0,
			incrementMethodMetrics: []string{},
			incrementMethodLabels:  []map[string]string{},

			incrementNMethodCalled:  0,
			incrementNMethodMetrics: []string{},
			incrementNMethodLabels:  []map[string]string{},

			incrementALlMethodCalled:  0,
			incrementALlNMethodCalled: 0,
		}
	}
	return m.inc
}

// Mock state object
type mockState struct {
	setMethodCalled  int
	setMethodNumbers []int
	setMethodMetrics []string
	setMethodLabels  []map[string]string
}

func (s *mockState) Set(metric string, n int, labels ...map[string]string) {
	s.setMethodCalled++
	s.setMethodMetrics = append(s.setMethodMetrics, metric)
	s.setMethodNumbers = append(s.setMethodNumbers, n)
	if labels[0] != nil {
		s.setMethodLabels = append(s.setMethodLabels, labels[0])
	}
}

// Mock StateFactory object
type mockStateFactory struct {
	s *mockState

	createMethodCalled int
}

func newMockStateFactory() *mockStateFactory {
	return &mockStateFactory{createMethodCalled: 0}
}

func (m *mockStateFactory) Create() state.State {
	m.createMethodCalled++
	if m.s == nil {
		m.s = &mockState{
			setMethodCalled:  0,
			setMethodMetrics: []string{},
			setMethodNumbers: []int{},
			setMethodLabels:  []map[string]string{},
		}
	}
	return m.s
}

// Tests block begin

func TestPrometheusClient_NewPrometheus(t *testing.T) {
	p := NewPrometheus("namespace", newMockIncrementerFactory(), newMockStateFactory())
	assert.IsType(t, &Prometheus{}, p)
	assert.Equal(t, "namespace", p.namespace)
	assert.IsType(t, &mockIncrementerFactory{}, p.incFactory)
	assert.IsType(t, &mockStateFactory{}, p.stFactory)
	assert.IsType(t, map[string]incrementer.Incrementer{}, p.increments)
	assert.IsType(t, map[string]state.State{}, p.states)
	assert.IsType(t, map[string]*prometheus.HistogramVec{}, p.histograms)
}

func TestPrometheusClient_BuildTimer(t *testing.T) {
	p := NewPrometheus("namespace", newMockIncrementerFactory(), newMockStateFactory())
	tt := p.BuildTimer()
	_, ok := tt.(*timer.Memory)
	assert.True(t, ok)
}

func TestPrometheusClient_Close(t *testing.T) {
	p := NewPrometheus("namespace", newMockIncrementerFactory(), newMockStateFactory())
	tt := p.Close()
	assert.Nil(t, tt)
}

func TestPrometheusClient_TrackMetric(t *testing.T) {
	m := newMockIncrementerFactory()
	p := NewPrometheus("namespace", m, newMockStateFactory())
	p.TrackMetric("section", bucket.NewMetricOperation("foo", "bar", "baz"))

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 2, m.inc.incrementMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementMethodMetrics)
	assert.Equal(t, []map[string]string{}, m.inc.incrementMethodLabels)
}

func TestPrometheusClient_TrackMetricIncrementsAlreadyExists(t *testing.T) {
	m := newMockIncrementerFactory()
	p := NewPrometheus("namespace", m, newMockStateFactory())
	p.TrackMetric("section", bucket.NewMetricOperation("foo", "bar", "baz"))
	p.TrackMetric("section", bucket.NewMetricOperation("foo", "bar", "baz"))

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 4, m.inc.incrementMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section", "namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementMethodMetrics)
	assert.Equal(t, []map[string]string{}, m.inc.incrementMethodLabels)

	assert.Equal(t, 0, m.inc.incrementNMethodCalled)

	assert.Equal(t, 0, m.inc.incrementALlMethodCalled)
	assert.Equal(t, 0, m.inc.incrementALlNMethodCalled)
}

func TestPrometheusClient_TrackMetricN(t *testing.T) {
	m := newMockIncrementerFactory()
	p := NewPrometheus("namespace", m, newMockStateFactory())
	p.TrackMetricN("section", bucket.NewMetricOperation("foo", "bar", "baz"), 999)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 0, m.inc.incrementMethodCalled)

	assert.Equal(t, 2, m.inc.incrementNMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementNMethodMetrics)
	assert.Equal(t, []map[string]string{}, m.inc.incrementNMethodLabels)

	assert.Equal(t, 0, m.inc.incrementALlMethodCalled)
	assert.Equal(t, 0, m.inc.incrementALlNMethodCalled)
}

func TestPrometheusClient_TrackState(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)
	p.TrackState("section", bucket.NewMetricOperation("foo", "bar", "baz"), 888)
	assert.Equal(t, 1, s.createMethodCalled)

	assert.Equal(t, 1, s.s.setMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz"}, s.s.setMethodMetrics)
	assert.Equal(t, []int{888}, s.s.setMethodNumbers)
	assert.Equal(t, []map[string]string{}, s.s.setMethodLabels)
}

func TestPrometheusClient_TrackStateAlreadyExists(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)
	p.TrackState("section", bucket.NewMetricOperation("foo", "bar", "baz"), 888)
	p.TrackState("section", bucket.NewMetricOperation("foo", "bar", "baz"), 888)
	assert.Equal(t, 1, s.createMethodCalled)

	assert.Equal(t, 2, s.s.setMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_section_foo_bar_baz"}, s.s.setMethodMetrics)
	assert.Equal(t, []int{888, 888}, s.s.setMethodNumbers)
	assert.Equal(t, []map[string]string{}, s.s.setMethodLabels)
}

func TestPrometheusClient_TrackOperation(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)

	p.TrackOperation("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, true)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 2, m.inc.incrementMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}}, m.inc.incrementMethodLabels)
}

func TestPrometheusClient_TrackOperationWithTimer(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()

	p := NewPrometheus("namespace", m, s)

	tt := p.BuildTimer()

	p.TrackOperation("section", bucket.NewMetricOperation("foo", "bar", "baz"), tt, true)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 2, m.inc.incrementMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}}, m.inc.incrementMethodLabels)

	assert.NotNil(t, p.histograms["namespace_section_foo_bar_baz"])
	_, err := p.histograms["namespace_section_foo_bar_baz"].GetMetricWithLabelValues("true")
	assert.Nil(t, err)
}

func TestPrometheusClient_TrackOperationAlreadyExists(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)

	p.TrackOperation("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, true)
	p.TrackOperation("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, false)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 4, m.inc.incrementMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section", "namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}, {"success": "false"}, {"success": "false"}}, m.inc.incrementMethodLabels)
}

func TestPrometheusClient_TrackOperationN(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)

	p.TrackOperationN("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, 999, true)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 2, m.inc.incrementNMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementNMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}}, m.inc.incrementNMethodLabels)
}

func TestPrometheusClient_TrackOperationNWithTimer(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()

	p := NewPrometheus("namespace", m, s)

	tt := p.BuildTimer()

	p.TrackOperationN("section", bucket.NewMetricOperation("foo", "bar", "baz"), tt, 999, true)

	assert.Equal(t, 2, m.createMethodCalled)
	assert.Equal(t, 2, m.inc.incrementNMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementNMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}}, m.inc.incrementNMethodLabels)

	assert.NotNil(t, p.histograms["namespace_section_foo_bar_baz"])
	_, err := p.histograms["namespace_section_foo_bar_baz"].GetMetricWithLabelValues("true")
	assert.Nil(t, err)

}

func TestPrometheusClient_TrackOperationNAlreadyExists(t *testing.T) {
	m := newMockIncrementerFactory()
	s := newMockStateFactory()
	p := NewPrometheus("namespace", m, s)

	p.TrackOperationN("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, 1, true)
	p.TrackOperationN("section", bucket.NewMetricOperation("foo", "bar", "baz"), nil, 2, false)

	assert.Equal(t, 2, m.createMethodCalled)

	assert.Equal(t, 4, m.inc.incrementNMethodCalled)
	assert.Equal(t, []string{"namespace_section_foo_bar_baz", "namespace_total_section", "namespace_section_foo_bar_baz", "namespace_total_section"}, m.inc.incrementNMethodMetrics)
	assert.Equal(t, []map[string]string{{"success": "true"}, {"success": "true"}, {"success": "false"}, {"success": "false"}}, m.inc.incrementNMethodLabels)
}


<|start_filename|>bucket/section_test_callback_test.go<|end_filename|>
package bucket

import (
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	testKey = "testKey"
)

func Test_sectionsTestRegistry(t *testing.T) {
	assert.NotNil(t, GetSectionTestCallback(SectionTestTrue))
	assert.NotNil(t, GetSectionTestCallback(SectionTestIsNumeric))
	assert.NotNil(t, GetSectionTestCallback(SectionTestIsNotEmpty))

	assert.Nil(t, GetSectionTestCallback(testKey))

	RegisterSectionTest(testKey, func(PathSection) bool {
		return false
	})
	assert.NotNil(t, GetSectionTestCallback(testKey))
}

func parseAndAssertParseResults(t *testing.T, s string) {
	m, err := ParseSectionsTestsMap(s)
	require.NoError(t, err)
	assert.NotEmpty(t, reflect.ValueOf(m).MapKeys())

	callback, ok := m["foo"]
	assert.True(t, ok)
	assert.NotNil(t, callback)
	assert.True(t, callback.Callback("foo"))

	callback, ok = m["bar"]
	assert.True(t, ok)
	assert.NotNil(t, callback)
	assert.True(t, callback.Callback("12"))
	assert.False(t, callback.Callback("~12"))

	callback, ok = m["baz"]
	assert.True(t, ok)
	assert.NotNil(t, callback)
	assert.True(t, callback.Callback("12"))
	assert.True(t, callback.Callback("~12"))
	assert.False(t, callback.Callback("-"))

	callback, ok = m["qaz"]
	assert.False(t, ok)
}

func TestRegisterSectionTest(t *testing.T) {
	m, err := ParseSectionsTestsMap("")
	assert.Nil(t, err)
	assert.Empty(t, reflect.ValueOf(m).MapKeys())

	parseAndAssertParseResults(t, "foo:true:bar:numeric:baz:not_empty")
	parseAndAssertParseResults(t, "foo:true\nbar:numeric:baz:not_empty")
	parseAndAssertParseResults(t, "\nfoo:true\nbar:numeric:baz:not_empty")
	parseAndAssertParseResults(t, "\nfoo:true:bar:numeric\nbaz:not_empty")
	parseAndAssertParseResults(t, "\nfoo:true\nbar:numeric\nbaz:not_empty")
	parseAndAssertParseResults(t, "\nfoo:true\nbar:numeric\nbaz:not_empty\n")
}

func TestRegisterSectionTest_ErrInvalidFormat(t *testing.T) {
	_, err := ParseSectionsTestsMap("foo")
	assert.Equal(t, err, ErrInvalidFormat)

	_, err = ParseSectionsTestsMap("foo:bar:baz")
	assert.Equal(t, err, ErrInvalidFormat)
}

func TestRegisterSectionTest_ErrUnknownSectionTest(t *testing.T) {
	_, err := ParseSectionsTestsMap("foo:NOT_EISTS")
	assert.Equal(t, err, ErrUnknownSectionTest)

	_, err = ParseSectionsTestsMap("foo:true:baz:NOT_EISTS")
	assert.Equal(t, err, ErrUnknownSectionTest)
}

func TestSectionsTestsMap_String(t *testing.T) {
	m, err := ParseSectionsTestsMap("foo:true:bar:numeric:baz:not_empty")
	require.NoError(t, err)

	assert.Equal(t, "[bar: numeric, baz: not_empty, foo: true]", m.String())
}


<|start_filename|>hooks/logrus_test.go<|end_filename|>
package hooks

import (
	"io/ioutil"
	"testing"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/client"
	log "github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

func TestNewLogrusHook(t *testing.T) {
	statsClient := client.NewMemory(true)
	section := "errors"
	b := bucket.NewPlain(section, bucket.NewMetricOperation(log.ErrorLevel.String()), true, true)

	hook := NewLogrusHook(statsClient, section)
	log.AddHook(hook)
	log.SetLevel(log.DebugLevel)
	log.SetOutput(ioutil.Discard)

	log.Debug("debug")
	log.Info("info")
	log.Warn("warn")
	log.Error("error")

	assert.Equal(t, 2, len(statsClient.CountMetrics))
	assert.Equal(t, 1, statsClient.CountMetrics[b.Metric()])
	assert.Equal(t, 1, statsClient.CountMetrics[b.MetricTotal()])

	log.Errorf("error section: %s", section)
	log.Errorln("error line")

	assert.Equal(t, 2, len(statsClient.CountMetrics))
	assert.Equal(t, 3, statsClient.CountMetrics[b.Metric()])
	assert.Equal(t, 3, statsClient.CountMetrics[b.MetricTotal()])
}


<|start_filename|>incrementer/factory.go<|end_filename|>
package incrementer

// CounterFactory interface for making new CounterVec instances
type CounterFactory interface {
	Create(metric string, labelKeys []string) CounterVec
}

// Factory interface for making new incrementer instances
type Factory interface {
	Create() Incrementer
}


<|start_filename|>client_test.go<|end_filename|>
package stats

import (
	"testing"

	"github.com/hellofresh/stats-go/client"
	"github.com/stretchr/testify/assert"
)

func TestNewClient(t *testing.T) {
	statsClient, err := NewClient("log://")
	assert.NoError(t, err)
	assert.IsType(t, &client.Log{}, statsClient)

	statsClient, err = NewClient("memory://")
	assert.NoError(t, err)
	assert.IsType(t, &client.Memory{}, statsClient)

	statsClient, err = NewClient("noop://")
	assert.NoError(t, err)
	assert.IsType(t, &client.Noop{}, statsClient)

	statsClient, err = NewClient("unknown://")
	assert.Nil(t, statsClient)
	assert.Error(t, err)
	assert.Equal(t, ErrUnknownClient, err)
}


<|start_filename|>timer/duration.go<|end_filename|>
package timer

import (
	"time"
)

// Duration struct is Timer interface implementation that writes all timings to statsd
type Duration struct {
	duration time.Duration
}

// NewDuration creates new duration timer instance
func NewDuration(duration time.Duration) *Duration {
	return &Duration{duration: duration}
}

// StartAt starts timer at a given time
func (t *Duration) StartAt(s time.Time) Timer {
	return t
}

// Start starts timer
func (t *Duration) Start() Timer {
	return t
}

// Finish writes elapsed time for metric to statsd
func (t *Duration) Finish() time.Duration {
	return t.duration
}


<|start_filename|>bucket/bucket_prometheus_test.go<|end_filename|>
package bucket

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestPrometheus_Metric(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "foo_bar_baz_qaz"},
		{"foo", NewMetricOperation("bar", "baz", MetricEmptyPlaceholder), true, "foo_bar_baz"},
		{"foo", NewMetricOperation("bar", "dot.baz", MetricEmptyPlaceholder), true, "foo_bar_dot_baz"},
		{"foo", NewMetricOperation("bar", "underscore_baz", MetricEmptyPlaceholder), true, "foo_bar_underscorebaz"},
		{"foo.foo", NewMetricOperation("bar", "underscore_baz", MetricEmptyPlaceholder), true, "foo_foo_bar_underscorebaz"},
	}

	for _, data := range dataProvider {
		b := NewPrometheus(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.Metric())
	}
}

func BenchmarkNewPrometheus(b *testing.B) {
	operation := NewMetricOperation("bar", "baz", "qaz")
	for n := 0; n < b.N; n++ {
		NewPrometheus("foo", operation, true, false)
	}
}

func BenchmarkPrometheus_Metric(b *testing.B) {
	bucket := NewPrometheus("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.Metric()
	}
}

func BenchmarkPrometheus_MetricTotal(b *testing.B) {
	bucket := NewPrometheus("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.MetricTotal()
	}
}

func TestPrometheus_MetricTotal(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "total_foo"},
		{"foo", NewMetricOperation("bar", "baz", "qaz"), false, "total_foo"},
	}

	for _, data := range dataProvider {
		b := NewPrometheus(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.MetricTotal())
	}
}


<|start_filename|>doc.go<|end_filename|>
/*
This is generic stats library that we at HelloFresh use in our projects to collect services' stats and then create monitoring
dashboards to track activity and problems.

For a full guide visit https://github.com/hellofresh/stats-go
*/

package stats


<|start_filename|>log/logging.go<|end_filename|>
package log

import (
	"fmt"
	l "log"
	"os"
	"strings"
)

// Handler defines logger type to override stats debug and error logging
type Handler func(msg string, fields map[string]interface{}, err error)

var handler Handler

func init() {
	logger := l.New(os.Stderr, "[StatsGo] ", l.LstdFlags)

	SetHandler(func(msg string, fields map[string]interface{}, err error) {
		if nil != err {
			if nil == fields {
				fields = make(map[string]interface{})
			}
			fields["error"] = err.Error()
		}

		msgParts := make([]string, len(fields)+1)

		msgParts[0] = msg
		idx := 1
		for k, v := range fields {
			msgParts[idx] = fmt.Sprintf("%s=%v", k, v)
			idx++
		}

		logger.Println(strings.Join(msgParts, "\t"))
	})
}

// SetHandler sets log handler to use for stats debug and error logging
func SetHandler(h Handler) {
	handler = h
}

// Log calls log handler
func Log(msg string, fields map[string]interface{}, err error) {
	handler(msg, fields, err)
}


<|start_filename|>bucket/metric_storage.go<|end_filename|>
package bucket

import "sync"

type metricStorage struct {
	sync.Mutex

	threshold uint
	metrics   map[string]map[string]uint
}

func newMetricStorage(threshold uint) *metricStorage {
	return &metricStorage{threshold: threshold, metrics: make(map[string]map[string]uint)}
}

func (s *metricStorage) LooksLikeID(firstSection, secondSection string) bool {
	s.Lock()
	defer s.Unlock()

	if _, ok := s.metrics[firstSection]; !ok {
		s.metrics[firstSection] = make(map[string]uint, s.threshold)
	}

	// avoid storing all values to avoid memory loss
	if uint(len(s.metrics[firstSection])) < s.threshold {
		s.metrics[firstSection][secondSection]++
	}

	return uint(len(s.metrics[firstSection])) >= s.threshold
}


<|start_filename|>Makefile<|end_filename|>
NO_COLOR=\033[0m
OK_COLOR=\033[32;01m
ERROR_COLOR=\033[31;01m
WARN_COLOR=\033[33;01m

.PHONY: test lint

all: lint test

test:
	@echo "$(OK_COLOR)==> Running unit tests$(NO_COLOR)"
	@go test -cover ./... -coverprofile=coverage.txt -covermode=atomic

lint:
	@echo "$(OK_COLOR)==> Linting with golangci-lint$(NO_COLOR)"
	@docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.31 golangci-lint run -v


<|start_filename|>bucket/bucket_plain_test.go<|end_filename|>
package bucket

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestPlain_Metric(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "foo.bar.baz.qaz"},
		{"foo", NewMetricOperation("bar", "baz", MetricEmptyPlaceholder), true, "foo.bar.baz.-"},
		{"foo", NewMetricOperation("bar", "dot.baz", MetricEmptyPlaceholder), true, "foo.bar.dot_baz.-"},
		{"foo", NewMetricOperation("bar", "underscore_baz", MetricEmptyPlaceholder), true, "foo.bar.underscore__baz.-"},
		{"foo.foo", NewMetricOperation("bar", "underscore_baz", MetricEmptyPlaceholder), true, "foo_foo.bar.underscore__baz.-"},
		{"foo", NewMetricOperation("bar"), true, "foo.bar.-.-"},
		{"foo", NewMetricOperation("bar", "baz"), true, "foo.bar.baz.-"},
		{"foo", NewMetricOperation("bar", "baz", "qaz", "vaz"), true, "foo.bar.baz.qaz"},
	}

	for _, data := range dataProvider {
		b := NewPlain(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.Metric())
	}
}

func BenchmarkNewPlain(b *testing.B) {
	operation := NewMetricOperation("bar", "baz", "qaz")
	for n := 0; n < b.N; n++ {
		NewPlain("foo", operation, true, false)
	}
}

func BenchmarkNewPlain_unicode(b *testing.B) {
	operation := NewMetricOperation("bar", "baz", "qaz")
	for n := 0; n < b.N; n++ {
		NewPlain("foo", operation, true, true)
	}
}

func BenchmarkPlain_Metric(b *testing.B) {
	bucket := NewPlain("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.Metric()
	}
}

func BenchmarkPlain_MetricWithSuffix(b *testing.B) {
	bucket := NewPlain("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.MetricWithSuffix()
	}
}

func BenchmarkPlain_MetricTotal(b *testing.B) {
	bucket := NewPlain("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.MetricTotal()
	}
}

func BenchmarkPlain_MetricTotalWithSuffix(b *testing.B) {
	bucket := NewPlain("foo", NewMetricOperation("bar", "baz", "qaz"), true, false)
	for n := 0; n < b.N; n++ {
		bucket.MetricTotalWithSuffix()
	}
}

func TestPlain_MetricWithSuffix(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "foo-ok.bar.baz.qaz"},
		{"foo", NewMetricOperation("bar", "baz", "qaz"), false, "foo-fail.bar.baz.qaz"},
	}

	for _, data := range dataProvider {
		b := NewPlain(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.MetricWithSuffix())
	}
}

func TestPlain_MetricTotal(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "total.foo"},
		{"foo", NewMetricOperation("bar", "baz", "qaz"), false, "total.foo"},
	}

	for _, data := range dataProvider {
		b := NewPlain(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.MetricTotal())
	}
}

func TestPlain_MetricTotalWithSuffix(t *testing.T) {
	dataProvider := []struct {
		Section   string
		Operation *MetricOperation
		Success   bool
		Metric    string
	}{
		{"foo", NewMetricOperation("bar", "baz", "qaz"), true, "total.foo-ok"},
		{"foo", NewMetricOperation("bar", "baz", "qaz"), false, "total.foo-fail"},
	}

	for _, data := range dataProvider {
		b := NewPlain(data.Section, data.Operation, data.Success, true)
		assert.Equal(t, data.Metric, b.MetricTotalWithSuffix())
	}
}


<|start_filename|>incrementer/log.go<|end_filename|>
package incrementer

import (
	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/log"
)

// Log struct is Incrementer interface implementation that writes all metrics to log
type Log struct{}

// Increment writes given metric to log
func (i *Log) Increment(metric string, labels ...map[string]string) {
	log.Log("Stats counter incremented", map[string]interface{}{
		"metric": metric,
	}, nil)
}

// IncrementN writes given metric to log
func (i *Log) IncrementN(metric string, n int, labels ...map[string]string) {
	log.Log("Stats counter incremented by n", map[string]interface{}{
		"metric": metric,
		"n":      n,
	}, nil)
}

// IncrementAll writes all metrics for given bucket to log
func (i *Log) IncrementAll(b bucket.Bucket) {
	incrementAll(i, b)
}

// IncrementAllN writes all metrics for given bucket to log
func (i *Log) IncrementAllN(b bucket.Bucket, n int) {
	incrementAllN(i, b, n)
}


<|start_filename|>bucket/bucket_plain.go<|end_filename|>
package bucket

import (
	"strings"
	"sync"
)

// MetricOperation is a list of metric operations to use for metric
type MetricOperation struct {
	sync.Mutex

	operations []string
	Labels     map[string]string
}

// NewMetricOperation  builds and returns new MetricOperation instance with defined label keys
func NewMetricOperation(operations ...string) *MetricOperation {
	ops := []string{MetricEmptyPlaceholder, MetricEmptyPlaceholder, MetricEmptyPlaceholder}

	opsLen := len(operations)
	switch {
	case opsLen >= MetricOperationsMaxLength:
		ops[0] = operations[0]
		ops[1] = operations[1]
		ops[2] = operations[2]
	case opsLen < 3:
		for i := 0; i < opsLen; i++ {
			ops[i] = operations[i]
		}
	}
	return &MetricOperation{operations: ops}
}

// WithLabels adds label value to existing MetricOperation instance
func (m *MetricOperation) WithLabels(labels map[string]string) *MetricOperation {

	if m.Labels == nil {
		m.Labels = labels
		return m
	}

	for k := range m.Labels {
		m.Labels[k] = ""
	}

	m.Lock()
	defer m.Unlock()

	for k := range labels {
		if _, ok := m.Labels[k]; !ok {
			// TODO: handle error properly
		} else {
			if _, ok := labels[k]; ok {
				m.Labels[k] = labels[k]
			}
		}
	}

	return m
}

// Plain struct in an implementation of Bucket interface that produces metric names for given section and operation
type Plain struct {
	section   string
	operation string
	success   bool
}

// NewPlain builds and returns new Plain instance
func NewPlain(section string, operation *MetricOperation, success, uniDecode bool) *Plain {
	operationSanitized := make([]string, cap(operation.operations))
	for k, v := range operation.operations {
		operationSanitized[k] = SanitizeMetricName(v, uniDecode)
	}
	return &Plain{SanitizeMetricName(section, uniDecode), strings.Join(operationSanitized, "."), success}
}

// Metric builds simple metric name in the form:
//  <section>.<operation-0>.<operation-1>.<operation-2>
func (b *Plain) Metric() string {
	return b.section + "." + b.operation
}

// MetricWithSuffix builds metric name with success suffix in the form:
//  <section>-ok|fail.<operation-0>.<operation-1>.<operation-2>
func (b *Plain) MetricWithSuffix() string {
	return b.section + "-" + operationsStatus[b.success] + "." + b.operation
}

// MetricTotal builds simple total metric name in the form:
//  total.<section>
func (b *Plain) MetricTotal() string {
	return totalBucket + "." + b.section
}

// MetricTotalWithSuffix builds total metric name with success suffix in the form
//  total-ok|fail.<section>
func (b *Plain) MetricTotalWithSuffix() string {
	return totalBucket + "." + b.section + "-" + operationsStatus[b.success]
}


<|start_filename|>timer/timer.go<|end_filename|>
package timer

import "time"

// Timer is a metric time tracking interface
type Timer interface {
	// Start starts timer
	Start() Timer
	// StartAt starts timer at a given time
	StartAt(time.Time) Timer
	// Finish returns elapsed time
	Finish() time.Duration
}


<|start_filename|>client/memory_test.go<|end_filename|>
package client

import (
	"net/http"
	"net/url"
	"reflect"
	"testing"

	"github.com/hellofresh/stats-go/bucket"
	"github.com/hellofresh/stats-go/timer"
	"github.com/stretchr/testify/assert"
)

func TestMemoryClient_BuildTimeTracker(t *testing.T) {
	client := NewMemory(true)
	tt := client.BuildTimer()
	_, ok := tt.(*timer.Memory)
	assert.True(t, ok)
}

func TestMemoryClient_TrackRequest(t *testing.T) {
	client := NewMemory(true)

	tt := client.BuildTimer()
	r := &http.Request{Method: http.MethodGet, URL: &url.URL{Path: "/hello/memory/test"}}
	success := true
	b := bucket.NewHTTPRequest(client.httpRequestSection, r, success, client.httpMetricCallback, false)

	client.TrackRequest(r, tt, success)

	assert.Equal(t, 1, len(client.TimerMetrics))
	assert.Equal(t, 4, len(client.CountMetrics))

	assert.Equal(t, b.Metric(), client.TimerMetrics[0].Bucket)
	assert.Equal(t, 1, client.CountMetrics[b.Metric()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricWithSuffix()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricTotal()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricTotalWithSuffix()])

	client.Close()

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 0, len(client.CountMetrics))
}

func TestMemoryClient_TrackOperation(t *testing.T) {
	client := NewMemory(true)

	tt := client.BuildTimer()
	section := "test-section"
	operation := bucket.NewMetricOperation("01", "02", "o3")
	success := true
	b := bucket.NewPlain(section, operation, success, true)

	client.TrackOperation(section, operation, tt, success)

	assert.Equal(t, 1, len(client.TimerMetrics))
	assert.Equal(t, 4, len(client.CountMetrics))

	assert.Equal(t, b.MetricWithSuffix(), client.TimerMetrics[0].Bucket)
	assert.Equal(t, 1, client.CountMetrics[b.Metric()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricWithSuffix()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricTotal()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricTotalWithSuffix()])

	client.Close()

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 0, len(client.CountMetrics))
}

func TestMemoryClient_TrackOperationN(t *testing.T) {
	client := NewMemory(true)

	tt := client.BuildTimer()
	section := "test-section"
	operation := bucket.NewMetricOperation("01", "02", "o3")
	success := true
	n := 5
	b := bucket.NewPlain(section, operation, success, true)

	client.TrackOperationN(section, operation, tt, n, success)

	assert.Equal(t, 1, len(client.TimerMetrics))
	assert.Equal(t, 4, len(client.CountMetrics))

	assert.Equal(t, b.MetricWithSuffix(), client.TimerMetrics[0].Bucket)
	assert.Equal(t, n, client.CountMetrics[b.Metric()])
	assert.Equal(t, n, client.CountMetrics[b.MetricWithSuffix()])
	assert.Equal(t, n, client.CountMetrics[b.MetricTotal()])
	assert.Equal(t, n, client.CountMetrics[b.MetricTotalWithSuffix()])

	client.Close()

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 0, len(client.CountMetrics))
}

func TestMemoryClient_TrackMetric(t *testing.T) {
	client := NewMemory(true)

	section := "test-section"
	operation := bucket.NewMetricOperation("01", "02", "o3")
	b := bucket.NewPlain(section, operation, true, true)

	client.TrackMetric(section, operation)

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 2, len(client.CountMetrics))

	assert.Equal(t, 1, client.CountMetrics[b.Metric()])
	assert.Equal(t, 0, client.CountMetrics[b.MetricWithSuffix()])
	assert.Equal(t, 1, client.CountMetrics[b.MetricTotal()])
	assert.Equal(t, 0, client.CountMetrics[b.MetricTotalWithSuffix()])

	client.Close()

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 0, len(client.CountMetrics))
}

func TestMemoryClient_TrackMetricN(t *testing.T) {
	client := NewMemory(true)

	section := "test-section"
	operation := bucket.NewMetricOperation("01", "02", "o3")
	n := 5
	b := bucket.NewPlain(section, operation, true, true)

	client.TrackMetricN(section, operation, n)

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 2, len(client.CountMetrics))

	assert.Equal(t, n, client.CountMetrics[b.Metric()])
	assert.Equal(t, 0, client.CountMetrics[b.MetricWithSuffix()])
	assert.Equal(t, n, client.CountMetrics[b.MetricTotal()])
	assert.Equal(t, 0, client.CountMetrics[b.MetricTotalWithSuffix()])

	client.Close()

	assert.Equal(t, 0, len(client.TimerMetrics))
	assert.Equal(t, 0, len(client.CountMetrics))
}

func TestMemoryClient_TrackState(t *testing.T) {
	client := NewMemory(true)

	section := "test-section"
	operation1 := bucket.NewMetricOperation("01", "02", "o3")
	operation2 := bucket.NewMetricOperation("p1", "p2", "p3")
	state1 := 13
	state2 := 66
	state12 := 77

	client.TrackState(section, operation1, state1)
	client.TrackState(section, operation2, state2)

	assert.Equal(t, 2, len(client.StateMetrics))
	assert.Equal(t, state1, client.StateMetrics[bucket.NewPlain(section, operation1, true, true).Metric()])
	assert.Equal(t, state2, client.StateMetrics[bucket.NewPlain(section, operation2, true, true).Metric()])

	client.TrackState(section, operation1, state12)
	assert.Equal(t, 2, len(client.StateMetrics))
	assert.Equal(t, state12, client.StateMetrics[bucket.NewPlain(section, operation1, true, true).Metric()])
	assert.Equal(t, state2, client.StateMetrics[bucket.NewPlain(section, operation2, true, true).Metric()])
}

func TestMemoryClient_SetHTTPMetricCallback(t *testing.T) {
	client := NewMemory(true)
	callback := func(metricParts *bucket.MetricOperation, r *http.Request) *bucket.MetricOperation {
		return metricParts
	}

	client.SetHTTPMetricCallback(callback)
	// asserting functions directly gives false result:
	// Not equal: (func(bucket.MetricOperation, *http.Request) bucket.MetricOperation)(0x1255160) (expected)
	//     != (bucket.HTTPMetricNameAlterCallback)(0x1255160) (actual)
	// so we assert objects pointers directly to make sure this is the same function object
	assert.Equal(t, reflect.ValueOf(callback).Pointer(), reflect.ValueOf(client.GetHTTPMetricCallback()).Pointer())
}

func TestMemoryClient_SetHTTPRequestSection(t *testing.T) {
	client := NewMemory(true)

	assert.Equal(t, bucket.SectionRequest, client.httpRequestSection)

	section := "test-section"
	client.SetHTTPRequestSection(section)
	assert.Equal(t, section, client.httpRequestSection)

	client.ResetHTTPRequestSection()
	assert.Equal(t, bucket.SectionRequest, client.httpRequestSection)
}


<|start_filename|>context/context_test.go<|end_filename|>
package context

import (
	"context"
	"testing"

	"github.com/hellofresh/stats-go/client"
	"github.com/stretchr/testify/require"
)

func TestContext(t *testing.T) {
	t.Parallel()

	tests := []struct {
		scenario string
		function func(*testing.T)
	}{
		{
			scenario: "when set the context",
			function: testSetContext,
		},
		{
			scenario: "when the client is set on context",
			function: testGetFromContextSuccess,
		},
		{
			scenario: "when the client is not set on context",
			function: testGetFromContextFail,
		},
	}

	for _, test := range tests {
		t.Run(test.scenario, func(t *testing.T) {
			test.function(t)
		})
	}
}

func testSetContext(t *testing.T) {
	statsClient := client.NewNoop()

	ctx := context.Background()
	ctx = New(ctx, statsClient)

	client := WithContext(ctx)
	require.NotNil(t, client)
}

func testGetFromContextSuccess(t *testing.T) {
	statsClient := client.NewNoop()

	ctx := context.Background()
	ctx = New(ctx, statsClient)

	client := WithContext(ctx)
	require.NotNil(t, client)
}

func testGetFromContextFail(t *testing.T) {
	client := WithContext(context.Background())
	require.NotNil(t, client)
}


<|start_filename|>bucket/bucket_prometheus.go<|end_filename|>
package bucket

import (
	"strings"

	"github.com/rainycape/unidecode"
)

// Prometheus struct in an implementation of Bucket interface that produces metric names with labels for given section and operation
type Prometheus struct {
	section   string
	operation string
	success   bool
}

// NewPrometheus builds and returns new Prometheus instance
func NewPrometheus(section string, operation *MetricOperation, success, uniDecode bool) *Prometheus {
	operationSanitized := make([]string, 0, len(operation.operations))
	for _, v := range operation.operations {
		sanitizedMetricName := sanitizeMetricName(v, uniDecode)
		// prometheus doesn't allow _ in then end of metric name
		if sanitizedMetricName != "" {
			operationSanitized = append(operationSanitized, sanitizedMetricName)
		}
	}

	return &Prometheus{
		section:   sanitizeMetricName(section, uniDecode),
		operation: strings.Join(operationSanitized, "_"),
		success:   success,
	}
}

// Metric builds simple metric name in the form:
//  <section>_<operation-0>_<operation-1>_<operation-2>
func (b *Prometheus) Metric() string {
	return b.section + "_" + b.operation
}

// MetricWithSuffix builds metric name with success suffix in the form:
//  <section>-ok|fail.<operation-0>.<operation-1>.<operation-2>
func (b *Prometheus) MetricWithSuffix() string {
	return b.section + "-" + operationsStatus[b.success] + "." + b.operation
}

// MetricTotal builds simple total metric name in the form:
//  total.<section>
func (b *Prometheus) MetricTotal() string {
	return totalBucket + "_" + b.section
}

// MetricTotalWithSuffix builds total metric name with success suffix in the form
//  total-ok|fail_<section>
func (b *Prometheus) MetricTotalWithSuffix() string {
	return totalBucket + "_" + b.section + "-" + operationsStatus[b.success]
}

func sanitizeMetricName(metric string, uniDecode bool) string {
	if metric == "" {
		return ""
	}

	if uniDecode {
		asciiMetric := unidecode.Unidecode(metric)
		if asciiMetric != metric {
			metric = prefixUnicode + asciiMetric
		}
	}

	metric = strings.Replace(metric, "-", "", -1)

	return strings.Replace(
		// Remove underscores
		strings.Replace(metric, "_", "", -1),
		// and replace dots with single underscore
		".",
		"_",
		-1,
	)
}


<|start_filename|>state/prometheus.go<|end_filename|>
package state

import (
	"sync"

	"github.com/prometheus/client_golang/prometheus"
)

// Prometheus struct is State interface implementation that writes all states
type Prometheus struct {
	sync.Mutex

	gauge        GaugeVec
	gaugeFactory GaugeFactory
}

// GaugeVec interface for gauge vectors in prometheus backend
type GaugeVec interface {
	GetMetricWithLabelValues(lvs ...string) (prometheus.Gauge, error)
	GetMetricWith(labels prometheus.Labels) (prometheus.Gauge, error)
	WithLabelValues(lvs ...string) prometheus.Gauge
	With(labels prometheus.Labels) prometheus.Gauge
}

// GaugeFactory interface for making new GaugeVec instances
type GaugeFactory interface {
	Create(metric string, labelKeys []string) GaugeVec
}

// PrometheusGaugeFactory implements GaugeFactory interface
type PrometheusGaugeFactory struct{}

// NewPrometheusGaugeFactory returns new PrometheusGaugeFactory instance
func NewPrometheusGaugeFactory() *PrometheusGaugeFactory {
	return &PrometheusGaugeFactory{}
}

// Create method returns new GaugeVec instance with metric and labelKeys attributes
func (f *PrometheusGaugeFactory) Create(metric string, labelKeys []string) GaugeVec {
	p := prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: metric,
			Help: " ",
		},
		labelKeys,
	)
	prometheus.Register(p)

	return p
}

// PrometheusStateFactory implements Factory interface
type PrometheusStateFactory struct{}

// NewPrometheusStateFactory returns new NewPrometheusIncrementerFactory instance
func NewPrometheusStateFactory() *PrometheusStateFactory {
	return &PrometheusStateFactory{}
}

// Create method returns new Prometheus incrementer instance
func (p *PrometheusStateFactory) Create() State {
	return NewPrometheus(NewPrometheusGaugeFactory())
}

// NewPrometheus creates new prometheus state instance
func NewPrometheus(gaugeFactory GaugeFactory) *Prometheus {
	return &Prometheus{gauge: nil, gaugeFactory: gaugeFactory}
}

// Set sets metric state
func (s *Prometheus) Set(metric string, n int, labels ...map[string]string) {
	var labelNames []string
	var labelValues []string

	if labels != nil {
		for k, v := range labels[0] {
			labelNames = append(labelNames, k)
			labelValues = append(labelValues, v)
		}
	}

	s.Lock()
	defer s.Unlock()

	if s.gauge == nil {
		s.gauge = s.gaugeFactory.Create(metric, labelNames)
	}

	s.gauge.WithLabelValues(labelValues...).Set(float64(n))
}


<|start_filename|>state/prometheus_test.go<|end_filename|>
package state

import (
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
)

type GaugeMock struct {
	prometheus.Metric
	prometheus.Collector
	setCalled      bool
	setCalledValue float64
}

func (c *GaugeMock) Set(n float64) {
	c.setCalled = true
	c.setCalledValue = n
}

func (c *GaugeMock) Inc() {}

func (c *GaugeMock) Dec() {}

func (c *GaugeMock) Add(float64) {}

func (c *GaugeMock) Sub(float64) {}

func (c *GaugeMock) SetToCurrentTime() {}

type GaugeVecMock struct {
	withLabelValuesCalls int
	values               []string
	gaugeMock            GaugeMock
}

func (m *GaugeVecMock) GetMetricWithLabelValues(lvs ...string) (prometheus.Gauge, error) {
	return nil, nil
}

func (m *GaugeVecMock) GetMetricWith(labels prometheus.Labels) (prometheus.Gauge, error) {
	return nil, nil
}

func (m *GaugeVecMock) WithLabelValues(lvs ...string) prometheus.Gauge {
	m.withLabelValuesCalls++
	m.values = lvs
	return &m.gaugeMock
}

func (m *GaugeVecMock) With(labels prometheus.Labels) prometheus.Gauge {
	return nil
}

type GaugeFactoryMock struct {
	mock GaugeVecMock
}

func (m *GaugeFactoryMock) Create(metric string, labelKeys []string) GaugeVec {
	m.mock = GaugeVecMock{values: labelKeys, gaugeMock: GaugeMock{}}
	return &m.mock
}

func TestPrometheus_Set(t *testing.T) {
	metric1 := "metric1"
	metricState1 := 10

	m := &GaugeFactoryMock{}
	s := NewPrometheus(m)

	s.Set(metric1, metricState1)
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 0, len(m.mock.values))
	assert.Equal(t, true, m.mock.gaugeMock.setCalled)
	assert.Equal(t, float64(10), m.mock.gaugeMock.setCalledValue)
}

func TestPrometheus_SetWithLabels(t *testing.T) {
	metric1 := "metric1"
	metricState1 := 10

	m := &GaugeFactoryMock{}
	s := NewPrometheus(m)

	s.Set(metric1, metricState1, map[string]string{"key1": "value1"})
	assert.Equal(t, 1, m.mock.withLabelValuesCalls)
	assert.Equal(t, 1, len(m.mock.values))
	assert.Equal(t, true, m.mock.gaugeMock.setCalled)
	assert.Equal(t, float64(10), m.mock.gaugeMock.setCalledValue)
}


<|start_filename|>incrementer/incrementer.go<|end_filename|>
package incrementer

import (
	"github.com/hellofresh/stats-go/bucket"
)

// Incrementer is a metric incrementer interface
type Incrementer interface {
	// Increment increments metric
	Increment(metric string, labels ...map[string]string)

	// IncrementN increments metric by n
	IncrementN(metric string, n int, labels ...map[string]string)

	// Increment increments all metrics for given bucket
	IncrementAll(b bucket.Bucket)

	// Increment increments all metrics for given bucket by n
	IncrementAllN(b bucket.Bucket, n int)
}

func incrementAll(i Incrementer, b bucket.Bucket) {
	i.Increment(b.Metric())
	i.Increment(b.MetricWithSuffix())
	i.Increment(b.MetricTotal())
	i.Increment(b.MetricTotalWithSuffix())
}

func incrementAllN(i Incrementer, b bucket.Bucket, n int) {
	i.IncrementN(b.Metric(), n)
	i.IncrementN(b.MetricWithSuffix(), n)
	i.IncrementN(b.MetricTotal(), n)
	i.IncrementN(b.MetricTotalWithSuffix(), n)
}


<|start_filename|>middleware/middleware.go<|end_filename|>
package middleware

import (
	"net/http"

	"github.com/felixge/httpsnoop"
	"github.com/hellofresh/stats-go/client"
	"github.com/hellofresh/stats-go/context"
	"github.com/hellofresh/stats-go/timer"
)

// New creates a new stats middleware
func New(s client.Client) func(http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			r = r.WithContext(context.New(r.Context(), s))

			mt := httpsnoop.CaptureMetrics(handler, w, r)
			t := timer.NewDuration(mt.Duration)

			success := mt.Code < http.StatusBadRequest
			s.TrackRequest(r, t, success)
		})
	}
}


<|start_filename|>state/factory.go<|end_filename|>
package state

// Factory interface for making new state instances
type Factory interface {
	Create() State
}


<|start_filename|>state/statsd.go<|end_filename|>
package state

import "gopkg.in/alexcesaro/statsd.v2"

// StatsD struct is State interface implementation that writes all states to statsd gauge
type StatsD struct {
	c *statsd.Client
}

// NewStatsD creates new statsd state instance
func NewStatsD(c *statsd.Client) *StatsD {
	return &StatsD{c: c}
}

// Set sets metric state
func (s *StatsD) Set(metric string, n int, labels ...map[string]string) {
	s.c.Gauge(metric, n)
}


<|start_filename|>timer/memory_test.go<|end_filename|>
package timer

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestMemoryTimeTracker(t *testing.T) {
	tt := &Memory{}
	tt.Start()
	d := tt.Finish()

	assert.True(t, d > time.Duration(0))
}
