From 13da6ff8dc794f9cad5245da063f3729a595d6bf Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 15 Aug 2016 11:05:29 -0400 Subject: [PATCH] initial prototype --- README.md | 26 ++++++++++++- handler.go | 50 ++++++++++++++++++++++++ setup.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 handler.go create mode 100644 setup.go diff --git a/README.md b/README.md index 8dca004..c9bb53e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ -caddy-dogstatsd +caddy dogstatsd =============== + +A [Caddy](https://caddyserver.com/) plugin for reporting metrics to [Datadog](https://datadoghq.com). + +## Installation + +## Configuration + +``` +dogstatsd [{host:port} [samplerate]] +``` + +``` +dogstatsd { + host {host:port} + samplerate {samplerate} + namespace {namespace} + tags {name:value} [{name:value}...] +} +``` + +## Metrics + +caddy.response.count - counter - number of requests handled +caddy.response.time - histogram - milliseconds spent handling request diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..4c45dc0 --- /dev/null +++ b/handler.go @@ -0,0 +1,50 @@ +package caddydogstatsd + +import ( + "fmt" + "net/http" + "time" + + "github.com/datadog/datadog-go/statsd" + "github.com/mholt/caddy/caddyhttp/httpserver" +) + +// DogstatsdHandler is a middleware handler for reporting dogstatsd metrics on requests +type DogstatsdHandler struct { + Client *statsd.Client + SampleRate float64 + Next httpserver.Handler +} + +// ServeHTTP is the middleware handler which will emit dogstatsd metrics after handling a request +func (h DogstatsdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + // If we do not have a statsd.Client configured, then skip any processing + if h.Client == nil { + return h.Next.ServeHTTP(w, r) + } + + // Grab the request start time + var start time.Time + start = time.Now() + + // Handle the request + var code int + var err error + code, err = h.Next.ServeHTTP(w, r) + + // Grab the request durection in Milliseconds + var elapsed time.Duration + var elapsedMS float64 + elapsed = time.Since(start) + elapsedMS = float64(elapsed.Nanoseconds()) / float64(time.Millisecond) + + // Report our request metrics to dogstatsd + var client statsd.Client + client = *h.Client + var extraTags = []string{ + fmt.Sprintf("status_code:%d", code), + } + client.Count("caddy.response.count", 1, extraTags, h.SampleRate) + client.TimeInMilliseconds("caddy.response.time", elapsedMS, extraTags, h.SampleRate) + return code, err +} diff --git a/setup.go b/setup.go new file mode 100644 index 0000000..f38bc7c --- /dev/null +++ b/setup.go @@ -0,0 +1,111 @@ +package caddydogstatsd + +import ( + "fmt" + "strconv" + "strings" + + "github.com/datadog/datadog-go/statsd" + "github.com/mholt/caddy" + "github.com/mholt/caddy/caddyhttp/httpserver" +) + +func init() { + // register our plugin with Caddy + caddy.RegisterPlugin("dogstatsd", caddy.Plugin{ + ServerType: "http", + Action: setup, + }) +} + +func setup(c *caddy.Controller) error { + for c.Next() { + // only parse if the initial directive is "dogstatsd" + if c.Val() != "dogstatsd" { + continue + } + + // default config values + var namespace = "" + var host = "127.0.0.1:8125" + var globalTags = []string{} + var sampleRate = 1.0 + + // if we have a block, then parse that + // e.g. + // dogstatsd { + // host 127.0.0.1:8125 + // } + for c.NextBlock() { + // each line if of the format `{key} {arg} [{arg}...]` + var key string + key = c.Val() + + var args []string + args = c.RemainingArgs() + // we expect every directive to have at least 1 argument + if len(args) == 0 { + return c.ArgErr() + } + + // parse directives + switch key { + case "host": + host = args[0] + case "samplerate": + var err error + sampleRate, err = strconv.ParseFloat(args[0], 64) + if err != nil { + return c.SyntaxErr(fmt.Sprintf("expected float for \"samplerate\", instead found \"%s\"", args[0])) + } + case "namespace": + namespace = args[0] + if !strings.HasSuffix(namespace, ".") { + namespace += "." + } + case "tags": + globalTags = args + default: + return c.SyntaxErr(fmt.Sprintf("expected one of \"host\", \"samplerate\", \"namespace\", \"tags\", instead found \"%s\"", key)) + } + } + + // handle non-block configuration + // e.g. + // dogstatsd [{host:port} [{samplerate}]] + if c.NextArg() { + var args []string + args = c.RemainingArgs() + if len(args) > 0 { + host = args[0] + } + if len(args) > 1 { + var err error + sampleRate, err = strconv.ParseFloat(args[1], 64) + if err != nil { + return c.SyntaxErr(fmt.Sprintf("expected float for \"samplerate\", instead found \"%s\"", args[1])) + } + } + } + + // add our middleware + var cfg *httpserver.SiteConfig + cfg = httpserver.GetConfig(c) + cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { + var client *statsd.Client + var err error + client, err = statsd.New(host) + if err == nil { + client.Namespace = namespace + client.Tags = globalTags + } + + return DogstatsdHandler{ + Client: client, + SampleRate: sampleRate, + Next: next, + } + }) + } + return nil +}