From 1f0e0f163a0dc3d7c9be2b13cb54990e4cbd30dd Mon Sep 17 00:00:00 2001
From: Will McCutchen <will@mccutch.org>
Date: Thu, 31 Mar 2022 12:16:09 -0400
Subject: [PATCH] Add example demonstrating custom instrumentation (#75)

---
 README.md                                 | 17 ++++++++--
 examples/custom-instrumentation/README.md | 17 ++++++++++
 examples/custom-instrumentation/go.mod    | 15 +++++++++
 examples/custom-instrumentation/go.sum    | 21 ++++++++++++
 examples/custom-instrumentation/main.go   | 40 +++++++++++++++++++++++
 5 files changed, 108 insertions(+), 2 deletions(-)
 create mode 100644 examples/custom-instrumentation/README.md
 create mode 100644 examples/custom-instrumentation/go.mod
 create mode 100644 examples/custom-instrumentation/go.sum
 create mode 100644 examples/custom-instrumentation/main.go

diff --git a/README.md b/README.md
index c97c5a3..565f36b 100644
--- a/README.md
+++ b/README.md
@@ -54,8 +54,8 @@ $ docker run -e HTTPS_CERT_FILE='/tmp/server.crt' -e HTTPS_KEY_FILE='/tmp/server
 ```
 
 The `github.com/mccutchen/go-httpbin/httpbin/v2` package can also be used as a
-library for testing an applications interactions with an upstream HTTP service,
-like so:
+library for testing an application's interactions with an upstream HTTP
+service, like so:
 
 ```go
 package httpbin_test
@@ -87,6 +87,17 @@ func TestSlowResponse(t *testing.T) {
 ```
 
 
+## Custom instrumentation
+
+If you're running go-httpbin in your own infrastructure and would like custom
+instrumentation (metrics, structured logging, request tracing, etc), you'll
+need to wrap this package in your own code and use the included
+[Observer][observer] mechanism to instrument requests as necessary.
+
+See [examples/custom-instrumentation][custom-instrumentation] for an example
+that instruments every request using DataDog.
+
+
 ## Installation
 
 To add go-httpbin to an existing golang project:
@@ -147,3 +158,5 @@ make imagepush
 [httpbin-repo]: https://github.com/kennethreitz/httpbin
 [ahmet]: https://github.com/ahmetb/go-httpbin
 [docker-hub]: https://hub.docker.com/r/mccutchen/go-httpbin/
+[observer]: https://pkg.go.dev/github.com/mccutchen/go-httpbin/v2/httpbin#Observer
+[custom-instrumentation]: ./examples/custom-instrumentation/
diff --git a/examples/custom-instrumentation/README.md b/examples/custom-instrumentation/README.md
new file mode 100644
index 0000000..6fc07a4
--- /dev/null
+++ b/examples/custom-instrumentation/README.md
@@ -0,0 +1,17 @@
+# Custom Instrumentation
+
+This example demonstrates how to use go-httpbin's [`Observer`][1] mechanism to
+add custom instrumentation to a go-httpbin instance.
+
+An _observer_ is a function that will be called with an [`httpbin.Result`][2]
+struct after every request, which provides a hook for custom logging, metrics,
+or other instrumentation.
+
+Note: This does require building your own small wrapper around go-httpbin, as
+you can see in [main.go](./main.go) here.  That's because go-httpbin has no
+dependencies outside of the Go stdlib, to make sure that it is as
+safe/lightweight as possible to include as a dependency in other applications'
+test suites where useful.
+
+[1]: https://pkg.go.dev/github.com/mccutchen/go-httpbin/v2/httpbin#Observer
+[2]: https://pkg.go.dev/github.com/mccutchen/go-httpbin/v2/httpbin#Result
diff --git a/examples/custom-instrumentation/go.mod b/examples/custom-instrumentation/go.mod
new file mode 100644
index 0000000..bb85219
--- /dev/null
+++ b/examples/custom-instrumentation/go.mod
@@ -0,0 +1,15 @@
+module httpbin-instrumentation
+
+go 1.18
+
+require (
+	github.com/DataDog/datadog-go v4.8.3+incompatible
+	github.com/mccutchen/go-httpbin/v2 v2.3.0
+)
+
+require (
+	github.com/Microsoft/go-winio v0.5.2 // indirect
+	github.com/stretchr/objx v0.3.0 // indirect
+	github.com/stretchr/testify v1.3.0 // indirect
+	golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
+)
diff --git a/examples/custom-instrumentation/go.sum b/examples/custom-instrumentation/go.sum
new file mode 100644
index 0000000..6c5e5f0
--- /dev/null
+++ b/examples/custom-instrumentation/go.sum
@@ -0,0 +1,21 @@
+github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q=
+github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/mccutchen/go-httpbin/v2 v2.3.0 h1:NqVqPVI8Ushb/YJIe9bXiruBl0CDBSvp7BQSPXrY5qs=
+github.com/mccutchen/go-httpbin/v2 v2.3.0/go.mod h1:+DBHcmg6EOeoizuiOI8iL12VIHXx+9YQNlz+gjB9uxk=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
+github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/examples/custom-instrumentation/main.go b/examples/custom-instrumentation/main.go
new file mode 100644
index 0000000..4415fc4
--- /dev/null
+++ b/examples/custom-instrumentation/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+
+	"github.com/DataDog/datadog-go/statsd"
+
+	"github.com/mccutchen/go-httpbin/v2/httpbin"
+)
+
+func main() {
+	statsdClient, _ := statsd.New("")
+
+	h := httpbin.New(
+		httpbin.WithObserver(datadogObserver(statsdClient)),
+	)
+
+	listenAddr := "0.0.0.0:8080"
+	http.ListenAndServe(listenAddr, h.Handler())
+}
+
+func datadogObserver(client statsd.ClientInterface) httpbin.Observer {
+	return func(result httpbin.Result) {
+		// Log the request
+		log.Printf("%d %s %s %s", result.Status, result.Method, result.URI, result.Duration)
+
+		// Submit a new distribution metric to datadog with tags that allow
+		// graphing request rate, timing, errors broken down by
+		// method/status/path.
+		tags := []string{
+			fmt.Sprintf("method:%s", result.Method),
+			fmt.Sprintf("status_code:%d", result.Status),
+			fmt.Sprintf("status_class:%dxx", result.Status/100),
+			fmt.Sprintf("uri:%s", result.URI),
+		}
+		client.Distribution("httpbin.request", float64(result.Duration.Milliseconds()), tags, 1.0)
+	}
+}
-- 
GitLab