stats

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2017 License: MIT Imports: 11 Imported by: 24

README

hellofresh/stats-go

Build Status Coverage Status GoDoc Go Report Card

Generic Stats library written in Go

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.

Key Features

  • Several stats backends:
    • log for development environment
    • statsd for production (with fallback to log if statsd server is not available)
    • memory for testing purpose, to track stats operations in unit tests
    • noop for environments that do not require any stats gathering
  • Fixed metric sections count for all metrics to allow easy monitoring/alerting setup in grafana
  • Easy to build HTTP requests metrics - timing and count
  • Generalise or modify HTTP Requests metric - e.g. skip ID part

Installation

go get -u github.com/hellofresh/stats-go

Usage

Instance creation
package main

import (
        "os"

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

func main() {
        // client that tries to connect to statsd service, fallback to debug log backend if fails to connect
        statsdClient, _ := stats.NewClient("statsd://statsd-host:8125", "my.app.prefix")
        defer statsdClient.Close()

        // debug log backend for stats
        logClient, _ := stats.NewClient("log://", "")
        defer logClient.Close()

        // memory backend to track operations in unit tests
        memoryClient, _ := stats.NewClient("memory://", "")
        defer memoryClient.Close()

        // noop backend to ignore all stats
        noopClient, _ := stats.NewClient("noop://", "")
        defer noopClient.Close()

        // client that tries to connect to statsd service, fallback to debug log backend if fails to connect
        // format for backward compatibility with previous version
        legacyStatsdClient, _ := stats.NewClient("statsd-host:8125", "my.app.prefix")
        defer legacyStatsdClient.Close()

        // debug log backend for stats
        // format for backward compatibility with previous version
        legacyLogClient, _ := stats.NewClient("", "")
        defer legacyLogClient.Close()

        // get settings from env to determine backend and prefix
        statsClient, _ := stats.NewClient(os.Getenv("STATS_DSN"), os.Getenv("STATS_PREFIX"))
        defer statsClient.Close()
}
Count metrics manually
timing := statsClient.BuildTimeTracker().Start()
operations := statsClient.MetricOperation{"orders", "order", "create"}
err := orderService.Create(...)
statsClient.TrackOperation("ordering", operations, timing, err == nil)

ordersInLast24h := orderService.Count(time.Duration(24)*time.Hour)
statsClient.TrackState("ordering", operations, ordersInLast24h)
Track requests metrics with middleware, e.g. for Gin Web Framework
package middleware

import (
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/gin"
	stats "github.com/hellofresh/stats-go"
)

// NewStatsRequest returns a middleware handler function.
func NewStatsRequest(sc stats.Client) gin.HandlerFunc {
	return func(c *gin.Context) {
		log.WithField("path", c.Request.URL.Path).Debug("Starting Stats middleware")

		timing := sc.BuildTimer().Start()

		c.Next()

		success := c.Writer.Status() < http.StatusBadRequest
		log.WithFields(log.Fields{"request_url": c.Request.URL.Path}).Debug("Track request stats")
		sc.TrackRequest(c.Request, timing, success)
	}
}
package main

import (
        "net/http"
        "os"

        "github.com/example/app/middleware"
        "github.com/gin-gonic/gin"
        stats "github.com/hellofresh/stats-go"
)

func main() {
        statsClient := stats.NewStatsdClient(os.Getenv("STATS_DSN"), os.Getenv("STATS_PREFIX"))
        defer statsClient.Close()

        router := gin.Default()
        router.Use(middleware.NewStatsRequest(statsClient))

        router.GET("/", func(c *gin.Context) {
                // will produce "<prefix>.get.-.-" metric
                c.JSON(http.StatusOK, "I'm producing stats!")
        })

        router.Run(":8080")
}
Usage in unit tests
package foo

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

const sectionStatsFoo = "foo"

func DoSomeJob(statsClient stats.Client) error {
        tt := statsClient.BuildTimer().Start()
        operation := bucket.MetricOperation{"do", "some", "job"}

        result, err := doSomeRealJobHere()
        statsClient.TrackOperation(sectionStatsFoo, operation, tt, result)

        return err
}
package foo

import (
        "testing"

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

func TestDoSomeJob(t *testing.T) {
        statsClient, _ := stats.NewClient("memory://", "") 
        
        err := DoSomeJob(statsClient)
        assert.Nil(t, err)
        
        statsMemory, _ := statsClient.(stats.MemoryClient)
        assert.Equal(t, 1, len(statsMemory.TimerMetrics))
        assert.Equal(t, "foo-ok.do.some.job", statsMemory.TimerMetrics[0].Bucket)
        assert.Equal(t, 1, statsMemory.CountMetrics["foo-ok.do.some.job"])
}
Generalise resources by type and stripping resource ID

In some cases you do not need to collect metrics for all unique requests, but a single metric for requests of the similar type, e.g. access time to concrete users pages does not matter a lot, but average access time is important. hellofresh/stats-go allows HTTP Request metric modification and supports ID filtering out of the box, so you can get generic metric get.users.-id- instead thousands of metrics like get.users.1, get.users.13, get.users.42 etc. that may make your graphite suffer from overloading.

To use metric generalisation by second level path ID, you can pass stats.HttpMetricNameAlterCallback instance to stats.Client.SetHttpMetricCallback(). Also there is a shortcut function stats.NewHasIDAtSecondLevelCallback() that generates a callback handler for stats.SectionsTestsMap, and shortcut function stats.ParseSectionsTestsMap, that generates sections test map from string, so you can get these values from config. It accepts a list of sections with test callback in the following format: <section>:<test-callback-name>. You can use either double colon or new line character as section-callback pairs separator, so all of the following forms are correct:

  • <section-0>:<test-callback-name-0>:<section-1>:<test-callback-name-1>:<section-2>:<test-callback-name-2>
  • <section-0>:<test-callback-name-0>\n<section-1>:<test-callback-name-1>\n<section-2>:<test-callback-name-2>
  • <section-0>:<test-callback-name-0>:<section-1>:<test-callback-name-1>\n<section-2>:<test-callback-name-2>

Currently the following test callbacks are implemented:

  • true - second path level is always treated as ID, e.g. /users/13 -> users.-id-, /users/search -> users.-id-, /users -> users.-id-
  • numeric - only numeric second path level is interpreted as ID, e.g. /users/13 -> users.-id-, /users/search -> users.search
  • not_empty - only not empty second path level is interpreted as ID, e.g. /users/13 -> users.-id-, /users -> users.-

You can register your own test callback functions using the stats.RegisterSectionTest() function before parsing sections map from string.

package main

import (
        "net/http"
        "os"

        "github.com/example/app/middleware"
        "github.com/gin-gonic/gin"
        stats "github.com/hellofresh/stats-go"
        "github.com/hellofresh/stats-go/bucket"
)

func main() {
        // STATS_IDS=users:not_empty:clients:numeric
        sectionsTestsMap, err := bucket.ParseSectionsTestsMap(os.Getenv("STATS_IDS"))
        if err != nil {
                sectionsTestsMap = map[bucket.PathSection]bucket.SectionTestDefinition{}
        }
        statsClient, _ := stats.NewClient(os.Getenv("STATS_DSN"), os.Getenv("STATS_PREFIX"))
        statsClient.SetHTTPMetricCallback(bucket.NewHasIDAtSecondLevelCallback(sectionsTestsMap))
        defer statsClient.Close()

        router := gin.Default()
        router.Use(middleware.NewStatsRequest(statsClient))

        router.GET("/users", func(c *gin.Context) {
                // will produce "<prefix>.get.users.-" metric
                c.JSON(http.StatusOK, "Get the userslist")
        })
        router.GET("/users/:id", func(c *gin.Context) {
                // will produce "<prefix>.get.users.-id-" metric 
                c.JSON(http.StatusOK, "Get the user ID " + c.Params.ByName("id"))
        })
        router.GET("/clients/:id", func(c *gin.Context) {
                // will produce "<prefix>.get.clients.-id-" metric
                c.JSON(http.StatusOK, "Get the client ID " + c.Params.ByName("id"))
        })

        router.Run(":8080")
}

Contributing

To start contributing, please check CONTRIBUTING.

Documentation

Documentation

Index

Constants

View Source
const (
	// StatsD is a dsn scheme value for statsd client
	StatsD = "statsd"
	// 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"
)

Variables

View Source
var ErrUnknownClient = errors.New("Unknown stats client type")

ErrUnknownClient is an error returned when trying to create stats client of unknown type

Functions

This section is empty.

Types

type Client added in v0.2.0

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

	// 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
}

Client is an interface for different methods of gathering stats

func NewClient added in v0.2.0

func NewClient(dsn, prefix string) (Client, error)

NewClient creates and builds new stats client instance by given dsn

type LogClient added in v0.2.0

type LogClient struct {
	*StatsdClient
}

LogClient is Client implementation for debug log

func NewLogClient added in v0.2.0

func NewLogClient() *LogClient

NewLogClient builds and returns new LogClient instance

type MemoryClient added in v0.2.0

type MemoryClient struct {
	sync.Mutex

	TimerMetrics []timer.Metric
	CountMetrics map[string]int
	StateMetrics map[string]int
	// contains filtered or unexported fields
}

MemoryClient is Client implementation for tests

func NewMemoryClient added in v0.2.0

func NewMemoryClient() *MemoryClient

NewMemoryClient builds and returns new MemoryClient instance

func (*MemoryClient) BuildTimer added in v0.3.0

func (c *MemoryClient) BuildTimer() timer.Timer

BuildTimer builds timer to track metric timings

func (*MemoryClient) Close added in v0.2.0

func (c *MemoryClient) Close() error

Close resets all collected stats

func (*MemoryClient) GetHTTPMetricCallback added in v0.3.1

func (c *MemoryClient) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback

GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request

func (*MemoryClient) ResetHTTPRequestSection added in v0.2.0

func (c *MemoryClient) ResetHTTPRequestSection() Client

ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"

func (*MemoryClient) SetHTTPMetricCallback added in v0.2.0

func (c *MemoryClient) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client

SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request

func (*MemoryClient) SetHTTPRequestSection added in v0.2.0

func (c *MemoryClient) SetHTTPRequestSection(section string) Client

SetHTTPRequestSection sets metric section for HTTP Request metrics

func (*MemoryClient) TrackOperation added in v0.2.0

func (c *MemoryClient) TrackOperation(section string, operation bucket.MetricOperation, t timer.Timer, success bool) Client

TrackOperation tracks custom operation

func (*MemoryClient) TrackOperationN added in v0.2.0

func (c *MemoryClient) TrackOperationN(section string, operation bucket.MetricOperation, t timer.Timer, n int, success bool) Client

TrackOperationN tracks custom operation with n diff

func (*MemoryClient) TrackRequest added in v0.2.0

func (c *MemoryClient) TrackRequest(r *http.Request, t timer.Timer, success bool) Client

TrackRequest tracks HTTP Request stats

func (*MemoryClient) TrackState added in v0.3.0

func (c *MemoryClient) TrackState(section string, operation bucket.MetricOperation, value int) Client

TrackState tracks metric absolute value

type NoopClient added in v0.2.1

type NoopClient struct {
	sync.Mutex
	// contains filtered or unexported fields
}

NoopClient is Client implementation that does literally nothing

func NewNoopClient added in v0.2.1

func NewNoopClient() *NoopClient

NewNoopClient builds and returns new NoopClient instance

func (*NoopClient) BuildTimer added in v0.3.0

func (c *NoopClient) BuildTimer() timer.Timer

BuildTimer builds timer to track metric timings

func (*NoopClient) Close added in v0.2.1

func (c *NoopClient) Close() error

Close closes underlying client connection if any

func (*NoopClient) GetHTTPMetricCallback added in v0.3.1

func (c *NoopClient) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback

GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request

func (*NoopClient) ResetHTTPRequestSection added in v0.2.1

func (c *NoopClient) ResetHTTPRequestSection() Client

ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"

func (*NoopClient) SetHTTPMetricCallback added in v0.2.1

func (c *NoopClient) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client

SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request

func (*NoopClient) SetHTTPRequestSection added in v0.2.1

func (c *NoopClient) SetHTTPRequestSection(section string) Client

SetHTTPRequestSection sets metric section for HTTP Request metrics

func (*NoopClient) TrackOperation added in v0.2.1

func (c *NoopClient) TrackOperation(section string, operation bucket.MetricOperation, t timer.Timer, success bool) Client

TrackOperation tracks custom operation

func (*NoopClient) TrackOperationN added in v0.2.1

func (c *NoopClient) TrackOperationN(section string, operation bucket.MetricOperation, t timer.Timer, n int, success bool) Client

TrackOperationN tracks custom operation with n diff

func (*NoopClient) TrackRequest added in v0.2.1

func (c *NoopClient) TrackRequest(r *http.Request, t timer.Timer, success bool) Client

TrackRequest tracks HTTP Request stats

func (*NoopClient) TrackState added in v0.3.0

func (c *NoopClient) TrackState(section string, operation bucket.MetricOperation, value int) Client

TrackState tracks metric absolute value

type StatsdClient added in v0.2.0

type StatsdClient struct {
	sync.Mutex
	// contains filtered or unexported fields
}

StatsdClient is Client implementation for statsd

func NewStatsdClient added in v0.2.0

func NewStatsdClient(dsn, prefix string) *StatsdClient

NewStatsdClient builds and returns new StatsdClient instance

func (*StatsdClient) BuildTimer added in v0.3.0

func (c *StatsdClient) BuildTimer() timer.Timer

BuildTimer builds timer to track metric timings

func (*StatsdClient) Close added in v0.2.0

func (c *StatsdClient) Close() error

Close statsd connection

func (*StatsdClient) GetHTTPMetricCallback added in v0.3.1

func (c *StatsdClient) GetHTTPMetricCallback() bucket.HTTPMetricNameAlterCallback

GetHTTPMetricCallback gets callback handler that allows metric operation alteration for HTTP Request

func (*StatsdClient) ResetHTTPRequestSection added in v0.2.0

func (c *StatsdClient) ResetHTTPRequestSection() Client

ResetHTTPRequestSection resets metric section for HTTP Request metrics to default value that is "request"

func (*StatsdClient) SetHTTPMetricCallback added in v0.2.0

func (c *StatsdClient) SetHTTPMetricCallback(callback bucket.HTTPMetricNameAlterCallback) Client

SetHTTPMetricCallback sets callback handler that allows metric operation alteration for HTTP Request

func (*StatsdClient) SetHTTPRequestSection added in v0.2.0

func (c *StatsdClient) SetHTTPRequestSection(section string) Client

SetHTTPRequestSection sets metric section for HTTP Request metrics

func (*StatsdClient) TrackOperation added in v0.2.0

func (c *StatsdClient) TrackOperation(section string, operation bucket.MetricOperation, t timer.Timer, success bool) Client

TrackOperation tracks custom operation

func (*StatsdClient) TrackOperationN added in v0.2.0

func (c *StatsdClient) TrackOperationN(section string, operation bucket.MetricOperation, t timer.Timer, n int, success bool) Client

TrackOperationN tracks custom operation with n diff

func (*StatsdClient) TrackRequest added in v0.2.0

func (c *StatsdClient) TrackRequest(r *http.Request, t timer.Timer, success bool) Client

TrackRequest tracks HTTP Request stats

func (*StatsdClient) TrackState added in v0.3.0

func (c *StatsdClient) TrackState(section string, operation bucket.MetricOperation, value int) Client

TrackState tracks metric absolute value

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL