stats

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2017 License: MIT Imports: 9 Imported by: 24

README

hellofresh/stats-go

Build 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

  • Two stats backends - statsd for production and log for development environment
  • 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.NewStatsdStatsClient("statsd-host:8125", "my.app.prefix")
        // explicitly use debug log backend for stats
        mutedClient := stats.NewStatsdStatsClient("", "my.app.prefix")

        // get settings from env to determine backend and prefix
        statsClient := stats.NewStatsdStatsClient(os.Getenv("STATS_DSN"), os.Getenv("STATS_PREFIX"))
}
Count metrics manually
timing := statsClient.BuildTimeTracker().Start()
operations := statsClient.MetricOperation{"orders", "order", "create"}
err := orderService.Create(...)
statsClient.TrackOperation("ordering", operations, timing, err == nil)
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.StatsClient) gin.HandlerFunc {
	return func(c *gin.Context) {
		log.WithField("path", c.Request.URL.Path).Debug("Starting Stats middleware")

		timing := sc.BuildTimeTracker().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.NewStatsdStatsClient(os.Getenv("STATS_DSN"), os.Getenv("STATS_PREFIX"))

        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")
}
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.StatsClient.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"
)

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

        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 (

	// MetricEmptyPlaceholder is a string placeholder for empty (unset) sections of operation
	MetricEmptyPlaceholder = "-"

	// MetricIDPlaceholder is a string placeholder for ID section of operation if any
	MetricIDPlaceholder = "-id-"
)
View Source
const (

	// 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"
)

Variables

View Source
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")
)

Functions

func RegisterSectionTest

func RegisterSectionTest(name string, callback SectionTestCallback)

RegisterSectionTest registers new section test callback function with its name

func SanitizeMetricName

func SanitizeMetricName(metric string) string

SanitizeMetricName modifies metric name to work well with statsd

func TestAlwaysTrue

func TestAlwaysTrue(PathSection) bool

TestAlwaysTrue section test callback function that gives true result to any section

func TestIsNotEmpty

func TestIsNotEmpty(s PathSection) bool

TestAlwaysTrue section test callback function that gives true result if section is not empty placeholder ("-")

func TestIsNumeric

func TestIsNumeric(s PathSection) bool

TestAlwaysTrue section test callback function that gives true result if section is numeric

Types

type Bucket

type Bucket interface {
	// Metric builds simple metric name in the form "<section>.<operation-0>.<operation-1>.<operation-2>"
	Metric() string

	// MetricWithSuffix builds metric name with success suffix in the form "<section>-ok|fail.<operation-0>.<operation-1>.<operation-2>"
	MetricWithSuffix() string

	// MetricTotal builds simple total metric name in the form total.<section>"
	MetricTotal() string

	// MetricTotalWithSuffix builds total metric name with success suffix in the form total-ok|fail.<section>"
	MetricTotalWithSuffix() string
}

Bucket is an interface for building metric names for operations

type BucketHttpRequest

type BucketHttpRequest struct {
	*BucketPlain
	// contains filtered or unexported fields
}

BucketHttpRequest struct in an implementation of Bucket interface that produces metric names for HTTP Request. Metrics has the following formats for methods:

Metric() -> <section>.<method>.<path-level-0>.<path-level-1>
MetricWithSuffix() -> <section>-ok|fail.<method>.<path-level-0>.<path-level-1>
TotalRequests() -> total.<section>
MetricTotalWithSuffix() -> total-ok|fail.<section>

Normally "<section>" is set to "request", but you can use any string value here.

func NewBucketHttpRequest

func NewBucketHttpRequest(section string, r *http.Request, success bool, callback HttpMetricNameAlterCallback) *BucketHttpRequest

NewBucketHttpRequest builds and returns new BucketHttpRequest instance

type BucketPlain

type BucketPlain struct {
	// contains filtered or unexported fields
}

BucketPlain struct in an implementation of Bucket interface that produces metric names for given section and operation

func NewBucketPlain

func NewBucketPlain(section string, operation MetricOperation, success bool) *BucketPlain

NewBucketPlain builds and returns new BucketPlain instance

func (*BucketPlain) Metric

func (b *BucketPlain) Metric() string

Metric builds simple metric name in the form:

<section>.<operation-0>.<operation-1>.<operation-2>

func (*BucketPlain) MetricTotal

func (b *BucketPlain) MetricTotal() string

MetricTotal builds simple total metric name in the form:

total.<section>

func (*BucketPlain) MetricTotalWithSuffix

func (b *BucketPlain) MetricTotalWithSuffix() string

MetricTotalWithSuffix builds total metric name with success suffix in the form

total-ok|fail.<section>

func (*BucketPlain) MetricWithSuffix

func (b *BucketPlain) MetricWithSuffix() string

MetricWithSuffix builds metric name with success suffix in the form:

<section>-ok|fail.<operation-0>.<operation-1>.<operation-2>

type HttpMetricNameAlterCallback

type HttpMetricNameAlterCallback func(metricParts MetricOperation, r *http.Request) MetricOperation

HttpMetricNameAlterCallback is a type for HTTP Request metric alter handler

func NewHasIDAtSecondLevelCallback

func NewHasIDAtSecondLevelCallback(hasIDAtSecondLevel SectionsTestsMap) HttpMetricNameAlterCallback

NewHasIDAtSecondLevelCallback returns HttpMetricNameAlterCallback implementation that checks for IDs on the second level of HTTP Request path

type Incrementer

type Incrementer interface {
	// Increment increments metric
	Increment(metric string)

	// IncrementN increments metric by n
	IncrementN(metric string, n int)

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

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

Incrementer is a metric incrementer interface

func NewIncrementer

func NewIncrementer(c *statsd.Client, muted bool) Incrementer

NewIncrementer builds and returns new Incrementer instance

type LogIncrementer

type LogIncrementer struct{}

LogIncrementer struct is Incrementer interface implementation that writes all metrics to log

func (*LogIncrementer) Increment

func (i *LogIncrementer) Increment(metric string)

Increment writes given metric to log

func (*LogIncrementer) IncrementAll

func (i *LogIncrementer) IncrementAll(b Bucket)

IncrementAll writes all metrics for given bucket to log

func (*LogIncrementer) IncrementAllN

func (i *LogIncrementer) IncrementAllN(b Bucket, n int)

IncrementN writes all metrics for given bucket to log

func (*LogIncrementer) IncrementN

func (i *LogIncrementer) IncrementN(metric string, n int)

IncrementN writes given metric to log

type LogTimeTracker

type LogTimeTracker struct {
	// contains filtered or unexported fields
}

LogTimeTracker struct is TimeTracker interface implementation that writes all timings to log

func (*LogTimeTracker) Finish

func (t *LogTimeTracker) Finish(bucket string)

Finish writes elapsed time for metric to log

func (*LogTimeTracker) Start

func (t *LogTimeTracker) Start() TimeTracker

Start starts timer

type MetricOperation

type MetricOperation [3]string

MetricOperation is a list of metric operations to use for metric

type PathSection

type PathSection string

PathSection type represents single path section string

type SectionTestCallback

type SectionTestCallback func(PathSection) bool

SectionTestCallback type represents section test callback function

func GetSectionTestCallback

func GetSectionTestCallback(name string) SectionTestCallback

GetSectionTestCallback returns section test callback function by name

type SectionTestDefinition

type SectionTestDefinition struct {
	Name     string
	Callback SectionTestCallback
}

SectionTestDefinition type represents section test callback definition

type SectionsTestsMap

type SectionsTestsMap map[PathSection]SectionTestDefinition

SectionTestDefinition type represents section test callbacks definitions map

func ParseSectionsTestsMap

func ParseSectionsTestsMap(s string) (SectionsTestsMap, error)

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 (SectionsTestsMap) String

func (m SectionsTestsMap) String() string

String returns pretty formatted string representation of SectionsTestsMap

type StatsClient

type StatsClient interface {
	// BuildTimeTracker builds timer to track metric timings
	BuildTimeTracker() TimeTracker
	// Close closes underlying client connection if any
	Close()

	// TrackRequest tracks HTTP Request stats
	TrackRequest(r *http.Request, tt TimeTracker, success bool)

	// TrackOperation tracks custom operation
	TrackOperation(section string, operation MetricOperation, tt TimeTracker, success bool)
	// TrackOperation tracks custom operation with n diff
	TrackOperationN(section string, operation MetricOperation, tt TimeTracker, n int, success bool)

	// SetHttpMetricCallback sets callback handler that allows metric operation alteration for HTTP Request
	SetHttpMetricCallback(callback HttpMetricNameAlterCallback) StatsClient

	// SetHttpRequestSection sets metric section for HTTP Request metrics
	SetHttpRequestSection(section string) StatsClient

	// ResetHttpRequestSection resets metric section for HTTP Request metrics to default value that is "request"
	ResetHttpRequestSection() StatsClient
}

StatsClient is an interface for different methods of gathering stats

type StatsdIncrementer

type StatsdIncrementer struct {
	// contains filtered or unexported fields
}

StatsdIncrementer struct is Incrementer interface implementation that writes all metrics to statsd

func (*StatsdIncrementer) Increment

func (i *StatsdIncrementer) Increment(metric string)

Increment increments metric in statsd

func (*StatsdIncrementer) IncrementAll

func (i *StatsdIncrementer) IncrementAll(b Bucket)

IncrementAll increments all metrics for given bucket in statsd

func (*StatsdIncrementer) IncrementAllN

func (i *StatsdIncrementer) IncrementAllN(b Bucket, n int)

IncrementAllN increments all metrics for given bucket in statsd

func (*StatsdIncrementer) IncrementN

func (i *StatsdIncrementer) IncrementN(metric string, n int)

IncrementN increments metric by n in statsd

type StatsdStatsClient

type StatsdStatsClient struct {
	// contains filtered or unexported fields
}

StatsdStatsClient is StatsClient implementation for statsd

func NewStatsdStatsClient

func NewStatsdStatsClient(dsn, prefix string) *StatsdStatsClient

NewStatsdStatsClient builds and returns new StatsdStatsClient instance

func (*StatsdStatsClient) BuildTimeTracker

func (sc *StatsdStatsClient) BuildTimeTracker() TimeTracker

BuildTimeTracker builds timer to track metric timings

func (*StatsdStatsClient) Close

func (sc *StatsdStatsClient) Close()

Close statsd connection

func (*StatsdStatsClient) ResetHttpRequestSection

func (sc *StatsdStatsClient) ResetHttpRequestSection() StatsClient

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

func (*StatsdStatsClient) SetHttpMetricCallback

func (sc *StatsdStatsClient) SetHttpMetricCallback(callback HttpMetricNameAlterCallback) StatsClient

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

func (*StatsdStatsClient) SetHttpRequestSection

func (sc *StatsdStatsClient) SetHttpRequestSection(section string) StatsClient

SetHttpRequestSection sets metric section for HTTP Request metrics

func (*StatsdStatsClient) TrackOperation

func (sc *StatsdStatsClient) TrackOperation(section string, operation MetricOperation, tt TimeTracker, success bool)

TrackOperation tracks custom operation

func (*StatsdStatsClient) TrackOperationN

func (sc *StatsdStatsClient) TrackOperationN(section string, operation MetricOperation, tt TimeTracker, n int, success bool)

TrackOperationN tracks custom operation with n diff

func (*StatsdStatsClient) TrackRequest

func (sc *StatsdStatsClient) TrackRequest(r *http.Request, tt TimeTracker, success bool)

TrackRequest tracks HTTP Request stats

type StatsdTimeTracker

type StatsdTimeTracker struct {
	// contains filtered or unexported fields
}

StatsdTimeTracker struct is TimeTracker interface implementation that writes all timings to statsd

func (*StatsdTimeTracker) Finish

func (t *StatsdTimeTracker) Finish(bucket string)

Finish writes elapsed time for metric to statsd

func (*StatsdTimeTracker) Start

func (t *StatsdTimeTracker) Start() TimeTracker

Start starts timer

type TimeTracker

type TimeTracker interface {
	// Start starts timer
	Start() TimeTracker
	// Finish writes elapsed time for metric
	Finish(bucket string)
}

TimeTracker is a metric time tracking interface

func NewTimeTracker

func NewTimeTracker(c *statsd.Client, muted bool) TimeTracker

NewTimeTracker builds and returns new TimeTracker instance

Jump to

Keyboard shortcuts

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